← All insights

Building an AI-Powered Outbound Calling System with Microsoft Teams

This guide documents a practical pattern for connecting an existing business application to Microsoft Teams outbound PSTN calling. A salesperson selects a customer and starts a call from the application. After the customer answers, the system can play a recorded or AI-generated voice message, or connect an application-hosted real-time media pipeline for a two-way AI conversation.

The implementation has two distinct workstreams: Microsoft infrastructure configuration and application integration. Keeping them separate is important: a successful Graph call-creation response proves that call setup has started, but it does not mean that audio will automatically play.

The examples use dummy tenant IDs, application IDs, domains and telephone numbers. Replace them with values from your own Microsoft tenant. The portal walkthrough illustrations are based on the supplied setup guide; portal labels can vary by tenant and Microsoft UI version.

1. Solution architecture

Logical architecture for the AI-powered outbound calling solution

ComponentResponsibility
Business application / CRMCustomer selection, salesperson action and call history
Backend calling serviceValidates requests, obtains tokens and orchestrates calls
Microsoft Entra IDApp-only identity and OAuth token issuance
Azure BotMicrosoft Teams calling integration
Microsoft Graph Communications APICall creation and call control
Teams resource accountApplication calling identity and assigned outbound number
PSTN connectivityRoutes the call to the customer's telephone
Calling webhookReceives call-state notifications
Media pipelinePlays prompts or processes real-time audio
AI / speech servicesOptional speech recognition, AI reasoning and text-to-speech

1.1 End-to-end workflow

Outbound call lifecycle from salesperson action to call termination

  1. The salesperson selects a customer and calling mode.
  2. The backend validates the destination number and business rules.
  3. The backend obtains or reuses an app-only Microsoft Graph access token.
  4. The backend creates the call through Microsoft Graph.
  5. Teams attempts to establish the PSTN call using the configured resource account.
  6. Graph sends call-state notifications to the public webhook.
  7. When the call reaches established, the application starts the selected media workflow.
  8. The application ends the call when the interaction is complete and stores the outcome.

2. Prerequisites and responsibilities

RequirementPurpose
Microsoft Entra administrative accessApp registration, permissions and consent
Azure subscription accessAzure Bot resource creation
Teams administrative accessResource account and phone-number configuration
Teams Phone Resource Account licenceEnables the resource account for voice applications
PSTN connectivityCalling Plan, Operator Connect or Direct Routing
Public HTTPS endpointReceives calling webhook notifications
TLS certificateSecures the webhook endpoint
Microsoft Graph PowerShellApp registration and licence operations
MicrosoftTeams PowerShellTeams resource-account and number operations
Azure CLIOptional Azure Bot provisioning

Install the PowerShell modules:

Install-Module MicrosoftTeams -Scope CurrentUser -Force
Install-Module Microsoft.Graph -Scope CurrentUser -Force

Use the least-privileged role combination approved by your organisation. Allow time for licensing, number assignment and resource-account binding to propagate; some changes may take up to 24 hours.

3. Infrastructure setup — Microsoft Entra ID

Step 1: Register the application

Open Microsoft Entra admin center.

Navigate to Entra ID → App registrations → New registration.

Configure:

FieldValue
NameContoso Call Bot (example)
Supported account typesAccounts in this organizational directory only (Single tenant)
Redirect URILeave empty for this client-credentials flow

Select Register. From the Overview page, copy the Application (client) ID and Directory (tenant) ID.

Illustrative Entra app registration screen showing the name, single-tenant option and Register action

Step 2: Create a client secret

Navigate to App registrations → your application → Certificates & secrets → Client secrets → New client secret.

Enter a description, select an expiry period that follows your credential policy, and select Add. Copy the secret Value immediately and store it securely.

Illustrative Entra client secret screen highlighting the secret Value

Important: The application uses the secret Value, not the Secret ID. The Value is shown only once. Never place it in frontend code, source control or application logs. For production, use a managed secrets store and rotate credentials before expiry.

