> This is a page from the ElevenLabs documentation. For a complete page index, fetch https://elevenlabs.io/docs/llms.txt. For the full documentation in a single file, fetch https://elevenlabs.io/docs/llms-full.txt.

# Graph calling-bot

## Overzicht

Met deze aanpak wordt de agent een **belbare Teams-identiteit**. Een gebruiker zoekt deze op naam en belt deze 1-op-1, waarna de agent realtime antwoordt — zonder telefoonnummer, PSTN of Communications Credits. Dit is de enige aanpak waarbij je op naam kunt bellen, en ook de meest complexe om te draaien.

Deze gebruikt een **Microsoft Graph-realtime-mediabot** (het Cloud Communications-belplatform). De media-SDK (`Microsoft.Skype.Bots.Media`) werkt **alleen met .NET op Windows Server** — er is geen Linux- of niet-.NET-optie voor ruwe audio in Teams-gesprekken.

> **Note**
>
> Dit is de enige aanpak die je **op naam** kunt bellen in Teams. Kies de [widget- tab](/docs/nl/eleven-agents/phone-numbers/microsoft-teams/widget-tab) voor een eenvoudigere configuratie, of
> [ACS](/docs/nl/eleven-agents/phone-numbers/microsoft-teams/azure-communication-services) als je
> specifiek een telefoonnummer wilt.

## Hoe het werkt

![Een Teams-gebruiker belt de bot op naam; Teams routeert het gesprek naar de mediabot op een Windows-VM, die ruwe PCM 16k-audio via een WebSocket doorstuurt naar de ElevenLabs-agent](/docs/_fern-files/elevenlabs.docs.buildwithfern.com/f411ec0b44d4c5ca1a61598fbd1e6162e46a1c74179eebb4b673ee1428823e84/assets/images/conversational-ai/teams-media-bot-architecture.svg)

De bot beantwoordt gesprekken met **media die door de applicatie wordt gehost**, ontvangt 50 audioframes/sec (20 ms PCM 16 kHz), stuurt deze via een WebSocket door naar de ElevenLabs-agent en streamt de audio van de agent terug naar het gesprek.

## Vereisten

1. Een **Azure Bot**-registratie + app (Entra-appregistratie).
2. **Graph-applicatiemachtigingen** met toestemming van de beheerder: `Calls.AccessMedia.All` (ruwe media) plus `Calls.Initiate.All`.
3. Een **Windows Server-VM** (≥ **2 fysieke cores** — bijvoorbeeld `Standard_D4s_v3`) met een openbaar IP-adres en geopende mediapoorten.
4. Een **door een CA ondertekend TLS-certificaat** op een openbare FQDN voor het media-/signaling-endpoint (het mediaplatform weigert zelfondertekende certificaten).
5. Een [ElevenLabs-agent](/docs/nl/eleven-agents/quickstart) die op beide kanten is ingesteld op **PCM 16000 Hz**: TTS-uitvoerformaat op het tabblad **Voice**, invoeraudioformaat van de gebruiker op het tabblad **Advanced**.

> **Warning**
>
> Een `D2s_v3` (2 vCPU = **1 fysieke core**) faalt met `MediaPlatform needs a system with at least 2
>   cores`. Gebruik een formaat met ≥ 2 fysieke cores (bijvoorbeeld `D4s_v3`).

## Machtigingen en rollen

