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

# Azure Communication Services

## Panoramica

Questo approccio assegna al tuo agente un **numero di telefono**. Un chiamante lo compone, [Azure Communication Services](https://learn.microsoft.com/en-us/azure/communication-services/concepts/call-automation/call-automation) (ACS) risponde con **streaming multimediale bidirezionale** e un piccolo bridge inoltra l'audio PCM tra ACS e l'agente ElevenLabs usando il [protocollo WebSocket dell'agente](/docs/it/eleven-agents/libraries/web-sockets) standard. È il modello di contact center/IVR, analogo a un'implementazione con [SIP trunking](/docs/it/eleven-agents/phone-numbers/sip-trunking), con ACS come operatore.

Si connette inoltre a Teams in due modi: un utente Teams con un Calling Plan può chiamare direttamente il numero ACS, oppure puoi esporre il numero tramite **Teams Phone Extensibility** affinché le chiamate a un account risorsa Teams vengano instradate ad ACS.

> **Note**
>
> ACS fornisce numeri PSTN solo in un [numero limitato di paesi](https://learn.microsoft.com/en-us/azure/communication-services/concepts/numbers/sub-eligibility-number-capability).
> Se nella tua area geografica non è disponibile un numero, usa invece un provider SIP con [SIP trunking](/docs/it/eleven-agents/phone-numbers/sip-trunking), oppure il [bot di chiamata Graph](/docs/it/eleven-agents/phone-numbers/microsoft-teams/graph-media-bot).

## Come funziona

![Un chiamante compone il numero ACS; ACS attiva IncomingCall tramite Event Grid verso il bridge, che risponde con streaming multimediale PCM 16k bidirezionale e lo inoltra all'agente ElevenLabs tramite WebSocket](/docs/_fern-files/elevenlabs.docs.buildwithfern.com/e77a27148e217fc7dc06c25007c1c141913fc831cbfe8a2f57405137646271af/assets/images/conversational-ai/teams-acs-architecture.svg)

L'audio è **PCM 16 kHz mono** su entrambi i lati (il formato di input/output dell'agente è `pcm_16000`), quindi viene trasferito come base64 senza ricampionamento.

Il bridge espone queste route:

| Route                    | Scopo                                                                                                   |
| ------------------------ | ------------------------------------------------------------------------------------------------------- |
| `POST /api/incomingCall` | Webhook Event Grid: convalida la sottoscrizione, quindi esegue `answer_call` con streaming multimediale |
| `POST /api/callbacks`    | Eventi del ciclo di vita di Call Automation (`CallConnected`, `CallDisconnected`, `AddParticipant*`)    |
| `GET\|WS /ws`            | Socket di streaming multimediale ACS ↔ ElevenLabs                                                       |
| `POST /api/outboundCall` | Opzionale: effettua una chiamata in uscita che collega chi risponde all'agente                          |

## Requisiti

1. Una sottoscrizione Azure **a pagamento** (MCA / EA / Pay-As-You-Go): le sottoscrizioni gratuite, di prova o sponsorizzate non possono acquistare numeri.
2. Una [risorsa Azure Communication Services](https://learn.microsoft.com/en-us/azure/communication-services/quickstarts/create-communication-resource).
3. Un host HTTPS per il bridge con un WebSocket pubblico (Azure Container Apps, App Service o una VM).
4. Un [agente ElevenLabs](/docs/it/eleven-agents/quickstart) impostato su **PCM 16000 Hz** su entrambi i lati: formato di output TTS nella scheda **Voce**, formato audio di input dell'utente nella scheda **Avanzate**.

## Autorizzazioni e ruoli

| Ambito               | Ruolo / autorizzazione                              | Motivo                                                                                  |
| -------------------- | --------------------------------------------------- | --------------------------------------------------------------------------------------- |
| Azure RBAC           | **Contributor** nel gruppo di risorse               | creare la risorsa ACS, la Container App e la sottoscrizione Event Grid                  |
| Sottoscrizione Azure | **Owner o Contributor** nella sottoscrizione        | acquistare numeri di telefono (altrimenti l'opzione di acquisto è disabilitata)         |
| Fatturazione         | Tipo di sottoscrizione **MCA / EA / Pay-As-You-Go** | le sottoscrizioni gratuite, di prova, sponsorizzate e Dev non possono acquistare numeri |

> **Note**
>
> Con il ruolo **Contributor** (non Owner), `az containerapp up` non può creare l'assegnazione del
> ruolo ACR pull per l'identità gestita. Abilita invece l'utente amministratore del registro e collegalo: vedi l'avviso nel passaggio 2.

## Passaggio 1 — Esegui il provisioning della risorsa ACS e del numero

```bash
RG=my-rg
# Register providers (once)
az provider register -n Microsoft.Communication --wait
az provider register -n Microsoft.EventGrid --wait

# Create the ACS resource
az communication create --name my-acs --resource-group $RG \
  --location global --data-location unitedstates
```

Acquista un numero nella risorsa (Portale → risorsa ACS → **Numeri di telefono → Ottieni**, oppure usa l'[SDK phone-numbers](https://learn.microsoft.com/en-us/azure/communication-services/quickstarts/telephony/get-phone-number)). Per un agente che **risponde** alle chiamate, è sufficiente un numero con **chiamate in entrata**; aggiungi la funzionalità **in uscita** se vuoi usare anche `/api/outboundCall`.

![Il pannello Phone numbers della risorsa ACS che elenca i numeri attivi con le relative funzionalità di chiamata](/docs/_fern-img/9d14034ee9f81e2ebcf64e30371f914ef6f87a8da30ac250e702662f2e5e1cc1.webp)

Per verificare dalla CLI (richiede `az extension add --name communication`) e recuperare la stringa di connessione che il bridge usa come `ACS_CONNECTION_STRING`:

```bash
CONN=$(az communication list-key -n my-acs -g $RG --query primaryConnectionString -o tsv)
az communication phonenumber list --connection-string "$CONN" --query "[].phoneNumber"
```

## Passaggio 2 — Distribuisci il bridge

Il bridge è una piccola app Flask + `flask-sock` che usa `azure-communication-callautomation`. Ecco il nucleo del flusso in entrata:

**`bridge.py (estratto)`**

```python title="bridge.py (estratto)"
from azure.communication.callautomation import (
    CallAutomationClient, MediaStreamingOptions, StreamingTransportType,
    MediaStreamingContentType, MediaStreamingAudioChannelType, AudioFormat,
)

@app.route("/api/incomingCall", methods=["POST"])
def incoming_call():
    for event in request.get_json():
        # Event Grid subscription validation handshake
        if event.get("eventType") == "Microsoft.EventGrid.SubscriptionValidationEvent":
            return jsonify({"validationResponse": event["data"]["validationCode"]})

        if event.get("eventType") == "Microsoft.Communication.IncomingCall":
            client = CallAutomationClient.from_connection_string(ACS_CONNECTION_STRING)
            client.answer_call(
                incoming_call_context=event["data"]["incomingCallContext"],
                callback_url=f"https://{HOST}/api/callbacks",
                media_streaming=MediaStreamingOptions(
                    transport_url=f"wss://{HOST}/ws",
                    transport_type=StreamingTransportType.WEBSOCKET,
                    content_type=MediaStreamingContentType.AUDIO,
                    audio_channel_type=MediaStreamingAudioChannelType.MIXED,
                    start_media_streaming=True,
                    enable_bidirectional=True,
                    audio_format=AudioFormat.PCM16_K_MONO,
                ),
            )
    return jsonify({"status": "ok"})
```

Nel socket `/ws`, inoltra PCM16 in entrambe le direzioni: invia i frame ACS `AudioData` a ElevenLabs come `{"user_audio_chunk": "<base64>"}` e rimanda l'audio dell'agente come `{"Kind":"AudioData","AudioData":{"Data":"<base64>"},"StopAudio":null}`. Il primo frame inviato da ACS è `AudioMetadata` (il formato negoziato): registralo e ignoralo. Il lato ElevenLabs usa il [protocollo WebSocket dell'agente](/docs/it/eleven-agents/libraries/web-sockets) standard.

> **Note**
>
> ACS usa maiuscole/minuscole JSON diverse per ciascuna direzione: i frame in entrata che **invia** sono in camelCase (`kind`,
> `audioData.data`), mentre i frame in uscita che **si aspetta** sono in PascalCase (`Kind`, `AudioData.Data`,
> `StopAudio`). Mantieni distinti i due formati: il relay seguente rispecchia questa differenza.

**`bridge.py — relay multimediale`**

```python title="bridge.py — relay multimediale"
import asyncio, json, os, queue, threading, websockets
from flask_sock import Sock

sock = Sock(app)
AGENT_ID = os.environ["ELEVENLABS_AGENT_ID"]
# US default; data residency: wss://api.eu.residency.elevenlabs.io, .in., or .sg.
EL_ORIGIN = os.environ.get("ELEVENLABS_ORIGIN", "wss://api.elevenlabs.io")
EL_WS = f"{EL_ORIGIN}/v1/convai/conversation?agent_id={AGENT_ID}"

@sock.route("/ws")
def media_stream(ws):
    loop = asyncio.new_event_loop()
    el = {"ws": None}
    to_acs = queue.Queue()  # outbound frames; only this handler thread touches `ws`

    async def el_session():
        async with websockets.connect(EL_WS) as elws:
            el["ws"] = elws
            await elws.send(json.dumps({"type": "conversation_initiation_client_data"}))
            async for msg in elws:
                data = json.loads(msg)
                kind = data.get("type")
                if kind == "audio":  # agent audio -> caller
                    b64 = data["audio_event"]["audio_base_64"]
                    to_acs.put({"Kind": "AudioData", "AudioData": {"Data": b64}, "StopAudio": None})
                elif kind == "ping":
                    await elws.send(json.dumps({"type": "pong", "event_id": data["ping_event"]["event_id"]}))
                elif kind == "interruption":  # barge-in
                    to_acs.put({"Kind": "StopAudio", "AudioData": None, "StopAudio": {}})

    threading.Thread(target=lambda: loop.run_until_complete(el_session()), daemon=True).start()

    # Keep all ACS-socket I/O on this one thread: receive with a short timeout,
    # then drain any audio the ElevenLabs thread queued. Sending from the other
    # thread would race flask-sock and corrupt the stream.
    try:
        while True:
            raw = ws.receive(timeout=0.02)  # None when no frame arrived this tick
            if raw:
                evt = json.loads(raw)
                if evt.get("kind") == "AudioData" and el["ws"]:  # caller audio -> agent
                    asyncio.run_coroutine_threadsafe(
                        el["ws"].send(json.dumps({"user_audio_chunk": evt["audioData"]["data"]})), loop)
            while not to_acs.empty():
                ws.send(json.dumps(to_acs.get_nowait()))
    except Exception:
        pass  # ACS socket closed
```

> **Note**
>
> Questo relay è volutamente essenziale. Per la produzione, aggiungi logging, riconnessione e
> chiusura pulita. Il riferimento completo dei messaggi è nella [documentazione WebSocket](/docs/it/eleven-agents/libraries/web-sockets).

> **Note**
>
> `EL_WS` si connette a un agente **pubblico**. Per un agente privato, fai in modo che il bridge richieda lato server un URL firmato di breve durata:
> `GET /v1/convai/conversation/get-signed-url?agent_id=...` con la tua chiave API, quindi connettiti all'URL restituito. Per la [residenza dei dati](/docs/it/overview/administration/data-residency), imposta `ELEVENLABS_ORIGIN` sull'host della tua area di residenza (`wss://api.eu.residency.elevenlabs.io`, `.in.` o `.sg.`): le richieste di URL firmati usano l'host `https://` corrispondente.

Distribuisci su Azure Container Apps e acquisisci l'FQDN pubblico:

```bash
az containerapp up --name acs-el-bridge --resource-group $RG \
  --source . --ingress external --target-port 8080 \
  --env-vars ELEVENLABS_AGENT_ID=$AGENT_ID \
    ELEVENLABS_ORIGIN=wss://api.elevenlabs.io

FQDN=$(az containerapp show -n acs-el-bridge -g $RG \
  --query properties.configuration.ingress.fqdn -o tsv)
```

Quindi imposta `BRIDGE_PUBLIC_HOST=$FQDN` e la stringa di connessione ACS (come secret) nell'app.

> **Warning**
>
> Con il ruolo **Contributor** (non Owner), `az containerapp up` non può creare il ruolo ACR pull
> dell'identità gestita. Abilita l'utente amministratore del registro (`az acr update --admin-enabled true`) e collegalo con `az containerapp registry set`, quindi esegui `az containerapp update --image ...`.

## Passaggio 3 — Instrada IncomingCall al bridge

Crea una sottoscrizione Event Grid sulla risorsa ACS che invii `IncomingCall` al bridge. L'handshake di convalida del bridge (sopra) completa automaticamente la sottoscrizione.

```bash
ACS_ID=$(az communication show -n my-acs -g $RG --query id -o tsv)
az eventgrid event-subscription create \
  --name acs-incomingcall \
  --source-resource-id "$ACS_ID" \
  --endpoint "https://$FQDN/api/incomingCall" \
  --endpoint-type webhook \
  --included-event-types Microsoft.Communication.IncomingCall

# Verify — should print "Succeeded"
az eventgrid event-subscription show --name acs-incomingcall \
  --source-resource-id "$ACS_ID" --query provisioningState -o tsv
```

La sottoscrizione appare nel pannello **Events** della risorsa ACS:

![Il pannello Events della risorsa ACS che elenca la sottoscrizione webhook acs-incomingcall filtrata su Microsoft.Communication.IncomingCall](/docs/_fern-img/f310aed9637e06b81906ca188350e3e721aaa5f4de69af2b3b6e7d157817a1ea.webp)

Chiama il numero: risponderà l'agente.

## Collegamento a Teams

* **Chiamata diretta:** un utente Teams con Teams Phone + un Calling Plan può chiamare il numero ACS come qualsiasi numero esterno.
* **Account risorsa Teams (TPE):** collega un account risorsa Teams alla risorsa ACS con [Teams Phone Extensibility](https://learn.microsoft.com/en-us/azure/communication-services/quickstarts/tpe/teams-phone-extensibility-quickstart), affinché le chiamate all'account risorsa attivino lo stesso flusso `IncomingCall` → bridge.

## Fine della chiamata

Quando l'agente termina la conversazione (ad esempio con lo strumento **End Call**), ElevenLabs chiude il WebSocket. Riaggancia il lato ACS affinché il chiamante non resti su una linea inattiva:

```python
CallAutomationClient.from_connection_string(ACS_CONNECTION_STRING) \
    .get_call_connection(call_connection_id).hang_up(is_for_everyone=True)
```

## Trasferimento assistito a un operatore

Gli strumenti di trasferimento nativi di ElevenLabs si applicano solo quando ElevenLabs gestisce la telefonia. In questo caso, quindi, l'agente attiva uno **strumento client personalizzato** (ad esempio `transfer_to_human`) che il bridge gestisce **aggiungendo** l'operatore alla chiamata in corso con `add_participant` (trasferimento assistito), anziché effettuare un trasferimento cieco:

```python
conn = client.get_call_connection(call_connection_id)
conn.add_participant(
    PhoneNumberIdentifier(human_number),
    source_caller_id_number=PhoneNumberIdentifier(your_outbound_number),
    invitation_timeout=30,
)
# then mute the bot and skip the end-of-call hangup so the human's leg survives
```

ACS invia i callback `AddParticipantSucceeded` / `AddParticipantFailed` a `/api/callbacks`. Restituisci un `client_tool_result` all'agente, così potrà pronunciare il messaggio di passaggio. Consulta gli [strumenti di sistema](/docs/it/eleven-agents/customization/tools/system-tools/transfer-to-number) per la configurazione lato agente.

> **Tip**
>
> Imposta la protezione del trasferimento non appena si attiva lo strumento (prima di chiamare `add_participant`), altrimenti una rapida chiusura del WebSocket EL può entrare in conflitto con il riaggancio e interrompere la chiamata prima che l'operatore si unisca.

## Risoluzione dei problemi

#### Nessun IncomingCall raggiunge il bridge

Verifica che sia stato effettuato il provisioning della sottoscrizione Event Grid (`provisioningState: Succeeded`) e che `/api/incomingCall` del bridge abbia restituito l'eco di convalida. Verifica che il numero supporti le chiamate in entrata e appartenga alla stessa risorsa ACS su cui è presente la sottoscrizione. Nella scheda **Filters** della sottoscrizione, i tipi di evento devono includere **Incoming Call**:

![La scheda Filters della sottoscrizione evento con il tipo di evento filtrato su Incoming Call](/docs/_fern-img/c7026e86d96551d713e4fd7d5753062f643fcd6a67b455866bb8f1bbb6b57eb1.webp)

#### \`CreateCallFailed\` / \`AddParticipantFailed\` per un numero internazionale

Le chiamate in uscita ACS verso alcune destinazioni (ad esempio l'India) sono limitate o intermittenti. Usa una destinazione supportata oppure anteponi al lato dell'operatore un numero SIP/Operator. La logica del bridge non cambia: si tratta di un errore a livello di operatore sul lato della chiamata in uscita.

#### L'audio è distorto o ha una velocità errata

Entrambi i lati devono usare PCM 16 kHz mono. Imposta il formato di input/output dell'agente su `pcm_16000`; il bridge registra il formato negoziato da `conversation_initiation_metadata`.

#### Non riesco ad acquistare un numero / il numero non è disponibile nel mio Paese

L'acquisto di numeri richiede un tipo di sottoscrizione a pagamento (MCA/EA/PAYG). Se ACS non offre numeri nel tuo Paese, usa invece un provider [SIP](/docs/it/eleven-agents/phone-numbers/sip-trunking).

## Link utili

* [Panoramica di ACS Call Automation](https://learn.microsoft.com/en-us/azure/communication-services/concepts/call-automation/call-automation)
* [Streaming audio ACS](https://learn.microsoft.com/en-us/azure/communication-services/concepts/call-automation/audio-streaming-concept)
* [Azioni di controllo chiamata (trasferimento / aggiunta partecipante)](https://learn.microsoft.com/en-us/azure/communication-services/how-tos/call-automation/actions-for-call-control)
* [Teams Phone Extensibility](https://learn.microsoft.com/en-us/azure/communication-services/quickstarts/tpe/teams-phone-extensibility-quickstart)
* [Protocollo WebSocket dell'agente](/docs/it/eleven-agents/libraries/web-sockets)
* [SIP trunking di ElevenLabs](/docs/it/eleven-agents/phone-numbers/sip-trunking)