Skip to main content
Stream audio between your application and your voice agent via WebSocket. Use this for web apps, mobile apps, or to bridge your own telephony provider.

Quick start

Get an access token from the /access-token endpoint. See Authenticating Client Apps for details.

Connection

Connect to the WebSocket endpoint:
Headers:

Protocol Overview

The WebSocket connection uses JSON messages for control events and base64-encoded audio for media. New server event types may be added over time. To keep your integration forward-compatible, ignore any event you do not recognize instead of treating it as an error. A session moves through four phases:
  1. Start. Send a start event with your audio config.
  2. Ready. The server responds with ack once the agent pipeline is ready.
  3. Converse. Stream user audio as media_input and play agent audio from media_output. In parallel, the server pushes conversation events (turn_started, turn_output_text_delta, turn_ended) that describe the dialogue as it happens.
  4. End. Either side can close the connection. The server’s close reason tells you why the call ended (see Connection Management).
After the call, you can fetch its full record from the calls API using the call_id from the ack.

Client events

Starting the Session (start)

Initializes the audio stream configuration.
  • config overrides your agent’s default input audio settings
  • stream_id is optional. If not provided, the server generates one and returns it in the ack event
This must be the first message sent.
Fields:
  • stream_id (optional): Stream identifier. If not provided, server generates one
  • config.input_format: Audio format for client audio input (mulaw_8000, pcm_16000, pcm_24000, pcm_44100)
  • config.output_audio_delivery (optional): How the server delivers agent audio. speaking_pace (default) paces audio to playback speed; as_available sends it as fast as the model produces it, so a client that buffers locally can play it back on its own clock for lower latency. Not supported when the agent uses background audio.
  • config.voice_id (optional): Override the agent’s default TTS voice
  • agent (optional): Allows configuring individual agent calls via API and previewing changes in introduction or prompt without publishing to production
  • metadata (optional): Custom metadata object. These will be passed through to the agent code, but there are some special fields you can use as well:
    • to (optional): Destination identifier for call routing (defaults to agent ID)
    • from (optional): Source identifier for the call (defaults to “websocket”)

Streaming User Audio (media_input)

Audio data sent from the client to the server. payload audio data should be base64 encoded.
Fields:
  • stream_id: Unique identifier for the Stream from the ack response
  • media.payload: Base64-encoded audio data in the format specified in the start event

Sending DTMF Tones (dtmf)

Sends DTMF (dual-tone multi-frequency) tones.
Fields:
  • stream_id: Stream identifier
  • dtmf: DTMF digit (0-9, *, #)

Sending Custom Metadata (custom)

Sends custom metadata to the agent.
Fields:
  • stream_id: Stream identifier
  • metadata: Object containing key-value pairs of custom data

Server events

Session Ready (ack)

Sent in response to start once the agent pipeline is ready to receive audio. Confirms the stream configuration and returns the server-generated stream_id if one wasn’t provided in the start event.
Fields:
  • call_id: Identifier of the call record created for this session. Use it with the calls API to fetch the recording and transcript after the call

Receiving Agent Audio (media_output)

The agent’s speech. payload is base64-encoded audio in the format set by config.input_format in the start event.

Conversation Events

Three events describe the conversation itself: turn_started, turn_output_text_delta, and turn_ended. Every turn gets an id from a single counter shared by both roles, starting at 1 and strictly increasing over the call, so id alone identifies a turn. The events for a single turn always arrive in order: turn_started, then any turn_output_text_delta events, then turn_ended. The server pushes them as the conversation happens, independent of audio delivery timing. Use these events to drive transcripts and speaking indicators, and see Build a live transcript for a code example.

When a Turn Begins (turn_started)

Sent when the user starts speaking or the agent starts responding.
Fields:
  • stream_id: Stream identifier
  • turn_started.id: Turn index, unique across the call
  • turn_started.role: user or assistant
  • turn_started.start_timestamp: Seconds since the start of the call (approximately when the client received ack)

Streaming Agent Text (turn_output_text_delta)

Sent for each word the agent speaks, as it is spoken. Use it to render the agent’s side of the transcript, word by word.
Fields:
  • stream_id: Stream identifier
  • turn_output_text_delta.id: Matches the id of the turn the text belongs to
  • turn_output_text_delta.role: Always assistant
  • turn_output_text_delta.text: The exact substring to append. Separator spaces are included, so build the running text with buffer[id] += text
These deltas cover the agent’s speech only. The user’s speech is not streamed word by word. It arrives as finalized text when the user’s turn ends, in the turn_ended event.

When a Turn Ends (turn_ended)

Sent when a turn finishes. It carries the complete, final text for the turn, which is the version you should store and display once the turn is over.
Fields:
  • stream_id: Stream identifier
  • turn_ended.id: Matches the id of the corresponding turn_started event
  • turn_ended.role: user or assistant
  • turn_ended.text: The finalized text of the turn. For a user turn, this is the transcribed speech. For an assistant turn, it is the text the agent actually spoke
  • turn_ended.was_interrupted: For assistant turns, true when the user interrupted the turn. For user turns, true only when the call ended mid-turn
  • turn_ended.start_timestamp, turn_ended.end_timestamp: Seconds since the start of the call
  • turn_ended.tool_calls: Tool calls made during the turn. Each entry has name, arguments, and optionally result and id
If the agent hangs up mid-turn, the connection closes without a final turn_ended. Treat the connection close as ending any open turn.

Handling Interruptions (clear)

Indicates the agent wants to clear/interrupt the current audio stream.

Call Transfers (transfer_call)

Indicates the agent wants to transfer the call to a phone number. The client is responsible for initiating the transfer on its telephony side.
Fields:
  • stream_id: Stream identifier
  • transfer.target_phone_number: E.164 phone number to transfer the call to

Build a live transcript

A live transcript shows the conversation on a UI as it happens, with the agent’s words appearing one at a time like live captions.
The two roles differ in how their text arrives, so render them differently. For a user turn, show a speaking indicator when the turn starts and fill in the text once turn_ended arrives. For an agent turn, show each word as its delta arrives so the response builds up at speaking pace.

Connection Management

Inactivity Timeout

The server closes idle connections after 180 seconds. Any client message resets the timer:
  • Application messages (media_input, dtmf, custom events)
  • Standard WebSocket ping frames
  • Any other valid WebSocket message
When the timeout occurs, the connection is closed with:
  • Code: 1000 (Normal Closure)
  • Reason: "connection idle timeout"

Ping/Pong Keepalive

To prevent inactivity timeouts during periods of silence, use standard WebSocket ping frames for periodic keepalive:
The server automatically responds to ping frames with pong frames and resets the inactivity timer upon receiving any message.

Connection Close

The connection can be closed by either the client or server using WebSocket close frames. Client-initiated close:
Server-initiated close: When the agent ends the call, the server closes the connection with:
  • Code: 1000 (Normal Closure)
  • Reason: "call ended by agent" or "call ended by agent, reason: {specific_reason}" if additional context is available

Best Practices

  1. Send start first — The connection closes if any other event is sent before start.
  2. Choose the right audio format — Match the format to your source: mulaw_8000 for telephony, pcm_44100 for web clients.
  3. Handle closes cleanly — Always capture close codes and reasons for debugging and recovery.
  4. Keep the connection alive — Send WebSocket ping frames every 60–90 seconds to avoid the 180-second inactivity timeout.
  5. Manage stream IDs — Provide your own stream_id values to improve observability across systems.
  6. Recover from idle timeouts — On 1000 / connection idle timeout, reconnect and resend a start event.