| Bereik                       | Rol / machtiging                                                                                                                                 | Waarom                                                                                                                          |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------- |
| Entra                        | **Application Administrator**                                                                                                                    | de appregistratie + Azure Bot maken                                                                                             |
| Entra                        | **Global Administrator / Privileged Role Administrator**                                                                                         | beheerderstoestemming verlenen voor de Graph-belmachtigingen — appmachtigingen kunnen niet zelf van toestemming worden voorzien |
| Microsoft Graph (applicatie) | `Calls.AccessMedia.All`, `Calls.Initiate.All`                                                                                                    | 1-op-1-gesprekken beantwoorden en toegang krijgen tot ruwe media                                                                |
| Azure RBAC                   | **Contributor** voor de resourcegroep                                                                                                            | de Windows-VM + Azure Bot maken                                                                                                 |
| Teams-beheerder              | [**upload van aangepaste apps**](https://admin.teams.microsoft.com/policies/manage-apps) toestaan; het **Calling**-kanaal van de bot inschakelen | de app sideloaden en gesprekken ontvangen                                                                                       |

## Stap 1 — Registreer de bot + Graph-machtigingen

Maak een appregistratie en een daaraan gekoppelde Azure Bot, en verleen vervolgens toestemming voor de belmachtigingen (je hebt Global Admin / Privileged Role Admin nodig om toestemming te geven):

```bash
APPID=$(az ad app create --display-name "ElevenLabs Teams Agent" \
  --sign-in-audience AzureADMyOrg --query appId -o tsv)
az ad sp create --id "$APPID"
# create a client secret and record it
az ad app credential reset --id "$APPID" --display-name bot --query password -o tsv

# Azure Bot bound to the app
az bot create --resource-group $RG --name el-teams-agent-bot \
  --app-type SingleTenant --appid "$APPID" --tenant-id $TENANT \
  --endpoint "https://YOUR_FQDN/api/messages" --sku S1
```

Verleen de twee Graph-applicatierollen en **beheerderstoestemming** (vereist Global Admin / Privileged Role Admin), en controleer vervolgens of de toewijzingen zijn doorgevoerd:

```bash
# Graph app roles: Calls.AccessMedia.All, Calls.Initiate.All
az ad app permission add --id "$APPID" --api 00000003-0000-0000-c000-000000000000 \
  --api-permissions a7a681dc-756e-4909-b988-f160edc6655f=Role \
    284383ee-7f6e-4e40-a2a8-e85dcb029101=Role
az ad app permission admin-consent --id "$APPID"

# Verify — should print both role ids
az rest --method GET \
  --url "https://graph.microsoft.com/v1.0/servicePrincipals(appId='$APPID')/appRoleAssignments" \
  --query "value[].appRoleId" -o tsv
```

> **Note**
>
> Als `admin-consent` `Consent validation failed` retourneert, verleen de approllen dan rechtstreeks aan de service-principal:
>
> ```bash
> GRAPH_SP=$(az ad sp show --id 00000003-0000-0000-c000-000000000000 --query id -o tsv)
> BOT_SP=$(az ad sp show --id "$APPID" --query id -o tsv)
> for ROLE in a7a681dc-756e-4909-b988-f160edc6655f 284383ee-7f6e-4e40-a2a8-e85dcb029101; do
>   az rest --method POST \
>     --url "https://graph.microsoft.com/v1.0/servicePrincipals/$GRAPH_SP/appRoleAssignedTo" \
>     --body "{\"principalId\": \"$BOT_SP\", \"resourceId\": \"$GRAPH_SP\", \"appRoleId\": \"$ROLE\"}"
> done
> ```

Controleer in de portal in het [Entra-beheercentrum](https://entra.microsoft.com/#view/Microsoft_AAD_RegisteredApps/RegisteredAppsListBlade) onder **App registrations → je app → API permissions**: beide machtigingen moeten met groene vinkjes als **Granted** worden weergegeven.

![Het scherm met API-machtigingen van de appregistratie, waarop Calls.AccessMedia.All en Calls.Initiate.All als
verleend worden weergegeven](/docs/_fern-img/4abc1e220d3fc03845594aa4f835656425cc1c39448dae30dace98e672fd8fb1.webp)

## Stap 2 — Richt de Windows-VM, het certificaat en de poorten in

```bash
az vm create -g $RG -n teams-media-bot --image Win2022Datacenter \
  --size Standard_D4s_v3 --admin-username azureuser --admin-password '<strong-pw>' \
  --public-ip-sku Standard --public-ip-address-dns-name elevenmediabot
az vm open-port -g $RG -n teams-media-bot --port 80,443,8445,9441 --priority 300
```

Op de VM (de native code van het mediaplatform heeft deze nodig — Windows Server bevat ze standaard niet):

```powershell
# VC++ runtime + Media Foundation feature (required by NativeMedia.dll)
choco install -y vcredist140
Install-WindowsFeature Server-Media-Foundation

# CA cert for the VM's FQDN via win-acme (HTTP-01), then import to LocalMachine\My
& wacs.exe --target manual --host <vm-fqdn>.cloudapp.azure.com `
  --validation selfhosting --store pfxfile --pfxfilepath C:\bot\certs --accepttos
```

Open dezelfde poorten in de **Windows Firewall** en noteer de **thumbprint** van het certificaat — de bot koppelt Kestrel (443 + een meldingspoort) en het mediaplatform (8445) hieraan.

> **Tip**
>
> De eigen `*.cloudapp.azure.com`-FQDN van de VM werkt voor een Let's Encrypt-certificaat — een apart domein
> is niet nodig.

## Stap 3 — Bouw en voer de bot uit

Begin met Microsoft's [`microsoft-graph-comms-samples`](https://github.com/microsoftgraph/microsoft-graph-comms-samples) **PublicSamples/EchoBot** — deze is gericht op `net6.0` en bouwt met de .NET SDK (geen Visual Studio Build Tools nodig):

```powershell
git clone --depth 1 https://github.com/microsoftgraph/microsoft-graph-comms-samples.git C:\bot\samples
cd C:\bot\samples\Samples\PublicSamples\EchoBot\src
dotnet build EchoBot.sln -c Release
```

Configureer de sectie `AppSettings` van `appsettings.json` met je `AadAppId`, `AadAppSecret`, `ServiceDnsName`/`MediaDnsName` (de FQDN van de VM), `CertificateThumbprint` en poorten (bellen `443`, meldingen `9441`, media `8445`). Voeg voor de ElevenLabs-bridge hieronder twee instellingen toe: `ElevenLabsAgentId` en `ElevenLabsOrigin` (`wss://api.elevenlabs.io`, of je residency-host). Voer deze uit als een Windows-geplande taak / service, zodat deze na herstarts blijft draaien.

> **Warning**
>
> De standaard **uitvoertijdlimiet (72 uur)** van Task Scheduler beëindigt langlopende taken stilzwijgend — een bot die bij het opstarten wordt gestart, stopt drie dagen later en gesprekken mislukken met "we couldn't connect you". Schakel de limiet uit en voeg opnieuw starten bij een fout toe:
>
> ```powershell
> $s = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Seconds 0) `
>   -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) -StartWhenAvailable
> Set-ScheduledTask -TaskName EchoBot -Settings $s
> ```

> **Warning**
>
> De standaard EchoBot crasht bij een gesprek naar de standaardpoort 443: `HttpHelpers.SetAbsoluteUri`
> roept `req.Host.Port.Value` aan, wat null is als de Host-header geen expliciete poort heeft. Pas dit aan naar
> `req.Host.Port ?? (req.IsHttps ? 443 : 80)`.

### Vervang de echo door ElevenLabs

De audionaad van EchoBot is helder: `SpeechService.AppendAudioBuffer(in)` en een event `OnSendMediaBufferEventArgs(out)`. Vervang de Azure-Speech-body door een ElevenLabs-agent-WebSocket-bridge die hetzelfde oppervlak behoudt:

**`SpeechService.cs — ElevenLabs-bridge (kern)`**

```csharp title="SpeechService.cs — ElevenLabs-bridge (kern)"
public class SpeechService
{
    private readonly AppSettings _settings;
    private readonly ILogger _logger;
    private ClientWebSocket _ws;
    private bool _started;
    private bool _connecting;

    public event EventHandler<MediaStreamEventArgs> SendMediaBuffer; // agent audio -> call
    public event EventHandler FlushMedia;                            // barge-in: drop queued audio

    public SpeechService(AppSettings settings, ILogger logger) { _settings = settings; _logger = logger; }

    // Caller audio -> ElevenLabs
    public async Task AppendAudioBuffer(AudioMediaBuffer buffer)
    {
        if (!_started)
        {
            if (_connecting) return;            // a connect attempt is already in flight
            _connecting = true;
            try { await Connect(); _started = true; }
            catch (Exception ex) { _logger.Error(ex, "ElevenLabs connect failed; retry on next frame"); return; }
            finally { _connecting = false; }
        }
        if (_ws?.State != WebSocketState.Open || buffer.Length <= 0) return;
        var pcm = new byte[buffer.Length];
        Marshal.Copy(buffer.Data, pcm, 0, (int)buffer.Length);
        var msg = JsonSerializer.Serialize(new { user_audio_chunk = Convert.ToBase64String(pcm) });
        await _ws.SendAsync(Encoding.UTF8.GetBytes(msg), WebSocketMessageType.Text, true, default);
    }

    private async Task Connect()
    {
        _ws = new ClientWebSocket();
        // ElevenLabsOrigin: wss://api.elevenlabs.io, or a residency host (.eu./.in./.sg.)
        var url = $"{_settings.ElevenLabsOrigin}/v1/convai/conversation?agent_id={_settings.ElevenLabsAgentId}";
        await _ws.ConnectAsync(new Uri(url), default);
        await _ws.SendAsync(Encoding.UTF8.GetBytes(
            JsonSerializer.Serialize(new { type = "conversation_initiation_client_data" })),
            WebSocketMessageType.Text, true, default);
        _ = Task.Run(ReceiveLoop);
    }

    private async Task ReceiveLoop()
    {
        var buf = new byte[32768]; var sb = new StringBuilder();
        while (_ws.State == WebSocketState.Open)
        {
            sb.Clear(); WebSocketReceiveResult r;
            do { r = await _ws.ReceiveAsync(buf, default); sb.Append(Encoding.UTF8.GetString(buf, 0, r.Count)); }
            while (!r.EndOfMessage);

            using var doc = JsonDocument.Parse(sb.ToString());
            var type = doc.RootElement.GetProperty("type").GetString();
            if (type == "audio")            // ElevenLabs audio -> call
                Emit(Convert.FromBase64String(doc.RootElement
                    .GetProperty("audio_event").GetProperty("audio_base_64").GetString()));
            else if (type == "ping")
                await _ws.SendAsync(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(new {
                    type = "pong", event_id = doc.RootElement.GetProperty("ping_event").GetProperty("event_id").GetInt32() })),
                    WebSocketMessageType.Text, true, default);
            else if (type == "interruption")  // barge-in: drop any agent audio still queued
                FlushMedia?.Invoke(this, EventArgs.Empty);
        }
    }

    // slice PCM into 20 ms / 640-byte frames the media platform expects
    private void Emit(byte[] pcm)
    {
        var all = new List<AudioMediaBuffer>(); long tick = DateTime.Now.Ticks;
        for (int off = 0; off < pcm.Length; off += 640)
        {
            var frame = new byte[640];
            Array.Copy(pcm, off, frame, 0, Math.Min(640, pcm.Length - off));
            all.AddRange(Utilities.CreateAudioMediaBuffers(frame, tick, _logger));
            tick += 20 * 10000;
        }
        if (all.Count > 0) SendMediaBuffer?.Invoke(this, new MediaStreamEventArgs { AudioMediaBuffers = all });
    }
}
```

Beide kanten gebruiken mono PCM 16 kHz, dus het is een base64-doorvoer — stel de agent in op `pcm_16000`. Bij een ElevenLabs-`interruption` (barge-in) activeert de bridge `FlushMedia`; koppel dit aan je mediastream zodat alle wachtrijstaande `AudioMediaBuffer`s worden verwijderd, anders blijft de agent over de beller heen praten. De volledige berichtreferentie staat in de [WebSocket-documentatie](/docs/nl/eleven-agents/libraries/web-sockets). Het ophangen aan het einde van een gesprek en warme overdracht worden in de onderstaande secties behandeld.

> **Note**
>
> De URL in `Connect()` bereikt een **openbare** agent. Vraag voor een privéagent server-side een kortdurende
> ondertekende URL aan — `GET /v1/convai/conversation/get-signed-url?agent_id=...` met je API-
> sleutel — en maak in plaats daarvan verbinding met de geretourneerde URL. Stel bij [data- residency](/docs/nl/overview/administration/data-residency) `ElevenLabsOrigin` in op je residency-
> host (`wss://api.eu.residency.elevenlabs.io`, `.in.`, of `.sg.`) — aanvragen voor ondertekende URL's gebruiken de
> bijbehorende `https://`-host.

