azuremicrosoft-graph-apimicrosoft-teamsazure-bot-service

Microsoft Graph: Invite PSTN Participant to Teams Call Returns 200 OK but No Call Received


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.


Solution

  • βœ… 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:

    πŸͺœ Step-by-Step Guide

    1. Request a Phone Number from Microsoft

    2. Create an Application Instance (Resource Account)

    ⚠️ 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):

    enter image description here

    πŸ“Œ Without the Calling Plan license, the bot will not be allowed to place outbound PSTN calls.

    4. Convert the Number to a Voice App Number (important and easily missed!)

    In Teams Admin Center:

    5. Assign the Number to the Bot Resource Account

    Still in Teams Admin Center:

    enter image description here

    6. Verify the Assignment via PowerShell

    Run the following to confirm it's linked properly:

    Get-CsPhoneNumberAssignment -TelephoneNumber "+44xxxxxxxxxx"
    

    You should see the resource account assigned.

    7. Wait for Activation

    enter image description here

    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.