> 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.

# Bot di chiamata Graph

## Panoramica

Questo approccio rende l'agente un'**identità Teams chiamabile**. Un utente lo cerca per nome e lo chiama in 1:1, e l'agente risponde in tempo reale — senza numero di telefono, PSTN o Communications Credits. È l'unico approccio chiamabile per nome e il più complesso da gestire.

Usa un **bot Microsoft Graph per contenuti multimediali in tempo reale** (la piattaforma di chiamata Cloud Communications). L'SDK multimediale (`Microsoft.Skype.Bots.Media`) funziona **solo con .NET su Windows Server** — non esiste un percorso Linux o non .NET per l'audio raw nelle chiamate Teams.

> **Note**
>
> Questo è l'unico approccio chiamabile **per nome** all'interno di Teams. Per una configurazione più semplice, usa la [scheda widget](/docs/eleven-agents/phone-numbers/microsoft-teams/widget-tab), oppure
> [ACS](/docs/eleven-agents/phone-numbers/microsoft-teams/azure-communication-services) se vuoi
> specificamente un numero di telefono.

## Come funziona

![Un utente Teams chiama il bot per nome; Teams instrada la chiamata al bot multimediale su una VM Windows, che inoltra l'audio PCM raw a 16 kHz all'agente ElevenLabs tramite WebSocket](/docs/_fern-files/elevenlabs.docs.buildwithfern.com/f411ec0b44d4c5ca1a61598fbd1e6162e46a1c74179eebb4b673ee1428823e84/assets/images/conversational-ai/teams-media-bot-architecture.svg)

Il bot risponde con **contenuti multimediali ospitati dall'applicazione**, riceve 50 frame audio/sec (PCM 16 kHz da 20 ms), li inoltra all'agente ElevenLabs tramite WebSocket e trasmette l'audio dell'agente nella chiamata.

## Requisiti

