> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getkarta.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Create a WebSocket session

> Connect to a voice agent using a WebSocket URL.

Your backend authenticates to Karta and creates a signed URL. Your browser connects using that URL.

## Create the URL

Use the ID of a live voice agent in your Karta account:

```bash theme={null}
curl --fail-with-body 'https://api.getkarta.ai/public/v1/websocket/sessions' \
  -H "Authorization: Bearer $KARTA_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "agent_id": "agent-123",
    "agent_override": {
      "language_config": {
        "primary_language": "hi"
      }
    },
    "llm_dynamic_variables": {
      "customer_name": "Aditi",
      "customer_reference": "customer-456"
    }
  }'
```

`agent_override` is best-effort. If its schema is malformed or unsupported, the session is still created with the stored agent configuration and the response includes a warning explaining why the override was ignored.

A successful request returns `201 Created`:

```json theme={null}
{
  "signed_url": "wss://voice.getkarta.ai/ws/?token={token}"
}
```

For example, an invalid override still returns `201 Created`:

```json theme={null}
{
  "signed_url": "wss://voice.getkarta.ai/ws/?token={token}",
  "warnings": [
    {
      "code": "agent_override_ignored",
      "path": "agent_override.language_config.primary_language",
      "message": "Expected a non-empty string. The stored agent configuration will be used."
    }
  ]
}
```