## Stap 4 — Maak de bot belbaar in Teams

1. **Schakel Calling in** op het Teams-kanaal van de Azure Bot en stel de **calling webhook** in op `https://YOUR_FQDN/api/calling`:

   ```bash
   az bot msteams create -g $RG -n el-teams-agent-bot \
     --enable-calling --calling-web-hook "https://YOUR_FQDN/api/calling"
   ```

   In de portal vind je dit bij je Azure Bot-resource → **Channels** → **Microsoft Teams** → tabblad **Calling**:

   ![Het scherm Azure Bot Channels waarop het Microsoft Teams-kanaal als
   gezond wordt weergegeven](/docs/_fern-img/01115d26ab645dd6ec6569a36a918cd4de0214c79dcb0070d7a3c3a5e544c93b.webp)

   ![Het tabblad Calling van het Teams-kanaal waarop Enable calling is aangevinkt en de calling webhook is
   ingesteld](/docs/_fern-img/7c4819f64878bfb152661adc948c1170f9efd48ba3d68401c4a4d4ba075951b0.webp)

2. Bouw een **Teams-appmanifest** met `bots[0].supportsCalling: true` en de app-ID van de bot, en sideload dit (**Apps → Manage your apps → Upload a custom app**), of publiceer het organisatiebreed zonder de UI: `New-TeamsApp -DistributionMethod organization -Path ./bot-app.zip` (MicrosoftTeams PowerShell-module).

