> This is a page from the ElevenLabs documentation. For an index of the documentation, fetch https://elevenlabs.io/docs/llms.txt.

# Voximplant

> **Warning**
>
> The Voximplant integration is in limited availability and is enabled per workspace. Contact your
> ElevenLabs representative before routing customer traffic.

## Overview

The Voximplant integration connects a call in a VoxEngine scenario directly to an agent in
ElevenAgents over a WebSocket, using Voximplant Media Streams. Voximplant owns telephony:
it dials or answers the call and decides what happens afterwards. ElevenAgents owns the
conversation. No SIP trunk or middleware is required.

Inbound and outbound calls work the same way: once the call is connected, the scenario opens one
WebSocket per call to ElevenLabs. When the conversation ends, ElevenLabs sends the scenario a single
decision event that says whether to hand the caller to a human, hang up, or treat the call as a
timeout.

## How the integration works

1. Your VoxEngine scenario answers an inbound call or places an outbound one.
2. The scenario opens a WebSocket to the ElevenLabs endpoint for your agent, authenticating with an
   ElevenLabs API key as a bearer token.
3. The scenario starts the media stream with `call.sendMediaTo()`, passing any `customParameters`.
   ElevenLabs turns each parameter into a dynamic variable and replies with its own `start`.
4. Voximplant and ElevenLabs exchange 16-bit linear PCM audio at 8 kHz in `media` messages.
5. When the conversation ends, ElevenLabs sends the rest of the agent's last phrase, one
   `elevenlabs-decision` custom event, and a `stop` message. The scenario then closes the WebSocket
   and acts on the decision.

## Requirements

Before you begin, ensure you have:

1. A Voximplant account with an application, a phone number or outbound calling, and permission to
   edit scenarios.
2. An ElevenLabs workspace with the Voximplant integration enabled.
3. An ElevenLabs agent and a dedicated API key.

## Configure ElevenLabs

#### Create or select an agent

Create the agent in ElevenAgents. Give it a first message if it should speak as soon as the call
connects. Audio formats are set by the integration, so the agent's configured formats are not used
for Voximplant calls.

#### Enable End call

In **Agent → Tools → System tools**, enable **End call** so the agent can finish the conversation.
The reason the agent gives when it ends the call is sent to your scenario with the decision.

#### Add a Voximplant transfer rule (optional)

To let the agent hand the caller to a human, give it the **Transfer to number** system tool with a
transfer rule whose provider configuration is `voximplant`. The rule has no destination: the
conversation ends with a `transfer-to-agent` decision and your scenario routes the caller. Transfer
rules are configured through the API. Add the tool with a `PATCH` on the agent:

```bash
curl -X PATCH "https://api.elevenlabs.io/v1/convai/agents/agent_7101k5zvyjhmfg983brhmhkd98n6" \
  -H "xi-api-key: $ELEVENLABS_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "conversation_config": {"agent": {"prompt": {"built_in_tools": {
      "end_call": {"type": "system", "name": "end_call", "description": "",
                   "params": {"system_tool_type": "end_call"}},
      "transfer_to_number": {
        "type": "system", "name": "transfer_to_number", "description": "",
        "params": {
          "system_tool_type": "transfer_to_number",
          "transfers": [],
          "transfer_rules": [{
            "condition": "the caller asks to speak with a human",
            "provider_configs": [
              {"type": "voximplant", "config": {"type": "voximplant_handoff"}}
            ]
          }]
        }
      }
    }}}}
  }'
```

Send the agent's full `built_in_tools` object, including tools it already has such as `end_call`.
The option the agent echoes back is the fixed token `voximplant`, so one Voximplant rule per tool is
enough. Phone numbers and SIP URIs configured for other providers are not offered on Voximplant
calls.

> **Note**
>
> Transfer rules are an API-managed setting. Configure and update the **Transfer to number** tool of
> an agent that uses them through the API, as above; the dashboard's tool editor works with the
> per-number transfer list.

#### Create a dedicated API key

Create an API key in the same workspace as the agent and scope it to ElevenAgents. To accept
connections only from Voximplant, restrict the key's allowed IP addresses to Voximplant's media
servers, which Voximplant lists at
`https://api.voximplant.com/getMediaResources?with_mediaservers`.

