> 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

## Översikt

Med den här metoden får din agent ett **telefonnummer**. En uppringare ringer numret, [Azure Communication Services](https://learn.microsoft.com/en-us/azure/communication-services/concepts/call-automation/call-automation) (ACS) svarar med **dubbelriktad mediestreaming**, och en liten brygga vidarebefordrar PCM-ljud mellan ACS och ElevenLabs-agenten via standardprotokollet för [agent-WebSocket](/docs/sv/eleven-agents/libraries/web-sockets). Det är kontaktcenter-/IVR-mönstret — samma upplägg som en [SIP trunking](/docs/sv/eleven-agents/phone-numbers/sip-trunking)-distribution, med ACS som operatör.

Den ansluter också till Teams på två sätt: en Teams-användare med Calling Plan kan ringa ACS-numret direkt, eller så kan du lägga **Teams Phone Extensibility** framför numret så att samtal till ett Teams-resurskonto dirigeras till ACS.

> **Note**
>
> ACS tillhandahåller PSTN-nummer endast i en [begränsad uppsättning länder](https://learn.microsoft.com/en-us/azure/communication-services/concepts/numbers/sub-eligibility-number-capability).
> Om ett nummer inte är tillgängligt i din region använder du i stället en SIP-leverantör med [SIP trunking](/docs/sv/eleven-agents/phone-numbers/sip-trunking), eller [Graph-samtalsboten](/docs/sv/eleven-agents/phone-numbers/microsoft-teams/graph-media-bot).

## Så fungerar det

![En uppringare ringer ACS-numret; ACS skickar IncomingCall via Event Grid till bryggan, som svarar med dubbelriktad PCM 16k-mediastreaming och vidarebefordrar den till ElevenLabs-agenten via en WebSocket](/docs/_fern-files/elevenlabs.docs.buildwithfern.com/e77a27148e217fc7dc06c25007c1c141913fc831cbfe8a2f57405137646271af/assets/images/conversational-ai/teams-acs-architecture.svg)

Ljudet är **PCM 16 kHz mono** i båda riktningarna (agentens in-/utdataformat är `pcm_16000`), så det skickas vidare som base64 utan omsampling.

Bryggan exponerar följande rutter:

| Rutt                     | Syfte                                                                                        |
| ------------------------ | -------------------------------------------------------------------------------------------- |
| `POST /api/incomingCall` | Event Grid-webhook: validerar prenumerationen och kör sedan `answer_call` med mediestreaming |
| `POST /api/callbacks`    | Call Automation-livscykelhändelser (`CallConnected`, `CallDisconnected`, `AddParticipant*`)  |
| `GET\|WS /ws`            | ACS-mediaströmssocket ↔ ElevenLabs                                                           |
| `POST /api/outboundCall` | Valfritt: ring ett utgående samtal som kopplar mottagaren till agenten                       |

## Krav

1. En **betald** Azure-prenumeration (MCA / EA / Pay-As-You-Go) — kostnadsfria prov- och sponsringsprenumerationer kan inte köpa nummer.
2. En [Azure Communication Services-resurs](https://learn.microsoft.com/en-us/azure/communication-services/quickstarts/create-communication-resource).
3. En HTTPS-värd för bryggan med en offentlig WebSocket (Azure Container Apps, App Service eller en VM).
4. En [ElevenLabs-agent](/docs/sv/eleven-agents/quickstart) inställd på **PCM 16000 Hz** i båda riktningarna: TTS-utdataformat på fliken **Voice**, och ljudformat för användarindata på fliken **Advanced**.

## Behörigheter och roller

| Omfattning          | Roll / behörighet                               | Varför                                                                        |
| ------------------- | ----------------------------------------------- | ----------------------------------------------------------------------------- |
| Azure RBAC          | **Contributor** för resursgruppen               | skapa ACS-resursen, Container App och Event Grid-prenumerationen              |
| Azure-prenumeration | **Owner eller Contributor** för prenumerationen | köpa telefonnummer (köpalternativet är annars inaktiverat)                    |
| Fakturering         | Prenumerationstyp **MCA / EA / Pay-As-You-Go**  | kostnadsfria, prov-, sponsrings- och Dev-prenumerationer kan inte köpa nummer |

> **Note**
>
> Med **Contributor** (inte Owner) kan `az containerapp up` inte skapa rolltilldelningen för ACR-pull
> med hanterad identitet. Aktivera i stället registeradministratörsanvändaren och koppla den — se varningen i steg 2.

## Steg 1 — Etablera ACS-resursen och numret

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

Köp ett nummer i resursen (Portal → din ACS-resurs → **Phone numbers → Get**, eller [phone-numbers SDK](https://learn.microsoft.com/en-us/azure/communication-services/quickstarts/telephony/get-phone-number)). För en agent som **svarar** på samtal räcker ett nummer med **inbound calling**; lägg till stöd för **outbound** om du även vill använda `/api/outboundCall`.

![Vyn Phone numbers för ACS-resursen visar aktiva nummer med deras
samtalsfunktioner](/docs/_fern-img/9d14034ee9f81e2ebcf64e30371f914ef6f87a8da30ac250e702662f2e5e1cc1.webp)

För att verifiera från CLI:t (kräver `az extension add --name communication`) och hämta anslutningssträngen som bryggan använder som `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"
```

## Steg 2 — Distribuera bryggan

Bryggan är en liten Flask- + `flask-sock`-app som använder `azure-communication-callautomation`. Kärnan i det inkommande flödet:

**`bridge.py (utdrag)`**

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

På `/ws`-socketen vidarebefordrar du PCM16 i båda riktningarna: skicka vidare ACS-ramar med `AudioData` till ElevenLabs som `{"user_audio_chunk": "<base64>"}`, och skicka tillbaka agentens ljud som `{"Kind":"AudioData","AudioData":{"Data":"<base64>"},"StopAudio":null}`. Den första ramen ACS skickar är `AudioMetadata` (det förhandlade formatet) — logga den och ignorera den. ElevenLabs-sidan använder standardprotokollet för [agent-WebSocket](/docs/sv/eleven-agents/libraries/web-sockets).

> **Note**
>
> ACS använder olika JSON-storlek på bokstäverna per riktning: inkommande ramar som tjänsten **skickar** använder camelCase (`kind`,
> `audioData.data`), medan utgående ramar som den **förväntar sig** använder PascalCase (`Kind`, `AudioData.Data`,
> `StopAudio`). Håll de två formaten åtskilda — vidarebefordringen nedan speglar detta.

**`bridge.py — mediavidarebefordring`**

```python title="bridge.py — mediavidarebefordring"
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**
>
> Den här vidarebefordringen är avsiktligt minimal. För produktion bör du lägga till loggning, återanslutning och ordnad
> nedstängning. Den fullständiga meddelandereferensen finns i [WebSocket- dokumentationen](/docs/sv/eleven-agents/libraries/web-sockets).

> **Note**
>
> `EL_WS` ansluter till en **offentlig** agent. För en privat agent ska bryggan begära en kortlivad
> signerad URL på serversidan — `GET /v1/convai/conversation/get-signed-url?agent_id=...` med din API-
> nyckel — och ansluta till den returnerade URL:en i stället. För [data- residens](/docs/sv/overview/administration/data-residency) anger du `ELEVENLABS_ORIGIN` till din
> residensvärd (`wss://api.eu.residency.elevenlabs.io`, `.in.` eller `.sg.`) — begäranden om signerade URL:er
> använder motsvarande `https://`-värd.

Distribuera till Azure Container Apps och hämta den offentliga FQDN:en:

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

Ange sedan `BRIDGE_PUBLIC_HOST=$FQDN` och ACS-anslutningssträngen (som en hemlighet) i appen.

> **Warning**
>
> Med **Contributor** (inte Owner) kan `az containerapp up` inte skapa ACR-pullrollen för hanterad identitet.
> Aktivera registeradministratörsanvändaren (`az acr update --admin-enabled true`) och koppla den
> med `az containerapp registry set`, sedan `az containerapp update --image ...`.

## Steg 3 — Dirigera IncomingCall till bryggan

Skapa en Event Grid-prenumeration på ACS-resursen som skickar `IncomingCall` till bryggan. Bryggans valideringshandshake (ovan) slutför prenumerationen automatiskt.

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

Prenumerationen visas under ACS-resursens blad **Events**:

![Bladet Events för ACS-resursen listar webhook-prenumerationen acs-incomingcall, filtrerad
till
Microsoft.Communication.IncomingCall](/docs/_fern-img/f310aed9637e06b81906ca188350e3e721aaa5f4de69af2b3b6e7d157817a1ea.webp)

Ring numret — agenten svarar.

## Anslut till Teams

* **Direktuppringning:** En Teams-användare med Teams Phone + Calling Plan kan ringa ACS-numret som vilket externt nummer som helst.
* **Teams-resurskonto (TPE):** Koppla ett Teams-resurskonto till ACS-resursen med [Teams Phone Extensibility](https://learn.microsoft.com/en-us/azure/communication-services/quickstarts/tpe/teams-phone-extensibility-quickstart), så att samtal till resurskontot utlöser samma flöde `IncomingCall` → brygga.

## Avsluta samtalet

När agenten avslutar samtalet (till exempel med verktyget **End Call**) stänger ElevenLabs WebSocket-anslutningen. Lägg på ACS-samtalet så att uppringaren inte lämnas på en död linje:

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

## Varm överföring till en människa

ElevenLabs inbyggda överföringsverktyg gäller endast när ElevenLabs äger telefonin, så här utlöser agenten ett **anpassat klientverktyg** (till exempel `transfer_to_human`) som din brygga hanterar genom att **lägga till** människan i det aktiva samtalet med `add_participant` (varm överföring) i stället för en blind överföring:

```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 skickar callbacks för `AddParticipantSucceeded` / `AddParticipantFailed` till `/api/callbacks`. Returnera ett `client_tool_result` till agenten så att den kan säga sin överlämningsfras. Se [systemverktyg](/docs/sv/eleven-agents/customization/tools/system-tools/transfer-to-number) för konfiguration på agentsidan.

> **Tip**
>
> Ställ in överföringsskyddet direkt när verktyget utlöses (innan du anropar `add_participant`), annars kan en snabb stängning av EL-
> WebSocket ansluta till nedläggningen och avbryta samtalet innan människan hinner ansluta.

## Felsökning

#### Ingen IncomingCall når bryggan

Kontrollera att Event Grid-prenumerationen har etablerats (`provisioningState: Succeeded`) och att
bryggans `/api/incomingCall` returnerade valideringsekot. Kontrollera att numret har inkommande
samtalsfunktion och finns i samma ACS-resurs som prenumerationen. På prenumerationens
flik **Filters** måste händelsetyperna innehålla **Incoming Call**:

![Fliken Filters för händelseprenumerationen med händelsetypen filtrerad till Incoming
Call](/docs/_fern-img/c7026e86d96551d713e4fd7d5753062f643fcd6a67b455866bb8f1bbb6b57eb1.webp)

#### \`CreateCallFailed\` / \`AddParticipantFailed\` för ett internationellt nummer

ACS utgående samtal till vissa destinationer (t.ex. Indien) är begränsade eller intermittenta. Använd en destination som stöds,
eller lägg ett SIP-/Operator-nummer framför den mänskliga delen. Brygglogiken påverkas inte —
det är ett fel hos operatören på den utgående delen.

#### Ljudet är förvrängt eller har fel hastighet

Båda sidor måste använda PCM 16 kHz mono. Ställ in agentens in-/utdataformat på `pcm_16000`; bryggan
loggar det förhandlade formatet från `conversation_initiation_metadata`.

#### Kan inte köpa ett nummer / nummer är inte tillgängligt i mitt land

Köp av nummer kräver en betald prenumerationstyp (MCA/EA/PAYG). Om ACS inte erbjuder nummer i
ditt land använder du i stället en [SIP](/docs/sv/eleven-agents/phone-numbers/sip-trunking)-leverantör.

## Användbara länkar

* [Översikt över ACS Call Automation](https://learn.microsoft.com/en-us/azure/communication-services/concepts/call-automation/call-automation)
* [ACS-ljudstreaming](https://learn.microsoft.com/en-us/azure/communication-services/concepts/call-automation/audio-streaming-concept)
* [Åtgärder för samtalsstyrning (överföring / lägg till deltagare)](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)
* [Agent-WebSocket-protokoll](/docs/sv/eleven-agents/libraries/web-sockets)
* [ElevenLabs SIP trunking](/docs/sv/eleven-agents/phone-numbers/sip-trunking)