Zoek de app op naam in Teams en bel deze — de bot antwoordt en de ElevenLabs-agent spreekt.

![Een actief Teams-gesprek met de ElevenLabs-agent-
bot](/docs/_fern-img/0f1fad08a091bcd20ffbbf5c3949b5503f423d8eb6298f464a870b3a0a254726.webp)

> **Note**
>
> Voor 1-op-1-bellen op naam heb je geen telefoonnummer of resource account nodig — die zijn alleen voor PSTN-
> inbellen. `Calls.AccessMedia.All` maakt de raw-audiobridge mogelijk.

## Tekstchat (dezelfde bot)

Dezelfde Azure Bot kan ook **tekst** beantwoorden in Teams — gebruikers kunnen de agent dus bellen of ermee chatten. Bellen en berichten versturen zijn onafhankelijke kanalen op de bot: de calling webhook verwerkt spraak en een Bot Framework-**messaging endpoint** (`/api/messages`) verwerkt chat.

![Een Teams-chat waarin de ElevenLabs-agentbot tekstberichten
beantwoordt](/docs/_fern-img/fb75ae17848916790d29c1c0a703051946902893e46ed62884742db36be453b2.webp)

Wijs het messaging endpoint van de bot naar de host die dit aanbiedt (de mediabot of een andere service — dit hoeft niet de Windows-VM te zijn):