Step 3: Add Microsoft Graph application permissions

Navigate to API permissions → Add a permission → Microsoft Graph → Application permissions.

Add permissions according to the functionality being implemented:

PermissionWhen it is needed
Calls.Initiate.AllInitiating outbound 1:1 calls, including PSTN
Calls.AccessMedia.AllApplication-hosted real-time media
Calls.InitiateGroupCall.AllOnly when the application initiates group calls
Calls.JoinGroupCall.AllOnly when the application joins meetings or group calls
CallRecords.Read.AllOptional call-history and quality-record access

Select Grant admin consent and confirm.

Illustrative Microsoft Graph Application permissions screen

Use Application permissions for the app-only client-credentials architecture described here. Calls.Initiate.All is the key call-initiation permission. Add Calls.AccessMedia.All when implementing application-hosted media. Do not add optional permissions unless the corresponding capability is required.

4. Infrastructure setup — Azure Bot and Teams

Step 4: Create the Azure Bot

Open Azure portal, search for Azure Bot, and select Create.

Configure the bot:

FieldExample
Bot handlecontoso-callbot
SubscriptionYour Azure subscription
Resource grouprg-callbot
Type of AppSingle Tenant
Creation typeUse existing app registration
App IDEntra application client ID
App tenant IDEntra tenant ID

Select Review + create, then Create.

Illustrative Azure Bot creation screen showing Single Tenant and existing app registration

After deployment, open Configuration and verify that the Microsoft App ID and App Tenant ID match the Entra registration. For a calling-only bot, the messaging endpoint can remain empty if messaging is not part of the implementation.

Step 5: Enable the Microsoft Teams channel and calling

In the Azure Bot resource, navigate to Settings → Channels. Select Microsoft Teams, accept the applicable terms, choose the Microsoft Teams commercial channel and apply.

Open the Calling tab:

  1. Enable Enable calling.
  2. Enter the public HTTPS calling webhook URL.
  3. Select Apply.
  4. Return to Channels and verify that Microsoft Teams reports Healthy.

Example webhook:

https://app.contoso.com/api/calling

Illustrative Azure Bot Microsoft Teams Calling tab with calling enabled and webhook URL

Critical URL check: The webhook URL configured on the Azure Bot Calling tab and the callbackUri supplied in each Graph call request must be the same address. It must be publicly reachable over HTTPS with a valid certificate.

5. Infrastructure setup — Teams resource account and PSTN number

Step 6: Create and bind the resource account

The resource account is the calling identity. It is bound to the bot application ID, holds the required licence and owns the outbound number. It is not the salesperson's ordinary Teams user identity.

The setup guide creates this application instance with Teams PowerShell:

New-CsOnlineApplicationInstance -UserPrincipalName $RaUpn -DisplayName $RaName -ApplicationId $ClientId

$ra = Get-CsOnlineApplicationInstance -Identity $RaUpn
$RaObjectId = $ra.ObjectId

Sync-CsOnlineApplicationInstance -ObjectId $RaObjectId -ApplicationId $ClientId

The object ID returned by Get-CsOnlineApplicationInstance is the value later supplied as source.identity.applicationInstance.id in the Graph call request.

The account appears in Teams admin center under Voice → Resource accounts with resource-account type Other.

Step 7: Set usage location and assign licences

Open Microsoft 365 admin center.

Navigate to Users → Active users → select the resource account.

  1. Open the Account/contact information settings.
  2. Set Usage location to the country associated with the telephone number.
  3. Open Licences and apps.
  4. Assign Microsoft Teams Phone Resource Account.
  5. Assign the applicable Calling Plan when using Microsoft Calling Plans.
  6. Assign Communication Credits if using Pay-As-You-Go.
  7. Save the changes.

Illustrative Microsoft 365 resource account settings showing usage location and licence assignment