1. Una registrazione **Azure Bot** + app (registrazione dell'app Entra).
2. **Autorizzazioni dell'applicazione Graph** con consenso dell'amministratore: `Calls.AccessMedia.All` (contenuti multimediali raw) più `Calls.Initiate.All`.
3. Una **VM Windows Server** (≥ **2 core fisici** — ad esempio `Standard_D4s_v3`) con IP pubblico e porte multimediali aperte.
4. Un **certificato TLS firmato da una CA** su un FQDN pubblico per l'endpoint di contenuti multimediali/segnalazione (la piattaforma multimediale rifiuta i certificati autofirmati).
5. Un [agente ElevenLabs](/docs/eleven-agents/quickstart) impostato su **PCM 16000 Hz** su entrambi i lati: formato di output TTS nella scheda **Voce**, formato audio di input utente nella scheda **Avanzate**.

> **Warning**
>
> Un `D2s_v3` (2 vCPU = **1 core fisico**) non funziona e restituisce `MediaPlatform needs a system with at least 2
>   cores`. Usa una dimensione con ≥ 2 core fisici (ad esempio `D4s_v3`).

## Autorizzazioni e ruoli

| Ambito                         | Ruolo / autorizzazione                                                                                                                                  | Motivo                                                                                                                                             |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| Entra                          | **Amministratore applicazioni**                                                                                                                         | creare la registrazione dell'app + Azure Bot                                                                                                       |
| Entra                          | **Amministratore globale / Amministratore ruoli con privilegi**                                                                                         | concedere il consenso dell'amministratore per le autorizzazioni di chiamata Graph — le autorizzazioni dell'app non possono ricevere l'autoconsenso |
| Microsoft Graph (applicazione) | `Calls.AccessMedia.All`, `Calls.Initiate.All`                                                                                                           | rispondere a chiamate 1:1 e accedere a contenuti multimediali raw                                                                                  |
| Azure RBAC                     | **Collaboratore** nel gruppo di risorse                                                                                                                 | creare la VM Windows + Azure Bot                                                                                                                   |
| Amministratore Teams           | consentire il [**caricamento di app personalizzate**](https://admin.teams.microsoft.com/policies/manage-apps); abilitare il canale **Chiamate** del bot | caricare localmente l'app e ricevere chiamate                                                                                                      |

## Passaggio 1 — Registra il bot + autorizzazioni Graph

Crea una registrazione dell'app e un Azure Bot associato, quindi concedi e approva le autorizzazioni di chiamata (per il consenso ti servono i ruoli Amministratore globale / Amministratore ruoli con privilegi):

```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
```

Concedi i due ruoli dell'applicazione Graph e il **consenso dell'amministratore** (richiede Amministratore globale / Amministratore ruoli con privilegi), quindi verifica che le assegnazioni siano state applicate:

```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**
>
> Se `admin-consent` restituisce `Consent validation failed`, concedi invece i ruoli dell'app direttamente nel 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
> ```

Nel portale, verifica nell'[interfaccia di amministrazione Entra](https://entra.microsoft.com/#view/Microsoft_AAD_RegisteredApps/RegisteredAppsListBlade) in **Registrazioni app → la tua app → Autorizzazioni API**: entrambe le autorizzazioni dovrebbero mostrare **Concesso** con segni di spunta verdi.

![Il pannello delle autorizzazioni API della registrazione dell'app che mostra Calls.AccessMedia.All e Calls.Initiate.All
concesse](/docs/_fern-img/4abc1e220d3fc03845594aa4f835656425cc1c39448dae30dace98e672fd8fb1.webp)

## Passaggio 2 — Configura la VM Windows, il certificato e le porte

```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
```

Sulla VM (il codice nativo della piattaforma multimediale li richiede — Windows Server non li include per impostazione predefinita):

```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
```

Apri le stesse porte nel **firewall di Windows** e annota l'**impronta digitale** del certificato — il bot associa Kestrel (443 + una porta per le notifiche) e la piattaforma multimediale (8445) al certificato.

> **Tip**
>
> L'FQDN `*.cloudapp.azure.com` della VM funziona con un certificato Let's Encrypt — non serve un dominio
> separato.

## Passaggio 3 — Compila ed esegui il bot

Parti da **PublicSamples/EchoBot** di [`microsoft-graph-comms-samples`](https://github.com/microsoftgraph/microsoft-graph-comms-samples) di Microsoft — usa `net6.0` e viene compilato con l'SDK .NET (non servono Visual Studio Build Tools):

```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
```

Configura la sezione `AppSettings` di `appsettings.json` con `AadAppId`, `AadAppSecret`, `ServiceDnsName`/`MediaDnsName` (l'FQDN della VM), `CertificateThumbprint` e le porte (chiamate `443`, notifiche `9441`, contenuti multimediali `8445`). Aggiungi due impostazioni per il bridge ElevenLabs indicato sotto: `ElevenLabsAgentId` e `ElevenLabsOrigin` (`wss://api.elevenlabs.io`, o il tuo host di residenza). Eseguilo come attività/servizio pianificato di Windows, così rimane attivo dopo i riavvii.

> **Warning**
>
> Il **limite di tempo di esecuzione predefinito (72 ore)** dell'Utilità di pianificazione termina silenziosamente le attività a lunga esecuzione — un bot avviato all'avvio si arresta tre giorni dopo e le chiamate non riescono con "we couldn't connect you". Disabilita il limite e aggiungi il riavvio in caso di errore:
>
> ```powershell
> $s = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Seconds 0) `
>   -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) -StartWhenAvailable
> Set-ScheduledTask -TaskName EchoBot -Settings $s
> ```

> **Warning**
>
> EchoBot standard si arresta in modo anomalo per una chiamata effettuata sulla porta standard 443: `HttpHelpers.SetAbsoluteUri`
> chiama `req.Host.Port.Value`, che è null quando l'header Host non ha una porta esplicita. Correggilo in
> `req.Host.Port ?? (req.IsHttps ? 443 : 80)`.

### Sostituisci l'eco con ElevenLabs

Il collegamento audio di EchoBot è semplice: `SpeechService.AppendAudioBuffer(in)` e un evento `OnSendMediaBufferEventArgs(out)`. Sostituisci il corpo Azure Speech con un bridge WebSocket dell'agente ElevenLabs che mantiene la stessa interfaccia:

**`SpeechService.cs — bridge ElevenLabs (core)`**

```csharp title="SpeechService.cs — bridge ElevenLabs (core)"
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 });
    }
}
```

Entrambi i lati sono PCM mono a 16 kHz, quindi è un passthrough base64 — imposta l'agente su `pcm_16000`. In caso di `interruption` di ElevenLabs (barge-in), il bridge genera `FlushMedia`; collegalo al tuo stream multimediale affinché elimini eventuali `AudioMediaBuffer` in coda, altrimenti l'agente continua a parlare sopra il chiamante. Il riferimento completo dei messaggi è nella [documentazione WebSocket](/docs/eleven-agents/libraries/web-sockets). La chiusura al termine della chiamata e il trasferimento assistito sono descritti nelle sezioni seguenti.

> **Note**
>
> L'URL in `Connect()` raggiunge un agente **pubblico**. Per un agente privato, richiedi lato server un
> URL firmato a breve scadenza — `GET /v1/convai/conversation/get-signed-url?agent_id=...` con la tua chiave API
> — e connettiti invece all'URL restituito. Per la [residenza dei dati](/docs/overview/administration/data-residency), imposta `ElevenLabsOrigin` sul tuo host di residenza
> (`wss://api.eu.residency.elevenlabs.io`, `.in.` o `.sg.`) — le richieste di URL firmati usano l'host
> `https://` corrispondente.

