I'm working on a Teams calling bot that initiates a Teams meeting, joins the call, and then uses the Microsoft Graph API to dial out to a PSTN participant using:
POST /v1.0/communications/calls/{callId}/participants/invite
I'm using the following payload:
{
"participants": [
{
"@odata.type": "#microsoft.graph.invitationParticipantInfo",
"identity": {
"phone": {
"id": "+447..."
}
}
}
],
"clientContext": "62005836-5dc0-46a3-a966-d97847e1846e"
}
I receive a 200 OK response and the operation resource shows status: Running initially. The participant entry includes the phone number and a participantId is generated.
However, the PSTN phone never rings. The state of the call remains establishing, and polling the operation occasionally completes without an error, but no call ever takes place.
β Whatβs Working App registration has required Graph API permissions:
Calls.Initiate.All, Calls.JoinGroupCall.All, Calls.JoinGroupCallAsGuest.All, OnlineMeetings.ReadWrite.All, etc.
The bot successfully joins the Teams meeting using Graph.
The invite call appears to be accepted with a 201 created response even if PSTN is not dialled, which makes troubleshooting more difficult.
So I'm Looking to see if anyone else has had any success with getting a bot to call a PSTN number into a Microsoft Teams meeting? Gotten it to actually dial the number and ring a real phone?
Any pointers or configuration examples would be hugely appreciated.
β How We Got a Microsoft Teams Bot to Call PSTN Using a Calling Plan (Working Setup)
We successfully configured a Microsoft Teams bot (application instance) to make outbound PSTN calls using a Microsoft Calling Plan, despite Microsoft docs generally stating this isn't supported. Here's exactly how we did it:
This must be a user number.
Provisioning can take up to a week.
β οΈ Important: Don't use the Teams Admin Portal β it forces a Call Queue/AA role. Instead, use PowerShell to keep the account unassigned.
New-CsOnlineApplicationInstance `
-UserPrincipalName <bot-upn@yourtenant.com> `
-ApplicationId <BotAppId> `
-DisplayName "<Bot Display Name>"
3. Assign Required Licenses
Assign the following licenses to the application instance (via Microsoft 365 Admin Center or PowerShell):
β Microsoft Teams Resource Account License (free)
β Microsoft 365 Phone System License
β Microsoft Calling Plan (e.g., Domestic Calling Plan)
π Without the Calling Plan license, the bot will not be allowed to place outbound PSTN calls.
In Teams Admin Center:
Go to Voice > Phone Numbers
Find the number you requested earlier
Click Edit
Change the Assignment Type to Voice App
(this makes it usable by resource accounts)
Still in Teams Admin Center:
Run the following to confirm it's linked properly:
Get-CsPhoneNumberAssignment -TelephoneNumber "+44xxxxxxxxxx"
You should see the resource account assigned.
After assigning the number, the status may show as:
ActivationState : AssignmentPending
This is expected. It typically takes a few minutes to activate, but may take longer to fully propagate. Also worth knowing that our bot still could call even though Activation State said "AssignmentFailed"
Once the number is assigned:
Your bot can successfully initiate outbound PSTN calls via the Microsoft Graph /communications/calls
API.
You do not need Direct Routing or Operator Connect in this setup.
Example payload sent to the /communicatons/calls
API will dial in an external PSTN and a Teams user into the same scheduled meeting.
{
"@odata.type": "#microsoft.graph.call",
"callbackUri": "https://<your-callback-uri>.mock.pstmn.io/callback",
"source": {
"@odata.type": "#microsoft.graph.participantInfo",
"identity": {
"@odata.type": "#microsoft.graph.identitySet",
"applicationInstance": {
"@odata.type": "#microsoft.graph.identity",
"displayName": "dial-in-bot",
"id": "<bot-object-id>"
}
},
"countryCode": null,
"endpointType": null,
"region": null,
"languageId": null
},
"targets": [
{
"@odata.type": "#microsoft.graph.invitationParticipantInfo",
"identity": {
"@odata.type": "#microsoft.graph.identitySet",
"phone": {
"@odata.type": "#microsoft.graph.identity",
"id": "+44xxxxxxxxx"
}
}
},
{
"@odata.type": "#microsoft.graph.invitationParticipantInfo",
"identity": {
"@odata.type": "#microsoft.graph.identitySet",
"user": {
"@odata.type": "#microsoft.graph.identity",
"id": "<organizer-object-id>",
"displayName": "Joshua D"
}
}
}
],
"requestedModalities": [
"audio"
],
"mediaConfig": {
"@odata.type": "#microsoft.graph.serviceHostedMediaConfig"
},
"tenantId": "<tenant-id>"
}
πΉ Note on <bot-object-id>
:
This is the Object ID of the Azure AD resource account (application instance) that was created using New-CsOnlineApplicationInstance
and assigned the PSTN phone number.
It is not the Application (Client) ID of the bot's app registration in Azure β this often causes confusion. The source identity for the PSTN call must be the Teams-enabled resource account that holds the phone number, not the botβs app registration.
Contrary to popular belief, this setup does work with Calling Plans as long as:
The number is properly converted to a voice app number
The licenses are correctly assigned
The source identity in your call payload uses the resource account