The setup guide identifies PHONESYSTEM_VIRTUALUSER as the Teams Phone Resource Account SKU. The Calling Plan SKU depends on the tenant subscription and calling model. Communication Credits (MCOPSTNC) are relevant to applicable Pay-As-You-Go configurations. Do not copy an example SKU without checking the tenant's available subscriptions.

Step 8: Obtain a telephone number

Open Teams admin center.

Navigate to Voice → Phone numbers → Add.

  1. Enter an order name and select the country.
  2. Select the number type intended for resource accounts / voice apps. Depending on the portal version, this may be labelled Resource account (toll) or Voice app (toll).
  3. Do not select a normal User (subscriber) number for this workflow.
  4. Select the city/area code and quantity.
  5. Review and place the order.
  6. Wait until the number appears as Unassigned.

Step 9: Assign the telephone number

Navigate to Voice → Resource accounts → select the resource account → Assign/unassign phone number.

Select the correct phone-number type: Calling Plan, Operator Connect or Direct Routing.

Choose the acquired number and save. Verify that the resource account shows the assigned number and Licensed status.

Illustrative Teams resource account phone-number assignment screen

For Direct Routing, also grant an Online Voice Routing Policy that can route calls to the PSTN. A number appearing in the portal does not by itself prove that outbound routing is working.

6. Infrastructure automation — PowerShell and Azure CLI

The portal walkthrough explains where to configure each item. The following sequence captures the equivalent operations. Run only the sections required for your tenant; do not create a duplicate application or resource account if one already exists.

6.1 Define variables

$TenantId     = "<TENANT_ID>"
$AppName      = "Contoso Call Bot"
$RaUpn        = "[email protected]"
$RaName       = "Contoso Call Bot"
$UsageCountry = "US"
$PhoneNumber  = "+15550100100"
$WebhookUrl   = "https://app.contoso.com/api/calling"

6.2 Register the application and grant Graph permissions

Connect-MgGraph -TenantId $TenantId -Scopes "Application.ReadWrite.All","AppRoleAssignment.ReadWrite.All","User.ReadWrite.All","Organization.Read.All"

$app = New-MgApplication -DisplayName $AppName -SignInAudience "AzureADMyOrg"
$sp = New-MgServicePrincipal -AppId $app.AppId
$ClientId = $app.AppId

$secret = Add-MgApplicationPassword -ApplicationId $app.Id -PasswordCredential @{ displayName = "calling"; endDateTime = (Get-Date).AddMonths(24) }
$secret.SecretText

$graph = Get-MgServicePrincipal -Filter "appId eq '00000003-0000-0000-c000-000000000000'"
$wanted = "Calls.Initiate.All","Calls.AccessMedia.All"
$roles = $graph.AppRoles | Where-Object { $wanted -contains $_.Value }

Update-MgApplication -ApplicationId $app.Id -RequiredResourceAccess @(@{
  resourceAppId = $graph.AppId
  resourceAccess = @($roles | ForEach-Object { @{ id = $_.Id; type = "Role" } })
})

foreach ($r in $roles) {
  New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $sp.Id -PrincipalId $sp.Id -ResourceId $graph.Id -AppRoleId $r.Id | Out-Null
}

If the app already exists, skip creation and retrieve its existing service principal. Add only the permissions needed for the selected media mode.

6.3 Create the Azure Bot using Azure CLI

The portal is an alternative to this CLI sequence:

az login --tenant <TENANT_ID>
az group create --name rg-callbot --location eastus
az bot create --resource-group rg-callbot --name contoso-callbot --app-type SingleTenant --appid <CLIENT_ID> --tenant-id <TENANT_ID> --sku F0
az bot msteams create --resource-group rg-callbot --name contoso-callbot --enable-calling true --calling-web-hook https://app.contoso.com/api/calling
az bot msteams show --resource-group rg-callbot --name contoso-callbot