## Passaggio 4 — Rendilo chiamabile in Teams

1. **Abilita le chiamate** nel canale Teams dell'Azure Bot e imposta il **webhook di chiamata** su `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"
   ```

   Nel portale si trova nella risorsa Azure Bot → **Canali** → **Microsoft Teams** → scheda **Chiamate**:

   ![Il pannello Canali di Azure Bot con il canale Microsoft Teams elencato come
   integro](/docs/_fern-img/01115d26ab645dd6ec6569a36a918cd4de0214c79dcb0070d7a3c3a5e544c93b.webp)

   ![La scheda Chiamate del canale Teams con Abilita chiamate selezionato e il webhook di chiamata
   impostato](/docs/_fern-img/7c4819f64878bfb152661adc948c1170f9efd48ba3d68401c4a4d4ba075951b0.webp)

2. Crea un **manifest dell'app Teams** con `bots[0].supportsCalling: true` e l'ID app del bot, quindi caricalo localmente (**App → Gestisci le tue app → Carica un'app personalizzata**), oppure pubblicalo a livello di organizzazione senza l'interfaccia: `New-TeamsApp -DistributionMethod organization -Path ./bot-app.zip` (modulo PowerShell MicrosoftTeams).

Cerca l'app per nome in Teams e chiamala — il bot risponde e l'agente ElevenLabs parla.

![Una chiamata Teams attiva con il bot dell'agente
ElevenLabs](/docs/_fern-img/0f1fad08a091bcd20ffbbf5c3949b5503f423d8eb6298f464a870b3a0a254726.webp)

> **Note**
>
> Per le chiamate 1:1 per nome non servono né un numero di telefono né un account risorsa — sono necessari
> solo per le chiamate PSTN in ingresso. `Calls.AccessMedia.All` abilita il bridge audio raw.

## Chat di testo (stesso bot)

Lo stesso Azure Bot può anche rispondere ai messaggi **di testo** in Teams — gli utenti possono quindi chiamare l'agente oppure chattare con lui. Le chiamate e la messaggistica sono canali indipendenti del bot: il webhook di chiamata gestisce la voce, mentre un **endpoint di messaggistica** Bot Framework (`/api/messages`) gestisce la chat.

![Una chat Teams con il bot dell'agente ElevenLabs che risponde ai messaggi
di testo](/docs/_fern-img/fb75ae17848916790d29c1c0a703051946902893e46ed62884742db36be453b2.webp)

Indirizza l'endpoint di messaggistica del bot all'host che lo serve (il bot multimediale o qualsiasi altro servizio — non deve essere necessariamente la VM Windows):

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

Implementa l'endpoint con l'SDK Bot Framework e inoltra ogni messaggio all'agente in **modalità testo** tramite lo stesso WebSocket di conversazione usato per la voce — invia un evento [`user_message`](/docs/eleven-agents/customization/events/client-to-server-events), leggi l'evento [`agent_response`](/docs/eleven-agents/customization/events/client-events). Prima abilita il campo **primo messaggio** nelle impostazioni delle [sostituzioni](/docs/eleven-agents/customization/personalization/overrides) dell'agente — il codice seguente lo sostituisce con un valore vuoto, affinché la risposta risponda al messaggio dell'utente anziché al saluto dell'agente:

**`ChatBot.cs — chat di testo Teams -> ElevenLabs (modalità testo)`**

```csharp title="ChatBot.cs — chat di testo Teams -> ElevenLabs (modalità testo)"
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);
}
```

Registralo nel modo standard (un `CloudAdapter`, il bot tramite `AddTransient<IBot, ChatBot>()` e un controller `/api/messages`) e aggiungi gli ambiti di chat alla voce del bot nel manifest:

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

> **Note**
>
> Lo snippet apre una nuova conversazione per ogni messaggio, quindi ogni turno è indipendente. Per la
> **memoria** della chat, mantieni un WebSocket aperto per ogni `conversation.id` di Teams (riutilizzalo tra i turni) ed elimina le
> sessioni inattive — l'agente ricorderà quindi i messaggi precedenti in quella chat. La sostituzione `first_message`
> deve essere abilitata nelle impostazioni delle
> [sostituzioni](/docs/eleven-agents/customization/personalization/overrides) dell'agente — il server
> **chiude la conversazione** se viene inviata una sostituzione non consentita. Se non puoi abilitarla, ometti la
> sostituzione e ignora invece il primo `agent_response` di ogni sessione (il saluto), quindi restituisci
> quello successivo.

> **Tip**
>
> Se le risposte della chat non arrivano mai, abilita l'[evento client](/docs/eleven-agents/customization/events/client-events)
> **`agent_response`** nelle impostazioni **Avanzate** dell'agente — le risposte di testo vengono recapitate tramite quell'evento.

## Fine della chiamata

Quando ElevenLabs termina la conversazione (il relativo strumento **Termina chiamata** chiude il WebSocket), riaggancia la parte Teams:

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

## Trasferimento assistito a una persona

L'agente attiva uno strumento client personalizzato `transfer_to_human`; il bot **invita un utente Teams** nella chiamata in corso (aggiunta consultiva), quindi si ritira:

```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**
>
> Il trasferimento consultivo (`replacesCallId`) richiede che entrambe le parti siano utenti Teams nello \*\*stesso
> tenant \*\*; le destinazioni di trasferimento PSTN richiedono un'istanza dell'applicazione. Per informare prima la persona, passa un
> parametro `reason` dall'agente e riproducilo alla persona prima di collegarla.