#### Note the WebSocket URL

The scenario connects to a URL that contains your agent ID:

| Environment    | URL                                                                                                               |
| -------------- | ----------------------------------------------------------------------------------------------------------------- |
| Default        | `wss://api.elevenlabs.io/v1/convai/conversation/voximplant/agent_7101k5zvyjhmfg983brhmhkd98n6`                    |
| Data residency | `wss://api.<region>.residency.elevenlabs.io/v1/convai/conversation/voximplant/agent_7101k5zvyjhmfg983brhmhkd98n6` |

To pin a branch of the agent, add `?branch_id=<branch_id>`.

> **Info**
>
> If your ElevenLabs account is on an isolated residency environment, replace `<region>` with your
> region code. See [data residency](/docs/overview/administration/data-residency) for the available
> regions.

## Set up Voximplant

#### Create an application

In the [Voximplant control panel](https://manage.voximplant.com/applications), open **Applications**
and click **Create application**. Give it a name, for example `elevenlabs-agent`, and click
**Create**.

![Voximplant: Create application](/docs/_fern-files/_img/29af341fa0a32eaaab444ade8305208121487413c5868597386a973b14fe4fe7.webp)

#### Store the API key as a secret

In the application, open **Secrets** and click **Add secret**. Name it `ELEVENLABS_API_KEY`, paste
the API key you created for the agent's workspace into **Value**, and click **Add secret**. The
scenario reads it with `VoxEngine.getSecretValue()`, so the key never appears in the scenario code.

![Voximplant: Add secret](/docs/_fern-files/_img/ecf8be823bbc15b263e3f4838684a78de3a3594704be0055b443d15ab93e1485.webp)

#### Create the scenario

Open **Scenarios**, click **Create scenario**, name it `elevenlabs_agent`, paste the code below,
replace the agent ID in `ELEVENLABS_URL`, and click **Save**. It answers an inbound call, streams
it to the agent, and acts on the decision when the conversation ends. For an outbound call, open
the WebSocket in the `CallEvents.Connected` handler of the call you placed.

```javascript maxLines=0
const ELEVENLABS_URL =
  "wss://api.elevenlabs.io/v1/convai/conversation/voximplant/agent_7101k5zvyjhmfg983brhmhkd98n6";

VoxEngine.addEventListener(AppEvents.CallAlerting, ({ call }) => {
  call.answer();
  let decision = null;

  const ws = VoxEngine.createWebSocket(ELEVENLABS_URL, {
    headers: [
      {
        name: "Authorization",
        value: `Bearer ${VoxEngine.getSecretValue("ELEVENLABS_API_KEY")}`,
      },
    ],
  });

  ws.addEventListener(WebSocketEvents.OPEN, () => {
    call.sendMediaTo(ws, {
      encoding: WebSocketAudioEncoding.PCM16_8KHZ,
      customParameters: { customer_name: "Ana", task_id: "task-7" },
    });
    ws.sendMediaTo(call);
  });

  ws.addEventListener(WebSocketEvents.MESSAGE, ({ text }) => {
    const message = JSON.parse(text);
    if (message.customEvent === "elevenlabs-decision") {
      decision = message.payload;
      Logger.write(`ElevenLabs decision: ${JSON.stringify(decision)}`);
    }
  });

  ws.addEventListener(WebSocketEvents.CLOSE, (e) => {
    Logger.write(`ElevenLabs socket closed: code=${e.code} reason=${e.reason}`);
    if (decision?.action === "transfer-to-agent") {
      // Route the caller to a human here, for example with VoxEngine.callPSTN().
      call.say("Connecting you to an agent.", { language: VoiceList.Amazon.en_US_Joanna });
      call.addEventListener(CallEvents.PlaybackFinished, () => call.hangup());
      return;
    }
    call.hangup();
  });

  call.addEventListener(CallEvents.Disconnected, () => {
    ws.close();
    VoxEngine.terminate();
  });
});
```

![Voximplant: the scenario in the editor](/docs/_fern-files/_img/d2e26165f57cae40fc01e8de28052fef09b2f98bd90c0e1a8779423715f61e2e.webp)

#### Route calls to the scenario

Open **Routing**, click **Create rule**, give it a name, and assign the `elevenlabs_agent`
scenario. The default pattern `.*` sends every call to the application through the scenario;
narrow it to a phone number if the application handles other calls too. To route a phone number,
attach it to the application under **Numbers**.

![Voximplant: Create rule](/docs/_fern-files/_img/bc6830b7dcd66dc0b59595456b1197bdcbb9174f15778da7ba9655ba4b93936e.webp)

#### Make a test call

You can call the application from the browser without a phone number:

1. Open **Users**, click **Create user**, and create a user with a password.

   ![Voximplant: New user](/docs/_fern-files/_img/4754c9234588fd0caa336e1168e207b265557dc4e6a12b1cf8bced1275b8bae6.webp)

2. In **Routing**, click **Test tools**, then **Softphone**. Sign in as that user, allow
   microphone access, enter any destination, and call. The agent answers with its first message.

3. When the call ends, open **Call history**. Each call lists a WebSocket leg next to the call
   itself. Click **View log** to see the session.

   ![Voximplant: Call history](/docs/_fern-files/_img/4e2416ad3587bb320300173cf679f7c5d7a5e545550b0a647062a79866dac40c.webp)

   The log shows the decision ElevenLabs sent, the WebSocket close, and what the scenario did
   next. In this example the caller asked for a person, so the decision is `transfer-to-agent`
   with the reason the agent gave.

   ![Voximplant: Session log with the elevenlabs-decision
   event](/docs/_fern-files/_img/f8b4602fa02cfd3c417557377aaa5dd66e2e88a67e7adf4cb037ccae1840f45c.webp)

> **Warning**
>
> Act on the `elevenlabs-decision` event, not on `MEDIA_ENDED`. Voximplant fires `MEDIA_ENDED` after
> about a second without audio from ElevenLabs, which can happen in the middle of a conversation.

## Dynamic variables

Every key in `customParameters` becomes a dynamic variable with the same name. Values that are not
strings are converted to text, for example `true` becomes `"true"`. `customParameters` can be an
object or a JSON-encoded string. Keys that start with `system__` or `integration__` are reserved and
are dropped.

## Decision events

When the conversation ends, ElevenLabs sends exactly one decision before its `stop` message:

```json
{ "customEvent": "elevenlabs-decision", "payload": { "action": "terminate", "reason": "under_18" } }
```

| `action`            | When it is sent                                                                                                                                                                    | `reason`                                                        |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| `transfer-to-agent` | The agent called **Transfer to number** with a Voximplant transfer rule.                                                                                                           | The reason the agent gave for the transfer.                     |
| `timeout`           | The conversation timed out on silence or after the agent's last message, or the agent ended the call after 15 seconds of caller silence, not counting time the agent was speaking. | The timeout message, or the reason the agent gave.              |
| `terminate`         | The agent ended the call with **End call**, or the conversation ended normally.                                                                                                    | The reason the agent gave with **End call**, when there is one. |
| `unexpected`        | The conversation ended because of an error.                                                                                                                                        | A description of the error.                                     |

If the scenario sends `stop` or closes the WebSocket first, for example because the caller hung up,
ElevenLabs sends no decision.

## Audio

Voximplant and ElevenLabs exchange 16-bit mono linear PCM at 8 kHz in both directions. ElevenLabs
paces the agent's audio close to real time, so when the caller interrupts the agent, at most about
0.4 seconds of the interrupted response is still played.

## Authentication errors

ElevenLabs closes the WebSocket with code `1008` and reason `bad auth token` when the
`Authorization` header is missing or malformed, the API key is invalid or not allowed from the
connecting IP address, the key belongs to another workspace, or the key's user cannot access an
agent that has authentication enabled.

## Limitations and unsupported features

* Client tools and the **Play keypad touch tone** system tool are not supported. **Transfer to
  number** works only through a Voximplant transfer rule; the scenario decides where the caller
  goes.
* Configuration overrides such as a different first message cannot be passed from the scenario;
  only dynamic variables can.
* Data collection results are not sent to the scenario. Use the
  [post-call webhook](/docs/eleven-agents/workflows/post-call-webhooks) for them.