```bash
az bot update -g $RG -n el-teams-agent-bot --endpoint "https://YOUR_FQDN/api/messages"
```

Implementeer het endpoint met de Bot Framework SDK en stuur elk bericht in **tekstmodus** door naar de agent via dezelfde conversation WebSocket die voor spraak wordt gebruikt — verstuur een event [`user_message`](/docs/nl/eleven-agents/customization/events/client-to-server-events) en lees het event [`agent_response`](/docs/nl/eleven-agents/customization/events/client-events). Schakel eerst het veld **first message** in onder de instellingen voor [overrides](/docs/nl/eleven-agents/customization/personalization/overrides) van de agent — de onderstaande code overschrijft dit met leeg, zodat het antwoord een reactie is op het bericht van de gebruiker in plaats van de begroeting van de agent:

**`ChatBot.cs — Teams-tekstchat -> ElevenLabs (tekstmodus)`**

```csharp title="ChatBot.cs — Teams-tekstchat -> ElevenLabs (tekstmodus)"
public class ChatBot : ActivityHandler
{
    private readonly AppSettings _settings;
    public ChatBot(AppSettings settings) => _settings = settings;

    protected override async Task OnMessageActivityAsync(
        ITurnContext<IMessageActivity> turn, CancellationToken ct)
    {
        var reply = await AskAgent(turn.Activity.Text, ct);
        await turn.SendActivityAsync(MessageFactory.Text(reply), ct);
    }

    private async Task<string> AskAgent(string text, CancellationToken ct)
    {
        using var ws = new ClientWebSocket();
        var url = $"{_settings.ElevenLabsOrigin}/v1/convai/conversation?agent_id={_settings.ElevenLabsAgentId}";
        await ws.ConnectAsync(new Uri(url), ct);
        // Suppress the agent's greeting: with no override, the first agent_response is the
        // configured first message, not the answer to this user_message.
        await Send(ws, new
        {
            type = "conversation_initiation_client_data",
            conversation_config_override = new { agent = new { first_message = "" } },
        }, ct);
        await Send(ws, new { type = "user_message", text }, ct);

        var buf = new byte[16384]; var sb = new StringBuilder();
        while (ws.State == WebSocketState.Open)
        {
            sb.Clear(); WebSocketReceiveResult r;
            do { r = await ws.ReceiveAsync(buf, ct); sb.Append(Encoding.UTF8.GetString(buf, 0, r.Count)); }
            while (!r.EndOfMessage);

            using var doc = JsonDocument.Parse(sb.ToString());
            switch (doc.RootElement.GetProperty("type").GetString())
            {
                case "agent_response":
                    return doc.RootElement.GetProperty("agent_response_event")
                              .GetProperty("agent_response").GetString();
                case "ping":
                    await Send(ws, new { type = "pong", event_id = doc.RootElement
                        .GetProperty("ping_event").GetProperty("event_id").GetInt32() }, ct);
                    break;
            }
        }
        return "Sorry, I couldn't reach the agent.";
    }

    private static Task Send(ClientWebSocket ws, object msg, CancellationToken ct) =>
        ws.SendAsync(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(msg)),
            WebSocketMessageType.Text, true, ct);
}
```