## Risoluzione dei problemi

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

La VM ha un solo core fisico. Ridimensionala a ≥ 2 core fisici (ad esempio `D4s_v3`) e riavviala.

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

Installa **VC++ Redistributable** (`vcredist140`) e la funzionalità Windows **Server-Media-Foundation**,
quindi riavvia il bot.

#### La chiamata in ingresso restituisce 500 / non si connette

È il bug della porta null di EchoBot su 443 — correggi `HttpHelpers.SetAbsoluteUri` (vedi il passaggio 3). Verifica anche che
il certificato sia firmato da una CA e raggiungibile sulla porta 443.

#### Chiamando il bot viene visualizzato 'we couldn't connect you'

Verifica che le chiamate siano abilitate nel canale Teams con il webhook `/api/calling` corretto, che l'autorizzazione
Graph `Calls.AccessMedia.All` abbia ricevuto il consenso e che le porte 443/8445/9441 siano aperte sia nel
NSG sia nel firewall di Windows. Se le chiamate **funzionavano e hanno smesso di funzionare**, verifica che il processo del bot
sia ancora in esecuzione sulla VM — il limite di esecuzione predefinito di 72 ore dell'Utilità di pianificazione lo termina alcuni
giorni dopo l'avvio (vedi l'avviso nel passaggio 3).

## Link utili

* [Panoramica dei bot per chiamate e riunioni](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/calls-and-meetings/calls-meetings-bots-overview)
* [Concetti relativi ai contenuti multimediali in tempo reale](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/calls-and-meetings/real-time-media-concepts)
* [Registrare un bot di chiamata](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 (consultivo)](https://learn.microsoft.com/en-us/graph/api/call-transfer)
* [Protocollo WebSocket dell'agente](/docs/eleven-agents/libraries/web-sockets)