CLI parameters and available SKUs can change with Azure CLI versions and service updates. Check the current CLI help if a parameter is rejected.

6.4 Create and bind the resource account

Connect-MicrosoftTeams -TenantId $TenantId
New-CsOnlineApplicationInstance -UserPrincipalName $RaUpn -DisplayName $RaName -ApplicationId $ClientId
$ra = Get-CsOnlineApplicationInstance -Identity $RaUpn
$RaObjectId = $ra.ObjectId
Sync-CsOnlineApplicationInstance -ObjectId $RaObjectId -ApplicationId $ClientId

6.5 Assign usage location and licences

Get-MgSubscribedSku | Select-Object SkuPartNumber, SkuId, @{n="Free";e={$_.PrepaidUnits.Enabled - $_.ConsumedUnits}}
Update-MgUser -UserId $RaUpn -UsageLocation $UsageCountry

$skuParts = @("PHONESYSTEM_VIRTUALUSER","<CALLING_PLAN_SKU_PART_NUMBER>","MCOPSTNC")
$skus = Get-MgSubscribedSku | Where-Object { $skuParts -contains $_.SkuPartNumber }

Set-MgUserLicense -UserId $RaUpn -AddLicenses @($skus | ForEach-Object { @{ SkuId = $_.SkuId } }) -RemoveLicenses @()

Replace the Calling Plan placeholder with the SKU returned by the first command. Do not assign a SKU the tenant does not own or does not require.

6.6 Assign the number and voice-routing policy

Set-CsPhoneNumberAssignment -Identity $RaUpn -PhoneNumber $PhoneNumber -PhoneNumberType CallingPlan

# Direct Routing only:
Grant-CsOnlineVoiceRoutingPolicy -Identity $RaUpn -PolicyName "<YOUR_VOICE_ROUTING_POLICY>"

For Operator Connect or Direct Routing, use the corresponding phone-number type supported by the tenant.

6.7 Verify infrastructure readiness

Get-CsOnlineApplicationInstance -Identity $RaUpn | Format-List ObjectId, ApplicationId, PhoneNumber, TenantId
Get-CsOnlineUser $RaUpn | Format-List DisplayName, AccountType, EnterpriseVoiceEnabled, LineUri, FeatureTypes, AssignedPlan
Get-CsPhoneNumberAssignment -AssignedPstnTargetId $RaUpn | Format-List TelephoneNumber, NumberType, ActivationState, Capability, PstnAssignmentStatus

Expected checks:

FieldExpected value
ApplicationIdMatches the bot app client ID
AccountTypeResourceAccount
EnterpriseVoiceEnabledTrue
LineUritel: followed by the assigned number
FeatureTypesIncludes VoiceApp and applicable calling features
AssignedPlanIncludes MCOEV_VIRTUALUSER
ActivationStateActivated
CapabilityIncludes OutboundCalling
PstnAssignmentStatusVoiceApplicationAssigned

Stop and correct the infrastructure if these values are not as expected.

7. Prove the infrastructure with a direct test call

Before integrating the business application, place a test call directly from PowerShell. This isolates Microsoft tenant, resource-account, licence, number and Graph permission issues from application code.

7.1 Obtain an app-only token

$Secure = Read-Host "Client secret" -AsSecureString
$ClientSecret = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($Secure))

$Token = (Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token" -ContentType "application/x-www-form-urlencoded" -Body @{
  client_id = $ClientId
  client_secret = $ClientSecret
  scope = "https://graph.microsoft.com/.default"
  grant_type = "client_credentials"
}).access_token

Inspect the JWT claims locally. The token should contain the expected app ID, tenant ID and Calls.Initiate.All role. Do not paste access tokens into tickets, chat or logs.

7.2 Create the test call

Send a POST request to:

https://graph.microsoft.com/v1.0/communications/calls

Example request:

