Documentation | Deployment guide | Troubleshooting
Use the deployment guide for your first deployment. This reference covers permissions, configuration, repeatable deployments, and maintenance. Use the documentation index to choose between guides. Provider-owned forks use this same launcher; see Telephony provider guide (BYOT).
| Task | Section |
|---|---|
| Prepare access and tools | Prerequisites |
| Choose hosting | Service plans |
| Deploy | Run setup |
| Review application changes | Application registration |
| Understand provider routing | Provider configuration |
| Keep or replace a certificate | Certificate selection |
| Automate a reviewed deployment | Noninteractive example |
| Pin source and packages | Source versioning |
| Find saved values | Deployment summary |
| Activate or retire the endpoint | Activation, rollback |
- PowerShell 7+ or Azure Cloud Shell in PowerShell mode.
- Azure CLI 2.60.0+ for FC1 or 2.48.1+ for EP1, with a customer-tenant user login in Azure public cloud.
- Network access to GitHub, Azure, Microsoft Graph, Key Vault, and Function deployment endpoints.
- An available Linux FC1 or EP1 region with sufficient subscription quota.
- Provider account access and the test and activation procedure for your tenant. See before deployment.
Install missing tools using the official PowerShell and Azure CLI instructions, then check:
$PSVersionTable.PSVersion
az versionSetup can install the Azure CLI Bicep component after confirmation.
For unattended runs, install it beforehand or supply -InstallPrerequisites.
| Scope | Required access |
|---|---|
| Azure subscription | Owner, or Contributor plus Role Based Access Control Administrator with permission to create the required role assignments |
| Microsoft Entra ID | Application and enterprise-application management, plus Privileged Role Administrator for the Microsoft first-party service principal's Graph Application.Read.All grant |
| New application | Permission to register applications; an appropriate role such as Application Developer can provide this when user registration is restricted |
| Policy activation | Authentication Policy Administrator, used after deployment and validation |
Setup deploys at subscription scope and creates a resource group. Activate eligible PIM roles before running it. Azure roles and Microsoft Entra roles grant access to different resources. See Azure built-in roles and application creation permissions.
Microsoft Graph calls use the Azure CLI user session. OAuth setup uses the Microsoft Graph beta allowed-tenants configuration; confirm that your tenant permits this configuration.
Certificate reuse also requires operator access to the source vault's ARM resource, certificate, and secret-version metadata. For another vault, the operator must be able to grant the Function secret-read access. See certificate selection.
| Plan | Configuration deployed by setup |
|---|---|
Flex Consumption (FC1, default) |
Linux, 2048 MB, up to 40 on-demand instances, zero always-ready instances |
Premium (EP1) |
Linux, one warm instance, up to three elastic workers |
FC1 can scale to zero and cold-start. Its free usage grant is subject to Azure's billing terms. EP1 incurs a baseline compute charge while idle. Both plans also use storage, Key Vault, and monitoring. Review the cost estimate.
Press Enter to choose FC1 interactively. Unattended runs require -ServicePlan.
To change plans, deploy with a different resource prefix. For an independent endpoint, also use
a dedicated application. Coordinate certificate continuity when sharing an application.
Download the launcher into a private working folder:
Invoke-WebRequest `
-Uri 'https://raw.githubusercontent.com/Azure-Samples/ExternalPhoneProvider-AzureFunction-Sample/main/setup/Setup-Epp.ps1' `
-OutFile .\Setup-Epp.ps1Review it, then run:
.\Setup-Epp.ps1- Select the customer tenant and subscription.
- Choose Create a new application. For a rerun, select the existing application or enter its client ID.
- Choose an Azure region. Setup detects the tenant's EU or Global provider route.
- Enter or confirm the provider name: Infobip, Soprano, or Telesign.
- Choose the hosting plan and resource prefix. JavaScript is the default runtime; use
-Language csharpor-Language pythonto change it. - Review certificate handling if the app has existing keys.
- Review the complete plan. Enter Yes or Y to deploy; No, N, or Enter cancels.
Enter accepts a displayed menu default. Provider-name selection has no default. Use 0 or Cancel where offered to stop a selection.
The plan lists the resource names, provider URLs, source and package versions, permissions,
certificate changes, and required Azure resource-provider registrations. For a new application,
it shows name patterns containing <app-id-hash>; setup calculates the final names after
creating the app.
Setup keeps public ingress closed while preparing the resources and encryption key. It checks Easy Auth before opening ingress and verifies Function registration after publication. Wait for Infrastructure and Function deployment completed successfully and the saved summary. This confirms deployment, not live provider delivery.
If a new application is created but deployment fails later, use the client ID saved in
application-<client-id>.json and the original prefix when retrying.
See safe recovery.
Run setup interactively and answer No at the final deployment prompt.
Leave -ApproveDeployment unset.
Preflight downloads and verifies packages, checks permissions and regional availability, and compiles Bicep. Sign-in and tool-installation prompts are separate from deployment approval. Azure resources and application configuration change only after deployment approval.
Use parameters to skip selections you have already made while retaining the final plan review. Replace the IDs, region, provider, and prefix with your deployment values:
.\Setup-Epp.ps1 `
-TenantId '<customer-tenant-id>' `
-SubscriptionId '<subscription-id>' `
-ApplicationId '<existing-client-id>' `
-Location westus2 `
-Language javascript `
-ServicePlan FC1 `
-Provider '<provider-id>' `
-ResourcePrefix contosoUse infobip, soprano, or telesign for <provider-id>.
The resource-name suffix comes from the subscription, application client ID, and prefix.
Keep those values for reruns. Changing the provider or location while retaining them can
reconfigure the existing deployment rather than create another endpoint.
Use these milestones to follow the deployment:
| Stage | What setup does | What to retain or check |
|---|---|---|
| Application | Creates the selected new registration, if requested, and resolves resource names | Keep the printed client ID and application-<client-id>.json for recovery. |
| Resource providers | Registers missing required Azure namespaces | Resolve permission or regional-availability failures before retrying. |
| Infrastructure | Creates or updates the regional resources with public Function ingress closed | Inspect the named subscription deployment and resource group if this stage fails. |
| Encryption | Creates or reuses the approved certificate and verifies its versioned secret reference | Check the certificate ID, key ID, and expiration in the summary. |
| Application trust | Applies and reads back the application, role, certificate, and federation configuration | Confirm the reviewed permission and certificate changes. |
| Publication | Verifies Easy Auth, opens ingress, publishes the package, and verifies Function registration | Wait for startup verification, not just package upload. |
| Handoff | Saves deployment-*.json and prints next steps |
Retain the summary and public certificate, then complete provider authorization and validation. |
FC1 publishes through One Deploy. EP1 uploads the verified package to private Blob storage and uses managed-identity run-from-package, followed by restart and trigger synchronization. Publication and resource deployment can each take several minutes; keep the session open. If publication fails, setup attempts to close ingress again. Inspect any reported failure to close it before proceeding.
An application-creation record is a recovery aid. Use the completed deployment summary to identify the endpoint ready for validation.
For a new deployment, choose Create a new application during guided setup. The name defaults to EPP Application, and creation occurs after you approve the deployment plan. Follow the download-and-run instructions.
For an existing registration, supply its Application (client) ID.
Unattended setup requires an existing -ApplicationId.
Setup configures:
- For OAuth providers, organizational multitenancy and an allowed-tenant list containing the provider tenants, while preserving existing allowed tenants. The application home tenant remains allowed.
- For API-key providers, a single-tenant (
AzureADMyOrg) endpoint application, with no provider-tenant restriction configuration. - The endpoint enterprise application, required assignment, and the
Epp.Invokerole assigned to the Microsoft phone-provider service principal. - Microsoft Graph
Application.Read.Allfor that Microsoft service principal. - The identifier URI, public encryption certificate, and Easy Auth caller restriction.
- For OAuth providers only, an outbound user-assigned managed identity and its federated credential.
API-key setup stops if an existing endpoint application is multi-tenant. Review that application's provider dependencies and trusts manually, or choose a new dedicated application. Setup does not automatically convert it to single-tenant or remove existing tenant restrictions or federation. Incoming Microsoft caller authentication, app-role grants, encryption, and the Function's system-assigned identity remain required for both authentication modes.
Exact existing issuer/subject/audience federation matches are reused. Other trusts are preserved. Complete provider-side authorization after deployment.
Setup reads the selected profile from providers. All bundled profiles use OAuth and supply complete HTTPS URLs for both channels.
| Profile field | Purpose |
|---|---|
deployment.tenantId |
Provider's Security Store tenant; used for discovery |
deployment.AccessTokenScopeTenant |
OAuth token tenant; when omitted, setup uses tenantId |
deployment.routes.global / deployment.routes.eu |
Channel URLs, API application ID, scope, and timing values |
Setup maps the token tenant to EPP_PROVIDER_TENANT_ID and includes both provider tenants in
the application's reviewed allowlist. The Telesign,
Soprano, and Infobip profiles contain
the values used by guided setup.
Setup reads tenant_region_scope from the selected tenant's OpenID Connect discovery document.
EU tenants use the EU provider route; other scopes use Global.
An explicit -EndpointRegion eu or -EndpointRegion global must match that detected route.
EU tenants receive a shortlist of Azure-recommended physical regions within the
EU Data Boundary.
Manual entry and -Location remain available. An out-of-boundary or unverifiable EU location
produces a warning for review. Choose a location that meets your organization's requirements;
the recommendation is not a data-residency guarantee.
| Existing registered encryption certificates | Available action |
|---|---|
| None | Create a certificate automatically |
| One or more | Replace (interactive default) or Reuse |
Important
Replace removes all registered encryption credentials and registers one new certificate after approval. Reuse keeps the selected key and removes other encryption entries after approval. Coordinate changes across every endpoint sharing the application.
Signing credentials and Key Vault objects are preserved. The final plan lists the exact key IDs to keep or remove. Unattended runs with existing encryption credentials require an explicit action.
- Select Reuse and the registered encryption key ID.
- In Key Vault > Certificates > certificate > version, copy the
Certificate Identifier:
https://<vault>.vault.azure.net/certificates/<name>/<version>. - Supply
-KeyVaultSubscriptionIdif that vault is in another subscription in the same tenant. - Review the matching certificate and required vault permissions in the deployment plan.
The certificate must exactly match the registered certificate. It must use exportable RSA of at least 2048 bits and an enabled, certificate-managed PEM backing secret. Both the certificate and registered credential must be valid with more than 30 days remaining.
The Function's system-assigned identity reads the private-key reference. For a different vault, setup grants Key Vault Secrets User at the certificate secret's scope in RBAC mode. For an access-policy vault, the reviewed grant is Secrets/Get at vault scope. Provide a working network path from the Function to the vault.
Setup resolves the backing secret's exact version; certificate and secret version IDs can differ. It downloads only the public certificate and pins the Function to the versioned secret.
Replacement stages a new certificate before updating the registration. Setup verifies that the
Function can resolve the secret reference, then registers one matching encryption credential.
New certificates are exportable RSA-2048 with subject CN=ExternalPhoneProvider.
Coordinate a maintenance window: cached or in-flight requests can still use the previous key. Complete an authorized evaluation before returning the endpoint to service. After a partial failure, inspect the registration and select Reuse if the new key was already registered.
First review the corresponding interactive plan. Establish an Azure CLI user session in the intended tenant; MFA or consent can still require interaction.
Replace the placeholders and choose the region, plan, and prefix.
For <provider-id>, use infobip, soprano, or telesign.
$tenantId = '<customer-tenant-guid>'
$subscriptionId = '<subscription-guid>'
$applicationId = '<dedicated-app-client-guid>'
az login --tenant $tenantId
if ($LASTEXITCODE -ne 0) { throw 'Azure sign-in failed.' }
az account get-access-token --subscription $subscriptionId --output none
if ($LASTEXITCODE -ne 0) { throw 'Azure subscription token acquisition failed.' }
az account get-access-token --subscription $subscriptionId --resource-type ms-graph --output none
if ($LASTEXITCODE -ne 0) { throw 'Microsoft Graph token acquisition failed.' }
.\Setup-Epp.ps1 `
-TenantId $tenantId `
-SubscriptionId $subscriptionId `
-ApplicationId $applicationId `
-Location westus2 `
-Provider '<provider-id>' `
-Language javascript `
-ServicePlan FC1 `
-ResourcePrefix contoso `
-OutputDirectory .\epp-output `
-NonInteractive `
-ApproveDeploymentIn Cloud Shell, use the intended directory's existing login in place of az login.
Both -NonInteractive and -ApproveDeployment are required for an unattended deployment.
The example assumes the application has no registered encryption certificate.
For reuse, add these parameters to the setup command:
| Parameter | Value |
|---|---|
-CertificateAction |
Reuse |
-EncryptionKeyId |
Registered key ID to keep |
-KeyVaultCertificateId |
https://<vault>.vault.azure.net/certificates/<name>/<version> |
For deliberate replacement, use -CertificateAction Replace instead.
| Additional option | Purpose |
|---|---|
-ForceAuthentication |
Fresh device-code sign-in for interactive runs |
-InstallPrerequisites |
Authorize installation of a missing Azure CLI Bicep component |
-OutputDirectory |
Private output folder; default is epp-output beside the launcher |
-PassThru |
Return the structured deployment result in addition to saving it |
-SourceRepository, -SourceRef, -PackageReleaseTag |
Pin source and packages |
-PackagePath |
Deploy a ready-to-run ZIP from a local path or public HTTPS URL without GitHub release lookup or checksum comparison |
-ProviderEnvironment |
Dictionary of declared non-secret provider inputs; required inputs are collected before approval, and reruns preserve their current values |
For provider-fork profiles using API keys, setup never asks for credentials. After approval, it creates disabled placeholders only for missing credential secrets and preserves existing versions without reading their values. Upload new enabled versions with real credentials in the reported Key Vault after setup; never enable the dummy values. See API-key credential handoff.
Defaults are -SourceRepository Azure-Samples/ExternalPhoneProvider-AzureFunction-Sample and
-SourceRef main. Setup resolves one commit for its supporting scripts, Bicep templates,
catalogs, and profiles.
Packages come from stable GitHub releases:
| Runtime | Tag format |
|---|---|
| JavaScript | epp-packages-<run>-<attempt>-javascript |
| C# | epp-packages-<run>-<attempt>-dotnet |
| Python | epp-packages-<run>-<attempt>-python |
Each release contains released-package.zip and SHA256SUMS.txt. Setup verifies the ZIP
against the checksum from the same release.
For a repeatable deployment, pin a reviewed full commit SHA with -SourceRef and an exact
matching language tag with -PackageReleaseTag. Use the values recorded in the deployment summary.
To deploy custom source, publish a reviewed public fork and matching stable package release,
then select that repository, source commit, and release.
Supply a local ZIP or a public HTTPS URL instead of selecting a GitHub release:
# Tenant, subscription, and application selection remain interactive.
.\Setup-Epp.ps1 -SourceRef byot -Language javascript `
-PackagePath '.\released-package-byot.zip'
.\Setup-Epp.ps1 -SourceRef byot -Language javascript `
-PackagePath 'https://downloads.contoso.com/released-package-byot.zip'Replace the example URL with the actual download URL. HTTPS URLs must use a public hostname on port 443, without embedded credentials, query strings, or fragments. Local paths may be relative or absolute. Supply a ready-to-deploy ZIP with the selected runtime's entry point and dependencies, not a source archive.
-PackagePath cannot be combined with -PackageReleaseTag. It skips GitHub release lookup
and checksum comparison, but still validates the archive and records its hash. The plan
identifies that hash as unverified; the saved summary sets packageChecksumVerified to false.
Setup stages the ZIP before approval and still downloads setup assets from SourceRef.
Omitting -PackagePath retains the existing release selection and checksum verification.
Open the timestamped deployment-*.json in epp-output. Keep it in restricted storage.
| Fields | Purpose |
|---|---|
tenantId, subscriptionId, applicationId |
Customer context and endpoint application client ID |
resources |
Azure resource names |
provider, endpointRegion, tenantRegionScope |
Selected provider and detected routing scope |
providerTenantId, accessTokenScopeTenant |
Security Store tenant and OAuth token tenant |
providerSmsEndpoint, providerVoiceEndpoint |
Outbound channel URLs |
endpointUrl, identifierUri |
Endpoint and application identifier for testing and activation |
encryptionKeyId, certificateThumbprint, certificateId, certificateSecretId |
Encryption credential and exact certificate/secret versions |
certificateExpiresUtc, certificateRenewal |
Expiration and manual renewal requirement |
certificateAction, removedEncryptionKeyIds |
Approved encryption-credential changes |
certificateVaultSubscriptionId, certificateReferenceResolved |
Certificate source subscription and result of the Function's reference-resolution check |
allowedProviderTenantIds |
Reviewed provider-tenant allowlist |
endpointServicePrincipalId, microsoftPhoneProviderServicePrincipalId |
Tenant-local enterprise-application object IDs; use applicationId for the endpoint client ID |
sourceRevision, packageReleaseTag, packageSha256, language, servicePlan |
Deployment provenance and runtime |
source, packageUrl, sourcePackageSha256 |
Resolved setup-source URL, downloaded package URL, and verified source-package hash |
policyChanged |
false; activate policy after endpoint validation |
C# is recorded as csharp in the summary and uses dotnet package tags.
The deployment summary confirms setup completion. Verify
provider authorization and delivery
before activation.
Certificates issued by setup have 12-month validity and manual renewal.
The lifetime policy uses EmailContacts 30 days before expiry. Configure
Key Vault certificate contacts
and an independent renewal reminder with an owner.
The Function uses a pinned secret version. Renewing a certificate in Key Vault requires a coordinated update to the Microsoft Entra ID registration and the Function reference.
- Record the certificate and registered credential expiration dates.
- Schedule renewal before the 30-day remaining-validity limit.
- Use Replace during approved maintenance, or synchronize the renewed certificate through your administrator procedure and Reuse that exact registered version.
- Validate decryption and controlled live delivery after the change.
Include every endpoint sharing the application in the maintenance plan. Run setup against an application one session at a time.
Complete provider authorization and endpoint validation
first. Then have the Authentication Policy Administrator use Microsoft's current
telephony-provider configuration procedure for the tenant and channel.
The SMS
and voice
policies expose the integration through
telecomProviderSettings.
Important
The examples below use Microsoft Graph beta. Microsoft marks beta APIs as subject to change and unsupported for production applications. Confirm the supported administration procedure and feature availability for your tenant before changing a production policy.
-
Open Graph Explorer, sign in, and verify that its directory matches
tenantIdin the deployment summary. -
Activate the Authentication Policy Administrator role and select beta.
-
Select GET and enter the URL for the channel:
Channel URL SMS https://graph.microsoft.com/beta/policies/authenticationMethodsPolicy/authenticationMethodConfigurations/smsVoice https://graph.microsoft.com/beta/policies/authenticationMethodsPolicy/authenticationMethodConfigurations/voice -
In Modify permissions, consent to
Policy.ReadWrite.AuthenticationMethodfor this change. If it is not listed, use the profile menu's Consent to permissions option. -
Run the query. Save the
200 OKpolicy response in restricted storage with the tenant, channel, UTC time, and change owner. -
Record existing provider settings, method state, targeting, and channel-specific properties. Agree on a pilot scope, observation window, and rollback trigger.
Use Policy.Read.AuthenticationMethod for a separate read-only policy inspection.
The write permission above is needed when performing the update.
-
Run GET again immediately before the change. Stop and review if another administrator has changed the saved policy.
-
Change the method to PATCH, keeping the same channel URL.
-
In Request body, enter the JSON below for SMS. Replace
urlwith the summary'sendpointUrlandappIdwith its application client ID.{ "@odata.type": "#microsoft.graph.smsAuthenticationMethodConfiguration", "telecomProviderSettings": { "url": "https://<function-host>/api/SendOtp", "appId": "<application-client-id>", "isEvaluationModeEnabled": true, "migrationPercentage": 1 } } -
For voice, use the voice URL and change
@odata.typeto#microsoft.graph.voiceAuthenticationMethodConfiguration. -
Review the endpoint, client ID, evaluation flag, and percentage.
1is an illustrative starting percentage; use the value agreed for your evaluation. Keep the existing method state, targeting, and unrelated properties unchanged. -
Confirm Request headers includes
Content-Type: application/json. -
Run PATCH and expect
204 No Content. Switch back to GET and compare the returned settings with the intended change and saved policy. -
Run the authorized evaluation procedure for the channel and check the matching Function logs before continuing.
| Provider setting | Value to use |
|---|---|
url |
Public SendOtp URL, or the validated Front Door SendOtp URL for a multi-region deployment |
appId |
Endpoint application client ID from the deployment summary |
isEvaluationModeEnabled |
true for non-delivering evaluation; false for live routing |
migrationPercentage |
Integer from 0 through 100, selected for the rollout stage |
Include all four provider settings when updating the integration, and preserve the other policy properties. Repeat the procedure independently for SMS and voice if both channels are required.
Evaluation checks authenticated routing and decryption without sending through the provider. Run controlled live tests to verify provider access and recipient delivery.
After evaluation and separately controlled live tests pass, PATCH the full provider settings
with isEvaluationModeEnabled: false and the agreed initial migrationPercentage.
Read back the result before increasing traffic.
| Stage | Action | Evidence before continuing |
|---|---|---|
| Evaluation | Enable evaluation for each intended channel | Matching nonce, correlated evaluation events, and expected routing |
| Controlled live test | Verify provider authorization and a controlled SMS or voice send | Provider acceptance and separately confirmed recipient delivery |
| Initial live percentage | Start at the agreed percentage and pilot scope | Policy readback, healthy latency/errors, provider outcomes, and no rollback trigger |
| Increase | Change one rollout setting at a time and observe it for the agreed window | Representative traffic remains within the thresholds |
| Complete | Move to the approved final percentage | All intended channels pass, with operational owners and rollback ready |
Record the percentage, channel, UTC time, observation window, and owner at each stage. Stop the rollout if a threshold is exceeded. A percentage controls routing; verify the observed traffic rather than assuming an exact count or a stable set of users. See migration and evaluation.
- Restore the reviewed previous policy through the supported configuration procedure. Read it back and validate the restored authentication route. Keep the previous endpoint available until traffic has moved.
- For a package rollback, use the retained source, package, and settings. Coordinate certificate continuity and repeat endpoint validation.
- After traffic has moved and retention requirements are met, retire the dedicated Azure resources and separately review application registrations, federation, provider accounts, and monitoring.
Azure resource deletion does not undo policy or directory changes. Shared Microsoft service principals and grants need an impact review before removal. Respect Key Vault protection and retention, and confirm the remaining Azure and provider charges.
For a Graph-based rollback, build a PATCH from the saved prior provider settings and the correct
channel @odata.type; do not submit the whole GET response as an update. Read back the policy
and verify the restored route. To pause routing to the integration, set migrationPercentage
to 0 in the reviewed provider-settings object and validate the resulting authentication behavior.
Before removing Azure resources, have the Azure owner confirm the exact subscription, resource group, dependencies, and retention requirements. Review app registrations, enterprise applications, federated credentials, app-role grants, provider subscriptions, and alert rules separately.
From the repository root, compile Bicep and run the setup suites:
$templatePath = Join-Path ([IO.Path]::GetTempPath()) "epp-main-$([Guid]::NewGuid().ToString('N')).json"
az bicep build --file .\setup\infra\main.bicep --outfile $templatePath
if ($LASTEXITCODE -ne 0) { throw 'Bicep compilation failed.' }
$tests = @(
'Launcher.Tests.ps1',
'Accounts.Tests.ps1',
'Graph.Tests.ps1',
'ProviderConfiguration.Tests.ps1',
'Selections.Tests.ps1',
'Applications.Tests.ps1',
'Progress.Tests.ps1',
'Packages.Tests.ps1',
'Certificates.Tests.ps1',
'Providers.Tests.ps1'
)
foreach ($test in $tests) {
pwsh -NoProfile -File (Join-Path '.\setup\tests' $test)
if ($LASTEXITCODE -ne 0) { throw "$test failed." }
}
pwsh -NoProfile -File .\setup\tests\ServicePlans.Tests.ps1 -TemplatePath $templatePath
if ($LASTEXITCODE -ne 0) { throw 'ServicePlans.Tests.ps1 failed.' }
Remove-Item -LiteralPath $templatePathCI runs the same eleven suites on Windows and Linux. Network and cloud operations are mocked; deployed endpoint validation is separate.