Registreer deze op de standaardmanier (een `CloudAdapter`, de bot via `AddTransient<IBot, ChatBot>()` en een controller voor `/api/messages`) en voeg chatscopes toe aan de botvermelding in het manifest:

```json
"bots": [
  { "botId": "YOUR_APP_ID", "supportsCalling": true, "scopes": ["personal", "team", "groupChat"] }
]
```

> **Note**
>
> Het codefragment opent een nieuw gesprek per bericht, dus elke beurt staat op zichzelf. Houd voor chat-
> **geheugen** één WebSocket open per Teams-`conversation.id` (hergebruik deze tussen beurten) en ruim
> inactieve sessies op — de agent onthoudt dan eerdere berichten in die chat. De override `first_message`
> moet zijn ingeschakeld onder de instellingen voor
> [overrides](/docs/nl/eleven-agents/customization/personalization/overrides) van de agent — de server
> **sluit het gesprek** als een niet-toegestane override wordt verstuurd. Als je deze niet kunt inschakelen, laat je de
> override weg en negeer je in plaats daarvan de eerste `agent_response` van elke sessie (de begroeting), waarna je
> de volgende terugstuurt.

> **Tip**
>
> Als chatantwoorden nooit aankomen, schakel dan het **`agent_response`**-[client- event](/docs/nl/eleven-agents/customization/events/client-events) in de **Advanced**-
> instellingen van de agent in — tekstreacties worden via dat event geleverd.