{
  "@odata.type": "#microsoft.graph.call",
  "callbackUri": "https://app.contoso.com/api/calling",
  "tenantId": "{TENANT_ID}",
  "source": {
    "@odata.type": "#microsoft.graph.participantInfo",
    "identity": {
      "@odata.type": "#microsoft.graph.identitySet",
      "applicationInstance": {
        "@odata.type": "#microsoft.graph.identity",
        "id": "{RESOURCE_ACCOUNT_OBJECT_ID}",
        "displayName": "Contoso Call Bot"
      }
    }
  },
  "targets": [{
    "@odata.type": "#microsoft.graph.invitationParticipantInfo",
    "identity": {
      "@odata.type": "#microsoft.graph.identitySet",
      "phone": {
        "@odata.type": "#microsoft.graph.identity",
        "id": "+15550100199"
      }
    }
  }],
  "requestedModalities": ["audio"],
  "mediaConfig": {
    "@odata.type": "#microsoft.graph.serviceHostedMediaConfig"
  }
}

The values are illustrative. Use an E.164 destination number.

A successful HTTP 201 response and a ringing target phone demonstrate that infrastructure call initiation is working. Silence after the customer answers is normal at this stage: no prompt has been played yet.

7.3 Interpret the result

ResultMeaning / next action
HTTP 201 and phone ringsInfrastructure call initiation is working
HTTP 403, code 7507Check source identity, resource-account binding, licence, number and propagation
HTTP 403 without 7507Check Graph application permission and admin consent
HTTP 400Check request body and E.164 destination number
Token request failsCheck tenant, client ID, secret Value and expiry

Store the returned call ID. It is required for subsequent call operations and termination.

8. Application-side integration

8.1 Configuration values required by the backend

Application settingWhere to find it
Tenant IDEntra app registration Overview
Client ID / Bot App IDEntra app registration Overview
Client secretEntra Certificates & secrets — secret Value
Resource account object IDGet-CsOnlineApplicationInstance → ObjectId
Callback URLSame public URL configured on Azure Bot
Outbound caller IDNumber assigned to the resource account
Customer numberCustomer record, normalized to E.164
Token grantclient_credentials
Token scopehttps://graph.microsoft.com/.default
Media modeService-hosted or application-hosted

Three mistakes cause a large proportion of failed calls:

  1. Supplying a user's ID or delegated token instead of the resource account object ID and app-only token.
  2. Using the Secret ID instead of the secret Value.
  3. Supplying a callback URL that differs from the Azure Bot calling webhook.

8.2 Acquire and cache the token

The backend sends a client-credentials request to:

POST https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token

Parameters:

grant_type=client_credentials
client_id={CLIENT_ID}
client_secret={CLIENT_SECRET}
scope=https://graph.microsoft.com/.default

Cache the token until shortly before its expires_in time. Do not request a new token for every call, and never log the token or secret.

8.3 Create the call

Send a POST request to:

https://graph.microsoft.com/v1.0/communications/calls

The request includes:

  • callbackUri: public calling webhook.
  • tenantId: Entra tenant ID.
  • source.identity.applicationInstance.id: resource account object ID.
  • targets: customer telephone number in E.164 format.
  • requestedModalities: audio.
  • mediaConfig: configuration matching the selected media architecture.

The source must be the application instance. The setup guide does not use a user identity for the source, including for a proposed “call as me” feature.

Graph returns HTTP 201 with the call resource and call ID. Store the call ID against the application's call record.

8.4 Process the calling webhook

The webhook is the bridge between Microsoft call control and the application's call lifecycle.

