> 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

## Visão geral

Essa abordagem dá ao seu agente um **número de telefone**. Quem liga disca para ele, o [Azure Communication Services](https://learn.microsoft.com/en-us/azure/communication-services/concepts/call-automation/call-automation) (ACS) atende com **streaming de mídia bidirecional**, e uma pequena ponte retransmite o áudio PCM entre o ACS e o agente da ElevenLabs usando o [protocolo WebSocket do agente](/docs/pt/eleven-agents/libraries/web-sockets) padrão. É o padrão de central de atendimento / URA — a mesma estrutura de uma implantação de [tronco SIP](/docs/pt/eleven-agents/phone-numbers/sip-trunking), com o ACS como operadora.

Ele também se conecta ao Teams de duas formas: um usuário do Teams com um Plano de Chamadas pode discar diretamente para o número do ACS, ou você pode colocar **Teams Phone Extensibility** na frente do número para que chamadas a uma conta de recurso do Teams sejam roteadas para o ACS.

> **Note**
>
> O ACS provisiona números PSTN apenas em um [conjunto limitado de países](https://learn.microsoft.com/en-us/azure/communication-services/concepts/numbers/sub-eligibility-number-capability).
> Se um número não estiver disponível na sua região, use um provedor SIP com [tronco SIP](/docs/pt/eleven-agents/phone-numbers/sip-trunking) ou o [bot de chamadas do Graph](/docs/pt/eleven-agents/phone-numbers/microsoft-teams/graph-media-bot).

## Como funciona

![Quem liga disca para o número do ACS; o ACS dispara IncomingCall via Event Grid para a ponte, que atende com streaming de mídia PCM 16k bidirecional e o retransmite ao agente da ElevenLabs por um WebSocket](/docs/_fern-files/elevenlabs.docs.buildwithfern.com/e77a27148e217fc7dc06c25007c1c141913fc831cbfe8a2f57405137646271af/assets/images/conversational-ai/teams-acs-architecture.svg)

O áudio é **PCM mono de 16 kHz** em ambas as pontas (o formato de entrada/saída do agente é `pcm_16000`), então ele é transmitido como base64 sem reamostragem.

A ponte expõe estas rotas:

| Rota                     | Finalidade                                                                                           |
| ------------------------ | ---------------------------------------------------------------------------------------------------- |
| `POST /api/incomingCall` | Webhook do Event Grid: valida a assinatura e, em seguida, usa `answer_call` com streaming de mídia   |
| `POST /api/callbacks`    | Eventos de ciclo de vida do Call Automation (`CallConnected`, `CallDisconnected`, `AddParticipant*`) |
| `GET\|WS /ws`            | Socket de streaming de mídia do ACS ↔ ElevenLabs                                                     |
| `POST /api/outboundCall` | Opcional: faz uma chamada de saída que conecta quem atende ao agente                                 |

## Requisitos

1. Uma assinatura **paga** do Azure (MCA / EA / Pay-As-You-Go) — assinaturas gratuitas/de avaliação/patrocinadas não podem comprar números.
2. Um [recurso do Azure Communication Services](https://learn.microsoft.com/en-us/azure/communication-services/quickstarts/create-communication-resource).
3. Um host HTTPS para a ponte com um WebSocket público (Azure Container Apps, App Service ou uma VM).
4. Um [agente da ElevenLabs](/docs/pt/eleven-agents/quickstart) configurado para **PCM 16000 Hz** em ambas as pontas: formato de saída TTS na aba **Voice** e formato de áudio de entrada do usuário na aba **Advanced**.

## Permissões e funções

| Escopo              | Função / permissão                              | Motivo                                                                            |
| ------------------- | ----------------------------------------------- | --------------------------------------------------------------------------------- |
| Azure RBAC          | **Colaborador** no grupo de recursos            | criar o recurso ACS, o Container App e a assinatura do Event Grid                 |
| Assinatura do Azure | **Proprietário ou Colaborador** na assinatura   | comprar números de telefone (a opção de compra fica desabilitada caso contrário)  |
| Cobrança            | Tipo de assinatura **MCA / EA / Pay-As-You-Go** | assinaturas gratuitas, de avaliação, patrocinadas e Dev não podem comprar números |

> **Note**
>
> Com a função **Colaborador** (e não Proprietário), `az containerapp up` não consegue criar a atribuição
> da função de pull do ACR para a identidade gerenciada. Em vez disso, habilite o usuário administrador do registro
> e anexe-o — veja o aviso na Etapa 2.

## Etapa 1 — Provisionar o recurso ACS e o número

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

Compre um número no recurso (Portal → seu recurso ACS → **Phone numbers → Get**, ou o [SDK de phone-numbers](https://learn.microsoft.com/en-us/azure/communication-services/quickstarts/telephony/get-phone-number)). Para um agente que **atende** chamadas, basta um número com **chamadas recebidas**; adicione a capacidade de **saída** se também quiser usar `/api/outboundCall`.

![A tela Phone numbers do recurso ACS listando números ativos com suas capacidades de chamada](/docs/_fern-img/9d14034ee9f81e2ebcf64e30371f914ef6f87a8da30ac250e702662f2e5e1cc1.webp)

Para verificar pela CLI (requer `az extension add --name communication`) e buscar a string de conexão que a ponte usa como `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"
```

## Etapa 2 — Implantar a ponte

A ponte é um pequeno app Flask + `flask-sock` que usa `azure-communication-callautomation`. O núcleo do fluxo de entrada:

**`bridge.py (trecho)`**

```python title="bridge.py (trecho)"
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"})
```

No socket `/ws`, retransmita PCM16 nas duas direções: encaminhe frames `AudioData` do ACS para a ElevenLabs como `{"user_audio_chunk": "<base64>"}` e envie o áudio do agente de volta como `{"Kind":"AudioData","AudioData":{"Data":"<base64>"},"StopAudio":null}`. O primeiro frame enviado pelo ACS é `AudioMetadata` (o formato negociado) — registre-o em log e ignore-o. O lado da ElevenLabs usa o [protocolo WebSocket do agente](/docs/pt/eleven-agents/libraries/web-sockets) padrão.

> **Note**
>
> O ACS usa diferentes convenções de maiúsculas e minúsculas no JSON em cada direção: os frames recebidos que ele **envia** usam camelCase (`kind`,
> `audioData.data`), enquanto os frames de saída que ele **espera** usam PascalCase (`Kind`, `AudioData.Data`,
> `StopAudio`). Mantenha os dois formatos distintos — a retransmissão abaixo segue isso.

**`bridge.py — retransmissão de mídia`**

```python title="bridge.py — retransmissão de mídia"
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**
>
> Essa retransmissão é intencionalmente mínima. Para produção, adicione logs, reconexão e encerramento
> adequado. A referência completa de mensagens está na [documentação de WebSocket](/docs/pt/eleven-agents/libraries/web-sockets).

> **Note**
>
> `EL_WS` conecta-se a um agente **público**. Para um agente privado, faça a ponte solicitar no servidor uma URL
> assinada de curta duração — `GET /v1/convai/conversation/get-signed-url?agent_id=...` com sua chave de API
> — e conecte-se à URL retornada. Para [residência de dados](/docs/pt/overview/administration/data-residency), defina `ELEVENLABS_ORIGIN` como seu host de
> residência (`wss://api.eu.residency.elevenlabs.io`, `.in.` ou `.sg.`) — solicitações de URL assinada
> usam o host `https://` correspondente.

Implante no Azure Container Apps e capture o FQDN público:

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

Em seguida, defina `BRIDGE_PUBLIC_HOST=$FQDN` e a string de conexão do ACS (como segredo) no app.

> **Warning**
>
> Com a função **Colaborador** (e não Proprietário), `az containerapp up` não consegue criar a função de
> pull do ACR para a identidade gerenciada. Habilite o usuário administrador do registro (`az acr update --admin-enabled true`) e anexe-o
> com `az containerapp registry set`; depois, use `az containerapp update --image ...`.

## Etapa 3 — Rotear IncomingCall para a ponte

Crie uma assinatura do Event Grid no recurso ACS que envie `IncomingCall` para a ponte. O handshake de validação da ponte (acima) conclui a assinatura automaticamente.

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

A assinatura aparece na tela **Events** do recurso ACS:

![A tela Events do recurso ACS listando a assinatura de webhook acs-incomingcall filtrada para
Microsoft.Communication.IncomingCall](/docs/_fern-img/f310aed9637e06b81906ca188350e3e721aaa5f4de69af2b3b6e7d157817a1ea.webp)

Disque para o número — o agente atende.

## Como conectar ao Teams

* **Discagem direta:** um usuário do Teams com Teams Phone + Plano de Chamadas pode discar para o número ACS como qualquer número externo.
* **Conta de recurso do Teams (TPE):** vincule uma conta de recurso do Teams ao recurso ACS com [Teams Phone Extensibility](https://learn.microsoft.com/en-us/azure/communication-services/quickstarts/tpe/teams-phone-extensibility-quickstart) para que chamadas à conta de recurso disparem o mesmo fluxo `IncomingCall` → ponte.

## Fim da chamada

Quando o agente encerra a conversa (por exemplo, com a ferramenta **End Call**), a ElevenLabs fecha o WebSocket. Desligue a ponta do ACS para que quem ligou não fique em uma linha sem resposta:

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

## Transferência assistida para uma pessoa

As ferramentas nativas de transferência da ElevenLabs só se aplicam quando a ElevenLabs controla a telefonia. Portanto, aqui o agente dispara uma **ferramenta de cliente personalizada** (por exemplo, `transfer_to_human`) que sua ponte processa **adicionando** a pessoa à chamada ativa com `add_participant` (assistida), em vez de uma transferência cega:

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

O ACS emite callbacks `AddParticipantSucceeded` / `AddParticipantFailed` para `/api/callbacks`. Retorne um `client_tool_result` ao agente para que ele possa dizer sua frase de transferência. Consulte [ferramentas do sistema](/docs/pt/eleven-agents/customization/tools/system-tools/transfer-to-number) para ver a configuração do lado do agente.

> **Tip**
>
> Defina a proteção de transferência assim que a ferramenta for acionada (antes de chamar `add_participant`), ou um fechamento rápido do
> WebSocket da EL poderá competir com o desligamento e encerrar a chamada antes de a pessoa entrar.

## Solução de problemas

#### Nenhum IncomingCall chega à ponte

Confirme que a assinatura do Event Grid foi provisionada (`provisioningState: Succeeded`) e que a
`/api/incomingCall` da ponte retornou a confirmação de validação. Confirme que o número permite chamadas recebidas
e está no mesmo recurso ACS em que está a assinatura. Na aba **Filters** da assinatura,
os tipos de evento devem incluir **Incoming Call**:

![A aba Filters da assinatura de evento com o tipo de evento filtrado para Incoming
Call](/docs/_fern-img/c7026e86d96551d713e4fd7d5753062f643fcd6a67b455866bb8f1bbb6b57eb1.webp)

#### \`CreateCallFailed\` / \`AddParticipantFailed\` para um número internacional

Chamadas de saída do ACS para alguns destinos (por exemplo, Índia) são restritas ou intermitentes. Use um
destino compatível ou coloque um número SIP/de operadora na frente da ponta humana. A lógica da ponte não é afetada —
é uma falha no nível da operadora na ponta de saída.

#### O áudio está distorcido ou na velocidade errada

Os dois lados devem usar PCM mono de 16 kHz. Defina o formato de entrada/saída do agente como `pcm_16000`; a
ponte registra o formato negociado em `conversation_initiation_metadata`.

#### Não consigo comprar um número / número não disponível no meu país

A compra de números requer um tipo de assinatura paga (MCA/EA/PAYG). Se o ACS não oferecer números no
seu país, use um provedor [SIP](/docs/pt/eleven-agents/phone-numbers/sip-trunking).

## Links úteis

* [Visão geral do ACS Call Automation](https://learn.microsoft.com/en-us/azure/communication-services/concepts/call-automation/call-automation)
* [Streaming de áudio do ACS](https://learn.microsoft.com/en-us/azure/communication-services/concepts/call-automation/audio-streaming-concept)
* [Ações de controle de chamadas (transferir / adicionar participante)](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)
* [Protocolo WebSocket do agente](/docs/pt/eleven-agents/libraries/web-sockets)
* [Tronco SIP da ElevenLabs](/docs/pt/eleven-agents/phone-numbers/sip-trunking)