## Einde van het gesprek

Wanneer ElevenLabs het gesprek beëindigt (de tool **End Call** sluit de WebSocket), hang je de Teams-kant op:

```csharp
await this.Call.DeleteAsync(); // after a short delay so the goodbye audio finishes
```

## Warme overdracht naar een persoon

De agent activeert een aangepaste clienttool `transfer_to_human`; de bot **nodigt een Teams-gebruiker uit** voor het actieve gesprek (consultatieve toevoeging) en trekt zich vervolgens terug:

```csharp
var target = new IdentitySet { User = new Identity { Id = humanObjectId } };
await this.Call.Participants.InviteAsync(target, replacesCallId: null);
// suppress the end-call hangup while transferring, and mute the bot
```

> **Tip**
>
> Consultatieve overdracht (`replacesCallId`) vereist dat beide partijen Teams-gebruikers in **dezelfde
> tenant** zijn; PSTN-overdrachtsdoelen vereisen een application instance. Geef de persoon eerst een briefing door een
> parameter `reason` van de agent door te geven en deze aan de persoon af te spelen voordat je de verbinding maakt.

## Problemen oplossen

#### \`MediaPlatform needs a system with at least 2 cores\`

De VM heeft slechts één fysieke core. Wijzig de grootte naar ≥ 2 fysieke cores (bijvoorbeeld `D4s_v3`) en start opnieuw op.

#### \`Unable to load DLL 'NativeMedia'\`

Installeer de **VC++ Redistributable** (`vcredist140`) en de Windows-
functie **Server-Media-Foundation**, en start de bot vervolgens opnieuw.

#### Inkomend gesprek retourneert 500 / gesprek maakt geen verbinding

De EchoBot-poort-null-bug op 443 — pas `HttpHelpers.SetAbsoluteUri` aan (zie stap 3). Controleer ook of
het certificaat door een CA is ondertekend en bereikbaar is op 443.

#### Bij het bellen van de bot wordt 'we couldn't connect you' weergegeven

Controleer of Calling is ingeschakeld op het Teams-kanaal met de juiste `/api/calling`-webhook, of de
Graph-machtiging `Calls.AccessMedia.All` toestemming heeft en of poorten 443/8445/9441 zowel in de
NSG als in de Windows Firewall openstaan. Als bellen **eerder werkte en is gestopt**, controleer dan of het botproces
nog steeds op de VM draait — de standaarduitvoertijdlimiet van 72 uur van Task Scheduler beëindigt het enkele
dagen na het opstarten (zie de waarschuwing in stap 3).

## Handige links

* [Overzicht van bots voor gesprekken en vergaderingen](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/calls-and-meetings/calls-meetings-bots-overview)
* [Concepten voor realtime media](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/calls-and-meetings/real-time-media-concepts)
* [Een belbot registreren](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/calls-and-meetings/registering-calling-bot)
* [microsoft-graph-comms-samples (EchoBot)](https://github.com/microsoftgraph/microsoft-graph-comms-samples)
* [call: transfer (consultatief)](https://learn.microsoft.com/en-us/graph/api/call-transfer)
* [Agent-WebSocket-protocol](/docs/nl/eleven-agents/libraries/web-sockets)