See [Create WebSocket session](https://docs.getkarta.ai/api-reference/realtime/create-websocket-session.md) for request fields, response fields, and HTTP errors.

## Expiry and reconnection

The signed URL:

* Expires five minutes after it is created.
* Can open only one WebSocket connection.
* Cannot be reused when reconnecting.
* After the connection opens successfully, the conversation can continue beyond five minutes.

Treat the signed URL as a temporary credential. Do not share it or write the complete URL to logs.

## Connect to the session

| Setting | Value |
| - | - |
| WebSocket base URL | `wss://voice.getkarta.ai` |
| WebSocket authentication | Single-use signed URL |

Open the `signed_url` returned by the session request. Use the URL exactly as returned.

### Audio format

Use the audio format below to send user audio and decode agent audio.

| Property | Required value |
| - | - |
| Format | `pcm_16000` |
| Encoding | Signed 16-bit little-endian PCM |
| Sample rate | 16 kHz |
| Channels | Mono |
| Message encoding | Base64 inside JSON |

### Connection flow

1. Open the signed WebSocket URL.
2. Send one conversation initiation request.
3. Wait for the conversation metadata response.
4. Send user audio requests.
5. Play agent audio responses in the order received.
6. Close the WebSocket to end the conversation, or handle a server-initiated
   conversation end.

## Client-to-server messages

### Conversation initiation

Send this once after the WebSocket opens:

```json theme={null}
{
  "type": "conversation_initiation_client_data",
  "agent_override": {
    "language_config": {
      "primary_language": "hi"
    }
  },
  "llm_dynamic_variables": {
    "session_id": "123456"
  }
}
```

| Field | Type | Required | Description |
| - | - | - | - |
| `type` | `string` | yes | Must be `conversation_initiation_client_data` |
| `agent_override` | `object` | no | Session-only agent configuration overrides |
| `llm_dynamic_variables` | `object<string, unknown>` | no | Initial LLM dynamic variables for the conversation |

`agent_override.language_config.primary_language` can select any language
already configured as the agent's primary or secondary language. The selected
language also determines the welcome message from `languageDefaultsConfig`.
When provided during both session creation and conversation initiation, the
session-creation override wins.
An invalid override in the conversation-initiation message is ignored and the
conversation continues with the stored agent configuration.

### Contextual update

Once the conversation is active, add or replace LLM dynamic variables without
interrupting it:

```json theme={null}
{
  "type": "contextual_update",
  "contextual_update_values": {
    "text": "User opened the pricing page",
    "llm_dynamic_variables": {
      "current_page": "pricing",
      "customer_tier": "enterprise"
    }
  }
}
```

| Field | Type | Required | Description |
| - | - | - | - |
| `type` | `string` | yes | Must be `contextual_update` |
| `contextual_update_values` | `object` | yes | Values to apply to the active conversation |
| `contextual_update_values.text` | `string` | conditional | Non-interrupting context for the agent |
| `contextual_update_values.llm_dynamic_variables` | `object<string, unknown>` | conditional | Variables to merge into the active conversation |

At least one value inside `contextual_update_values` is required. The update is
asynchronous and does not produce a direct server response.

### User audio

Send this message while the user is speaking:

```json theme={null}
{
  "type": "user_audio",
  "user_audio_chunk": "base64EncodedPcm16AudioData=="
}
```

| Field | Type | Required | Description |
| - | - | - | - |
| `type` | `string` | yes | Must be `user_audio` |
| `user_audio_chunk` | `string` | yes | Base64-encoded PCM16, 16 kHz, mono audio |

Use chunks between 20 and 100 milliseconds:

| Duration | Raw audio size |
| - | - |
| 20 ms | 640 bytes |
| 100 ms | 3,200 bytes |

## Server-to-client messages

### Conversation metadata

Sent after the conversation initiation request:

```json theme={null}
{
  "type": "conversation_initiation_metadata",
  "conversation_initiation_metadata_event": {
    "user_input_audio_format": "pcm_16000",
    "agent_output_audio_format": "pcm_16000"
  }
}
```

| Field | Type | Description |
| - | - | - |
| `type` | `string` | Always `conversation_initiation_metadata` |
| `conversation_initiation_metadata_event` | `object` | Audio settings for the conversation |
| `conversation_initiation_metadata_event.user_input_audio_format` | `string` | Always `pcm_16000` |
| `conversation_initiation_metadata_event.agent_output_audio_format` | `string` | Always `pcm_16000` |

### Agent audio

Sent whenever the agent produces audio:

```json theme={null}
{
  "type": "audio",
  "audio_event": {
    "audio_base_64": "base64EncodedPcm16AudioData=="
  }
}
```

| Field | Type | Description |
| - | - | - |
| `type` | `string` | Always `audio` |
| `audio_event` | `object` | Agent audio payload |
| `audio_event.audio_base_64` | `string` | Base64-encoded PCM16, 16 kHz, mono audio |

Decode `audio_event.audio_base_64`, queue each chunk, and play chunks in the
order received without overlapping them.

### Conversation ended

Sent after the final agent audio when Karta ends the underlying agent session,
including after an inactivity timeout:

```json theme={null}
{
  "type": "conversation_ended"
}
```

Stop audio capture and release call resources when this message arrives. Karta
then closes the WebSocket normally with code `1000`; clients may also close it
immediately after handling the message.

## WebSocket close codes

| Code | Meaning | Client action |
| - | - | - |
| `1000` | The conversation ended normally. | Stop audio and release call resources. |
| `4001` | The signed URL is missing, invalid, expired, or already used. | Create a new signed URL. |
| `1013` | Karta authentication is temporarily unavailable. | Wait briefly, create a new signed URL, and reconnect. |

Never reconnect with the same signed URL.

## Minimal JavaScript example

After implementing session creation on your application server, request the
signed URL from your server and connect:

```js theme={null}
const { signed_url: signedUrl } = await fetch("/api/karta-websocket-session")
  .then((response) => response.json());

const ws = new WebSocket(signedUrl);

ws.onopen = () => {
  ws.send(JSON.stringify({
    type: "conversation_initiation_client_data",
    llm_dynamic_variables: {
      session_id: "123456"
    }
  }));
};

ws.onmessage = (event) => {
  const message = JSON.parse(event.data);

  if (message.type === "conversation_initiation_metadata") {
    console.log(message.conversation_initiation_metadata_event);
    return;
  }

  if (message.type === "audio") {
    const base64Audio = message.audio_event.audio_base_64;
    // Decode and queue this PCM16, 16 kHz, mono audio for playback.
    return;
  }

  if (message.type === "conversation_ended") {
    stopAudioCapture();
    ws.close();
  }
};

function sendAudioChunk(base64Pcm16) {
  ws.send(JSON.stringify({
    type: "user_audio",
    user_audio_chunk: base64Pcm16
  }));
}

function updateContext(contextualUpdateValues) {
  ws.send(JSON.stringify({
    type: "contextual_update",
    contextual_update_values: contextualUpdateValues
  }));
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.