Its responsibilities are:

  1. Accept POST notifications at the configured HTTPS URL.
  2. Validate the bearer token on every request.
  3. Verify the Bot Framework token issuer (https://api.botframework.com) and audience (the bot client ID).
  4. Read value[].resourceData.state from the communication notification.
  5. Track the call by ID through establishing, established and terminated.
  6. Read resultInfo for termination information.
  7. Start the selected media workflow only after the call is established.
  8. Return HTTP 202 Accepted within a few seconds and process slower work asynchronously.

Validate token signature, issuer, audience and expiry using a trusted token-validation library. Do not merely decode the JWT and trust its contents. Make webhook processing idempotent because notifications can be retried.

9. Media mode A — service-hosted recorded or AI-generated voice

Service-hosted media is appropriate when the customer needs to hear a prepared voice message: a sales introduction, appointment reminder, payment reminder, product announcement or customer notification.

The audio may be recorded by the salesperson or generated from a script by a text-to-speech provider. The resulting audio is prepared as a file before playback.

9.1 Prepare the audio file

The setup guide specifies:

  • WAV container.
  • PCM encoding.
  • 16-bit.
  • Mono.
  • 16 kHz sample rate.
  • Public HTTPS URL that Microsoft can fetch.

9.2 Wait for the established event

Do not play the audio immediately after receiving HTTP 201 from call creation. Wait for the webhook's established state.

9.3 Invoke playPrompt

POST https://graph.microsoft.com/v1.0/communications/calls/{call-id}/playPrompt

Example:

{
  "clientContext": "script-step-1",
  "prompts": [{
    "@odata.type": "#microsoft.graph.mediaPrompt",
    "mediaInfo": {
      "@odata.type": "#microsoft.graph.mediaInfo",
      "uri": "https://app.contoso.com/audio/greeting.wav",
      "resourceId": "3e1f0c2a-0000-4000-8000-000000000001"
    }
  }]
}

A playPromptOperation event with status completed arrives on the webhook. The application can then play the next step or listen for a response.

9.4 Capture a response (optional)

The setup guide identifies two operations:

  • recordResponse: records one customer reply to a file, which the application can transcribe separately.
  • subscribeToTone: listens for keypad digits.

Recording and transcription are separate operations. A speech-to-text provider and processing pipeline must be added if the application needs a text transcript.

10. Media mode B — application-hosted real-time AI conversation

Application-hosted media is intended for a live, two-way conversation rather than a fixed audio prompt.

The supplied setup guide identifies these additional requirements:

  • Calls.AccessMedia.All application permission.
  • Microsoft Real-time Media Platform.
  • A .NET bot hosted on Windows Server, such as an Azure VM or similar supported host.
  • Public IP connectivity.
  • Required media port range open through network controls.
  • TLS certificate for the public hostname.

The call request must use appHostedMediaConfig for this media architecture and the application must implement and host the corresponding real-time media bot. Do not simply change the media type in the JSON without deploying the media platform component.

10.1 Real-time conversation flow

Application-hosted real-time media and AI conversation pipeline

A typical application-level pipeline is:

  1. Customer audio reaches the real-time media component.
  2. Speech recognition converts incoming audio into text or another application-understood representation.
  3. AI conversation logic evaluates the response and context.
  4. Text-to-speech generates the outgoing response.
  5. The application streams the response audio back to the customer.
  6. The conversation continues until the objective is achieved or the call is terminated.

The speech-recognition engine, AI model, text-to-speech provider, conversation policy and interruption handling are application integrations. They are not automatically supplied by Microsoft Graph.

10.2 Production considerations

Plan for streaming latency, buffering, speech endpoint detection, customer interruptions, AI timeouts, fallback prompts, audio quality, network jitter, conversation context and media-session cleanup.

11. End the call and release resources

When the script or conversation is complete, terminate the call using its Graph call ID:

DELETE https://graph.microsoft.com/v1.0/communications/calls/{call-id}

The setup guide notes that otherwise the call can remain open until the other side hangs up. Persist the final outcome and release application-hosted media resources.

12. Security, consent and customer privacy

Before production use:

  • Protect client credentials and rotate them before expiry.
  • Grant only the Graph permissions required by the chosen media mode.
  • Validate every webhook notification.
  • Restrict who can initiate calls and which customers can be contacted.
  • Apply customer contact preferences and suppression rules.
  • Announce AI-generated voice and any recording at the start of the call, as applicable.
  • Check local rules for automated calls and recording consent.
  • Define audio/transcript retention and deletion policies.
  • Avoid logging secrets, tokens or unnecessary customer audio.

This is an engineering checklist, not legal advice. Obtain the required compliance review for each market in which calls will be placed.

13. Troubleshooting

SymptomLikely causeRecommended check
403, code 7507 — invalid source identityWrong source ID, user identity/token, missing resource-account binding, licence or number, or propagation delayUse the resource account ObjectId with an app-only token; check ApplicationId, run Sync-CsOnlineApplicationInstance, verify licence and number; allow propagation
403 Forbidden without 7507Calls.Initiate.All missing or admin consent not grantedCheck Application permission, consent and token roles
Bot/app not found or calling disabledAzure Bot missing, App ID mismatch or Teams calling disabledVerify App ID, Teams channel and Calling webhook
201 but phone never ringsNumber not activated, no outbound capability, credits exhausted or wrong destination formatCheck Activated, OutboundCalling, Communication Credits and E.164
Phone rings, then silenceWebhook does not handle established event or application does not invoke playPromptInspect webhook logs for the call ID and media operation
playPrompt fails or no audioAudio URL inaccessible or file format incorrectVerify public HTTPS and WAV PCM 16-bit mono 16 kHz
Call drops after a few secondsWebhook is slow to return 202 or token validation/processing failsAcknowledge promptly and process asynchronously
Token request fails (invalid_client)Secret ID used instead of Value, wrong client/tenant or expired secretCreate a new secret and copy its Value
Wrong caller IDResource account number/configuration is incorrectVerify assigned resource account number and PSTN configuration

Isolate infrastructure from application failures

Use the direct PowerShell test call in Section 7.

  • If the test call fails, troubleshoot Entra permissions, Azure Bot, resource-account binding, licences, number assignment and PSTN routing.
  • If the test call succeeds but the business application fails, focus on token acquisition, Graph request construction, callback URL, webhook validation and media handling.

For a Microsoft support case, retain the tenant ID, app ID, resource-account UPN, Graph request ID, timestamp and relevant verification output. Never share client secrets or access tokens.

14. Implementation assumptions and boundaries

TopicClassification
Entra app registration and app-only tokenCovered by the supplied setup guide
Azure Bot and Teams Calling webhookCovered by the supplied setup guide
Teams resource account, licence and PSTN numberCovered by the supplied setup guide
Graph call creation and webhook statesCovered by the supplied setup guide
Service-hosted playPromptCovered by the supplied setup guide
recordResponse and subscribeToToneIdentified by the supplied setup guide
Application-hosted real-time media requirementsCovered at infrastructure level by the supplied setup guide
Speech recognition, LLM orchestration and TTS providerAssumed application integrations; provider not specified
Database schema, queue, retries and observabilityRecommended production design; implementation-specific
Kubernetes or microservices deploymentOptional deployment choice, not a prerequisite

Conclusion

Building an AI-powered outbound calling capability requires more than generating speech and connecting a telephone number. Identity, telephony, call control, webhook processing and media delivery must work together.

Service-hosted media provides a practical approach for recorded or AI-generated messages. Application-hosted media enables real-time AI conversations but requires a dedicated real-time media host and processing pipeline.

The most useful implementation discipline is to validate the Microsoft infrastructure independently before debugging application code. Once the resource account, licence, PSTN number, bot binding and Graph permissions are correct, the application team can focus on the call lifecycle and selected media workflow.

The key takeaway: Treat call control and media processing as separate architectural responsibilities. This makes the solution easier to troubleshoot, extend and operate.

Microsoft references

Portal labels, licensing availability and API behavior can change. Verify current Microsoft documentation and tenant-specific availability before repeating the setup in another environment.