1- # External Phone Provider Function — Language-Agnostic Contract
1+ # External Phone Provider Function: Language-Agnostic Contract
22
33This defines the shared contract for [ JavaScript] ( ../javascript/ ) , [ Python] ( ../python/ ) and
44[ .NET] ( ../dotnet/ ) . See [ production limitations] ( #production-limitations ) before production use.
@@ -34,7 +34,7 @@ Forwarded headers, including `x-ms-client-principal`, do not establish trust by
3434replace the required Easy Auth gate. The handler does not use them to authenticate callers or forward
3535the incoming ` Authorization ` header to the provider. Configure Easy Auth as described in section 5.
3636
37- ### EPP request body — ` Envelope ` (cleartext envelope)
37+ ### EPP request body: ` Envelope ` (cleartext envelope)
3838
3939| Field | Required | Notes |
4040| -------| ----------| -------|
@@ -265,19 +265,19 @@ success-looking status. Explicit `Block`/`StepUp` outcomes remain non-success re
265265| ` Fail ` | ` 401 ` | provider returned 401/403 (auth) |
266266| ` Fail ` | ` 400 ` | other provider 4xx |
267267| ` Fail ` | ` 502 ` | other provider error, or missing credential/endpoint |
268- | — | ` 504 ` | request to the provider timed out |
269- | — | ` 502 ` | network error to the provider (non-timeout) |
268+ | N/A | ` 504 ` | request to the provider timed out |
269+ | N/A | ` 502 ` | network error to the provider (non-timeout) |
270270
271271---
272272
273273## 3. Provider adapter contract
274274
275275Each provider is one unit exposing three things:
276276
277- - ** ` manifest ` ** — protocol facts only:
278- - ` id ` — provider id selected by ` EPP_PROVIDER_NAME ` ; its base URL is ` EPP_PROVIDER_ENDPOINT `
279- - ` auth ` — ` { mode: 'apiKey', keyVaultSecretName, identityKeyVaultSecretName? } ` ; other modes fail closed
280- - ` responseMapping ` — map of provider status → ` Continue ` | ` Fail ` | ` Block ` | ` StepUp ` (+ ` default ` )
277+ - ** ` manifest ` ** : protocol facts only:
278+ - ` id ` : provider id selected by ` EPP_PROVIDER_NAME ` ; its base URL is ` EPP_PROVIDER_ENDPOINT `
279+ - ` auth ` : ` { mode: 'apiKey', keyVaultSecretName, identityKeyVaultSecretName? } ` ; other modes fail closed
280+ - ` responseMapping ` : map of provider status → ` Continue ` | ` Fail ` | ` Block ` | ` StepUp ` (+ ` default ` )
281281- ** ` buildRequest({ channel, endpoint, dispatch, credential, env }) ` ** → ` { url, method, headers, body } `
282282- ** ` parseResponse({ httpStatus, ok, json }) ` ** → ` ParsedResponse ` , containing ` success ` ,
283283 ` providerHttpStatus ` , optional ` providerMessageId ` , ` providerStatusName ` , ` providerStatusCode `
@@ -298,8 +298,36 @@ class hierarchy is required.
298298
299299Adapters require registration in the chosen runtime. Consult the selected adapter and its manifest
300300for required credentials and options: the manifest declares secret names and protocol mappings;
301- the implementation reads adapter-specific options from app settings. Do not duplicate individual
302- API contracts or credential catalogs in shared onboarding documentation.
301+ the implementation reads adapter-specific options from app settings. Individual API contracts remain
302+ in the adapters; the [ onboarding credential naming table] ( ONBOARDING.md#provider-credential-names )
303+ lists the exact manifest secret names for provisioning and authorized local tests. Keep that table
304+ aligned with the manifests; never include secret values in documentation or the settings sample.
305+
306+ ### Telesign CYOT integration
307+
308+ SMS and Voice both use ` POST https://verify.telesign.com/integration/msft/cyot ` with JSON. Configure
309+ the base URL as ` https://verify.telesign.com ` . The adapter supplies ` recipient.phone_number ` , the
310+ unchanged ` message.text ` , optional ` message.language ` , one selected ` channels[].channel ` , and
311+ ` correlation_id ` . Keep the leading ` + ` in the E.164 phone number; the guide's example ` 12345678 `
312+ does not satisfy its own required phone-number pattern.
313+
314+ Phase 1 supports Basic and Digest; this sample implements Basic only. Per
315+ [ Telesign's authentication instructions] ( https://developer.telesign.com/enterprise/docs/authentication#basic-authentication ) ,
316+ the header is ` Authorization: Basic <base64(UTF8(customer-id:api-key))> ` , using the raw Customer ID
317+ and API Key strings from Key Vault. Do not decode the API key first, send the API key alone, or
318+ substitute a key identifier. The guide's ` Basic YOUR_API_KEY ` is abbreviated, not the literal encoding.
319+ Provider-token authentication is described as Phase 2 and is not implemented for Telesign here.
320+
321+ The optional ` account_lifecycle_event ` and ` originating_ip ` fields are reserved for future intelligence
322+ capabilities. They are omitted; do not infer an originating address from the Function or synthesize
323+ account events to fill them.
324+
325+ Telesign's ` X-Shutter-Mode: true ` suppresses delivery at the provider while still calling its endpoint.
326+ It is appropriate for an explicitly authorized, direct provider diagnostic, not normal OTP delivery.
327+ The production adapter does not add or forward this header. Function evaluation mode remains separate:
328+ it validates/decrypts and skips all provider HTTP. A successful provider shutter probe is not evidence
329+ that an SMS was delivered or a Voice call was placed. Enabling the API globally also does not prove
330+ that a particular Customer ID/API Key pair is authorized for this integration.
303331
304332---
305333
@@ -359,10 +387,10 @@ subscription activation and changing tenant policy belong to provisioning, not t
359387
360388## 5. Required behaviors
361389
362- - ** Fail-closed** — only ` Continue ` → ` 200 accepted ` ; unknown status → ` Fail ` .
363- - ** Managed identity** — Key Vault access via managed identity only (user-assigned if ` AZURE_CLIENT_ID `
390+ - ** Fail-closed** : only ` Continue ` → ` 200 accepted ` ; unknown status → ` Fail ` .
391+ - ** Managed identity** : Key Vault access via managed identity only (user-assigned if ` AZURE_CLIENT_ID `
364392 set, else system-assigned). No static credentials.
365- - ** Privacy** — never log phone numbers, passcodes, nonce values, bearer tokens, API keys, JWE headers/payloads,
393+ - ** Privacy** : never log phone numbers, passcodes, nonce values, bearer tokens, API keys, JWE headers/payloads,
366394 raw exceptions or provider responses. There is no plaintext diagnostic override. Each handler
367395 writes one summary with a generated request ID, the first 16 lowercase hex characters of the
368396 correlation ID's SHA256 hash, HTTP status,
@@ -373,7 +401,7 @@ subscription activation and changing tenant policy belong to provisioning, not t
373401 This distinguishes token attachment from API-key fallback; it does not expose token values or claims.
374402 A configured encryption-key-ID mismatch adds a fixed warning, never either key ID or the JWE header.
375403 Disable SDK, platform and proxy body tracing separately.
376- - ** Platform authentication only** — enable Easy Auth with ` requireAuthentication=true ` ,
404+ - ** Platform authentication only** : enable Easy Auth with ` requireAuthentication=true ` ,
377405 ` unauthenticatedClientAction=Return401 ` and ` requireHttps=true ` . Configure the trusted tenant issuer
378406 and ` allowedAudiences ` for the endpoint app, plus a ** nonempty ` allowedApplications ` ** list pinned to
379407 the authorized SAS caller application ID. No excluded path may bypass authentication for SendOtp.
@@ -383,7 +411,7 @@ subscription activation and changing tenant policy belong to provisioning, not t
383411 or bypassed.** Core Tools supplies no Easy Auth: local execution must bind only to loopback, with
384412 no tunnels or public forwarding. Neither request data, JWE decryption, a fixed nonce nor forwarded
385413 principal headers authenticate the SAS caller.
386- - ** Timeout boundaries** — platform authentication and Key Vault retrieval happen outside the outbound HTTP
414+ - ** Timeout boundaries** : platform authentication and Key Vault retrieval happen outside the outbound HTTP
387415 timer. Python uses connect/read inactivity timeouts, not a hard elapsed-time deadline. The cap
388416 therefore does not guarantee a 3.2-second end-to-end response, especially on cold starts.
389417 A timed-out POST may already have been accepted; avoid blind retries that duplicate messages.
0 commit comments