> 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

## Descripción general

Este enfoque proporciona a tu agente un **número de teléfono**. Quien llama lo marca, [Azure Communication Services](https://learn.microsoft.com/en-us/azure/communication-services/concepts/call-automation/call-automation) (ACS) responde con **streaming multimedia bidireccional**, y un pequeño puente transmite audio PCM entre ACS y el agente de ElevenLabs mediante el [protocolo WebSocket de agentes](/docs/es/eleven-agents/libraries/web-sockets) estándar. Es el patrón de centro de contacto/IVR, la misma arquitectura que una implementación de [trunking SIP](/docs/es/eleven-agents/phone-numbers/sip-trunking), con ACS como operador.

También se conecta a Teams de dos formas: un usuario de Teams con un plan de llamadas puede marcar directamente el número de ACS, o puedes poner delante del número **Teams Phone Extensibility** para que las llamadas a una cuenta de recursos de Teams se dirijan a ACS.

> **Note**
>
> ACS aprovisiona números de PSTN solo en un [conjunto limitado de países](https://learn.microsoft.com/en-us/azure/communication-services/concepts/numbers/sub-eligibility-number-capability).
> Si no hay un número disponible en tu región, utiliza un proveedor SIP con [trunking SIP](/docs/es/eleven-agents/phone-numbers/sip-trunking) o el [bot de llamadas de Graph](/docs/es/eleven-agents/phone-numbers/microsoft-teams/graph-media-bot).

## Cómo funciona

![Quien llama marca el número de ACS; ACS envía IncomingCall mediante Event Grid al puente, que responde con streaming multimedia PCM 16k bidireccional y lo transmite al agente de ElevenLabs mediante un WebSocket](/docs/_fern-files/elevenlabs.docs.buildwithfern.com/e77a27148e217fc7dc06c25007c1c141913fc831cbfe8a2f57405137646271af/assets/images/conversational-ai/teams-acs-architecture.svg)

El audio es **PCM mono de 16 kHz** en ambos extremos (el formato de entrada/salida del agente es `pcm_16000`), por lo que se transmite como base64 sin remuestreo.

El puente expone estas rutas:

| Ruta                     | Finalidad                                                                                             |
| ------------------------ | ----------------------------------------------------------------------------------------------------- |
| `POST /api/incomingCall` | Webhook de Event Grid: valida la suscripción y después ejecuta `answer_call` con streaming multimedia |
| `POST /api/callbacks`    | Eventos de ciclo de vida de Call Automation (`CallConnected`, `CallDisconnected`, `AddParticipant*`)  |
| `GET\|WS /ws`            | Socket de streaming multimedia de ACS ↔ ElevenLabs                                                    |
| `POST /api/outboundCall` | Opcional: realiza una llamada saliente que conecta a quien responde con el agente                     |

## Requisitos

1. Una suscripción de Azure **de pago** (MCA / EA / Pay-As-You-Go); las suscripciones gratuitas, de prueba o patrocinadas no pueden comprar números.
2. Un [recurso de Azure Communication Services](https://learn.microsoft.com/en-us/azure/communication-services/quickstarts/create-communication-resource).
3. Un host HTTPS para el puente con un WebSocket público (Azure Container Apps, App Service o una VM).
4. Un [agente de ElevenLabs](/docs/es/eleven-agents/quickstart) configurado en **PCM 16000 Hz** en ambos extremos: formato de salida TTS en la pestaña **Voz** y formato de audio de entrada de usuario en la pestaña **Avanzado**.

## Permisos y roles

| Ámbito               | Rol / permiso                                    | Motivo                                                                               |
| -------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------ |
| Azure RBAC           | **Colaborador** en el grupo de recursos          | crear el recurso ACS, la Container App y la suscripción de Event Grid                |
| Suscripción de Azure | **Propietario o colaborador** en la suscripción  | comprar números de teléfono (de lo contrario, la opción de compra está desactivada)  |
| Facturación          | Tipo de suscripción **MCA / EA / Pay-As-You-Go** | las suscripciones gratuitas, de prueba, patrocinadas y Dev no pueden comprar números |

> **Note**
>
> Con el rol **Colaborador** (no Propietario), `az containerapp up` no puede crear la asignación del rol de
> extracción de ACR de identidad administrada. Activa el usuario administrador del registro y asígnalo en su lugar; consulta la advertencia del paso 2.

## Paso 1: aprovisiona el recurso y el número de ACS

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

Compra un número en el recurso (Portal → tu recurso ACS → **Números de teléfono → Obtener**, o el [SDK de phone-numbers](https://learn.microsoft.com/en-us/azure/communication-services/quickstarts/telephony/get-phone-number)). Para un agente que **responde** llamadas, basta con un número con **llamadas entrantes**; añade la capacidad **saliente** si también quieres usar `/api/outboundCall`.

![El panel Phone numbers del recurso ACS muestra los números activos y sus
capacidades de llamada](/docs/_fern-img/9d14034ee9f81e2ebcf64e30371f914ef6f87a8da30ac250e702662f2e5e1cc1.webp)

Para verificarlo desde la CLI (requiere `az extension add --name communication`) y obtener la cadena de conexión que el puente utiliza 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"
```

## Paso 2: despliega el puente

El puente es una pequeña aplicación Flask + `flask-sock` que utiliza `azure-communication-callautomation`. El núcleo del flujo entrante:

**`bridge.py (extracto)`**

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

En el socket `/ws`, transmite PCM16 en ambos sentidos: reenvía los frames `AudioData` de ACS a ElevenLabs como `{"user_audio_chunk": "<base64>"}` y devuelve el audio del agente como `{"Kind":"AudioData","AudioData":{"Data":"<base64>"},"StopAudio":null}`. El primer frame que envía ACS es `AudioMetadata` (el formato negociado): regístralo e ignóralo. El lado de ElevenLabs utiliza el [protocolo WebSocket de agentes](/docs/es/eleven-agents/libraries/web-sockets) estándar.

> **Note**
>
> ACS usa distintos formatos de mayúsculas y minúsculas en el JSON según la dirección: los frames entrantes que **envía** usan camelCase (`kind`,
> `audioData.data`), mientras que los frames salientes que **espera** usan PascalCase (`Kind`, `AudioData.Data`,
> `StopAudio`). Mantén ambos formatos diferenciados: la retransmisión de abajo los refleja.

**`bridge.py — retransmisión multimedia`**

```python title="bridge.py — retransmisión multimedia"
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**
>
> Esta retransmisión es intencionadamente mínima. Para producción, añade registros, reconexión y una
> desconexión ordenada. Consulta la referencia completa de mensajes en la [documentación de WebSocket](/docs/es/eleven-agents/libraries/web-sockets).

> **Note**
>
> `EL_WS` se conecta a un agente **público**. Para un agente privado, haz que el puente solicite una URL firmada de corta duración
> en el servidor: `GET /v1/convai/conversation/get-signed-url?agent_id=...` con tu clave de API,
> y conéctate en su lugar a la URL devuelta. Para [residencia de datos](/docs/es/overview/administration/data-residency), configura `ELEVENLABS_ORIGIN` con tu host de
> residencia (`wss://api.eu.residency.elevenlabs.io`, `.in.` o `.sg.`): las solicitudes de URL firmada
> usan el host `https://` correspondiente.

Despliega en Azure Container Apps y captura el 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)
```

Después, configura `BRIDGE_PUBLIC_HOST=$FQDN` y la cadena de conexión de ACS (como secreto) en la aplicación.

> **Warning**
>
> Con el rol **Colaborador** (no Propietario), `az containerapp up` no puede crear el rol de extracción de ACR
> de identidad administrada. Activa el usuario administrador del registro (`az acr update --admin-enabled true`) y asígnalo
> con `az containerapp registry set`; después ejecuta `az containerapp update --image ...`.

## Paso 3: dirige IncomingCall al puente

Crea una suscripción de Event Grid en el recurso ACS que publique `IncomingCall` en el puente. El protocolo de validación del puente (anterior) completa la suscripción automáticamente.

```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 suscripción aparece en el panel **Events** del recurso ACS:

![El panel Events del recurso ACS muestra la suscripción de webhook acs-incomingcall filtrada
por
Microsoft.Communication.IncomingCall](/docs/_fern-img/f310aed9637e06b81906ca188350e3e721aaa5f4de69af2b3b6e7d157817a1ea.webp)

Marca el número: el agente responderá.

## Conexión con Teams

* **Marcación directa:** un usuario de Teams con Teams Phone y un plan de llamadas puede marcar el número de ACS como cualquier número externo.
* **Cuenta de recursos de Teams (TPE):** vincula una cuenta de recursos de Teams al recurso ACS con [Teams Phone Extensibility](https://learn.microsoft.com/en-us/azure/communication-services/quickstarts/tpe/teams-phone-extensibility-quickstart) para que las llamadas a la cuenta de recursos activen el mismo flujo `IncomingCall` → puente.

## Fin de la llamada

Cuando el agente termina la conversación (por ejemplo, mediante su herramienta **Finalizar llamada**), ElevenLabs cierra el WebSocket. Cuelga el extremo de ACS para que quien llama no quede en una línea muerta:

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

## Transferencia asistida a una persona

Las herramientas de transferencia nativas de ElevenLabs solo se aplican cuando ElevenLabs gestiona la telefonía, así que aquí el agente activa una **herramienta de cliente personalizada** (por ejemplo, `transfer_to_human`) que tu puente gestiona **añadiendo** a la persona a la llamada activa con `add_participant` (asistida), en lugar de realizar una transferencia ciega:

```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 envía devoluciones de llamada `AddParticipantSucceeded` / `AddParticipantFailed` a `/api/callbacks`. Devuelve un `client_tool_result` al agente para que pueda decir su frase de transferencia. Consulta las [herramientas del sistema](/docs/es/eleven-agents/customization/tools/system-tools/transfer-to-number) para configurar el lado del agente.

> **Tip**
>
> Configura la protección de transferencia en el momento en que se active la herramienta (antes de llamar a `add_participant`), o un cierre rápido del
> WebSocket de EL podría competir con el cuelgue y finalizar la llamada antes de que se una la persona.

## Solución de problemas

#### No llega ningún IncomingCall al puente

Confirma que la suscripción de Event Grid se aprovisionó (`provisioningState: Succeeded`) y que
`/api/incomingCall` del puente devolvió el eco de validación. Confirma que el número tiene llamadas
entrantes y está en el mismo recurso ACS en el que se encuentra la suscripción. En la pestaña
**Filters** de la suscripción, los tipos de evento deben incluir **Incoming Call**:

![La pestaña Filters de la suscripción de evento, con el tipo de evento filtrado por Incoming
Call](/docs/_fern-img/c7026e86d96551d713e4fd7d5753062f643fcd6a67b455866bb8f1bbb6b57eb1.webp)

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

Las llamadas salientes de ACS a algunos destinos (por ejemplo, India) tienen restricciones o son intermitentes. Utiliza un
destino compatible o conecta el extremo de la persona mediante un número SIP/de operador. La lógica del puente no se ve afectada:
es un fallo a nivel de operador en el extremo saliente.

#### El audio está distorsionado o a una velocidad incorrecta

Ambos lados deben ser PCM mono de 16 kHz. Configura el formato de entrada/salida del agente en `pcm_16000`; el
puente registra el formato negociado desde `conversation_initiation_metadata`.

#### No puedo comprar un número / el número no está disponible en mi país

La compra de números requiere un tipo de suscripción de pago (MCA/EA/PAYG). Si ACS no ofrece números en
tu país, utiliza un proveedor [SIP](/docs/es/eleven-agents/phone-numbers/sip-trunking).

## Enlaces útiles

* [Descripción general de ACS Call Automation](https://learn.microsoft.com/en-us/azure/communication-services/concepts/call-automation/call-automation)
* [Streaming de audio de ACS](https://learn.microsoft.com/en-us/azure/communication-services/concepts/call-automation/audio-streaming-concept)
* [Acciones de control de llamadas (transferir / añadir 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 de agentes](/docs/es/eleven-agents/libraries/web-sockets)
* [Trunking SIP de ElevenLabs](/docs/es/eleven-agents/phone-numbers/sip-trunking)