Skip to main content
Open a real-time voice conversation with a Telnyx AI Assistant over a single WebSocket. Your application streams microphone audio to Telnyx as base64-encoded PCM16 frames, and Telnyx streams back lifecycle events, speech-detection events, user and assistant transcripts, and the assistant’s synthesized speech. Everything about the assistant’s behavior — instructions, model, voice, language, tools, transcription and interruption settings — is configured on the assistant itself, in the Portal or through the Assistants API. The WebSocket carries audio, image frames for vision, conversation events, and per-conversation dynamic variables. There is no frame to request a response — the assistant owns turn-taking and answers automatically when the user finishes speaking.
For browser and mobile clients, and for avatar conversations, use WebRTC. It carries the same conversation events, with audio and video as media tracks.

How a conversation flows

  1. Open a WebSocket connection, optionally setting the audio format through query parameters.
  2. Optionally send a session.update frame as your first message to pass dynamic variables for the conversation.
  3. Telnyx authenticates the connection, starts a conversation with the requested assistant, and sends a session.created frame confirming the negotiated input and output audio formats.
  4. Stream microphone audio as input_audio_buffer.append frames.
  5. Telnyx runs server-side voice-activity detection (VAD) and sends input_audio_buffer.speech_started and input_audio_buffer.speech_stopped on speech edges, followed by the user’s transcript.
  6. The assistant responds with a response.created frame, a stream of audio and transcript deltas, then response.output_audio.done and response.done.
  7. Speaking while the assistant is talking triggers barge-in: Telnyx stops the response and starts a new turn.

Connect and authenticate

Authenticate the WebSocket handshake with a Telnyx API v2 key in the Authorization header:
Connect from a server-side WebSocket client that supports the Authorization header. Keep the API key on your server. The examples in this guide use Node.js and Python.
Set audio preferences at connect time through query parameters:
All frames in both directions are JSON text messages with a type field. Audio travels inside JSON frames as base64 — never as binary WebSocket frames.

Session lifecycle

The first session frame is session.created. It confirms the conversation has started and reports the negotiated audio formats — read the output rate from it before playing PCM audio:
Each WebSocket connection starts a new conversation with the assistant. Turn detection is always server_vad — there is no manual turn mode to configure.
Only assistants whose voice streams raw PCM are supported over this WebSocket. Assistants whose voice outputs a compressed format (for example mp3 or opus) are rejected with an unsupported_voice_output_format error.

Pass dynamic variables

If the assistant’s instructions, greeting, or tools use dynamic variables, supply the values for this conversation with a session.update frame. Send it as your first frame, immediately on open — before any audio, and without waiting for session.created:
Telnyx starts the conversation as soon as the frame arrives; variables are resolved before the assistant’s first word. Omitting session.update starts the conversation with no client-supplied variables after a short timeout. Client-supplied values override same-named assistant defaults or values returned by the configured dynamic-variable webhook. Unspecified variables retain those defaults or webhook values; an empty object does not clear them. Only type is required. A bare { "type": "session.update" } — or one whose session object carries no dynamic_variables — is valid: it skips that timeout and starts the conversation with no client-supplied variables.
Values are string key/value pairs, and they reach the assistant exactly like dynamic variables passed on the REST conversation API.
session.update configures nothing but dynamic variables, and only before the conversation starts. Sending it after session.created is rejected with a session_update_after_start error, and a dynamic_variables value that is not an object is rejected with invalid_session_update. The telnyx_ namespace is reserved for system variables, so telnyx_conversation_channel is set by Telnyx and cannot be overridden.

Handling audio

Stream microphone audio

Send audio as input_audio_buffer.append frames. The audio field is base64-encoded, raw little-endian PCM16 (16-bit signed, mono) at the sample rate you chose with input_sample_rate:
There is no commit step. Keep appending audio continuously — server-side VAD detects when the user starts and stops speaking and turns the buffered audio into conversation turns automatically. A few practical rules:
  • Stream at a real-time pace, in small chunks (for example 20–100 ms of audio per frame). The server enforces an ingress budget; sending far faster than real time fails with an ingress_budget_exceeded error.
  • Keep frames under 1 MiB. Larger frames are rejected with a frame_too_large error.
  • Wait for session.created before sending audio. session.update is the only frame sent before it.
If your capture pipeline produces float samples (for example the Web Audio API), convert them to PCM16 before encoding:

Speech detection and user transcripts

Telnyx runs voice-activity detection server-side and reports speech edges. Both events are edge-triggered — each is sent only when the speaking state actually changes, and speech_stopped is never sent without a preceding speech_started:
After the user’s turn ends, Telnyx sends the transcript of what they said:

Receive assistant audio

Each assistant turn arrives as an ordered sequence of frames, correlated by response_id:
For a natural conversation, play deltas as they arrive rather than waiting for response.done. Buffer the decoded PCM16 in a queue that your audio output drains at the output sample rate.

Interruption and barge-in

Barge-in is built in. If the user speaks while the assistant is talking, Telnyx detects it, stops generating the response, and starts a new turn — you don’t send anything to make that happen. The interrupted turn finishes with response.done and status: "cancelled". One thing remains your responsibility: audio you have already received and queued locally. When you see input_audio_buffer.speech_started during playback, flush your local playback queue so the assistant doesn’t keep talking out of your speakers over the user:
You can also interrupt programmatically — for example when the user taps a stop button — with response.cancel:
Include response_id to target a specific response, or omit it to cancel the current one. Cancelling flushes playback on the Telnyx side and is followed by a response.done frame with status cancelled. Cancelling a stale or already-finished response is a no-op. Interruption sensitivity is tuned on the assistant, not the socket — see Interruption Settings.

Send text instead of audio

Inject a completed user turn as text with conversation.item.create. The assistant answers it exactly as it would a spoken turn — including responding with audio:
There is no response.create frame — the assistant owns turn-taking and answers automatically. Client-side tool results use a function_call_output item (see Client-side tools). Unsupported item shapes are rejected with an invalid_item error.

Add vision with camera or screen-share frames

Beta: The vision tool lets an assistant inspect camera or screen-share snapshots during the same realtime conversation. For example, a user can show a product or share an error message while asking the assistant for help. Your client captures and sends individual images; a continuous video stream is not sent over this WebSocket. Vision does not require an avatar. Add { "type": "vision" } to the assistant’s tools. Either the base model or its enabled delegation model must support image input. S2S assistants, including GPT Live, can inspect buffered frames through a vision-capable delegation model. Configure the tool and model before connecting; see Vision model support. You can also start with the Avatar with vision template in the Portal for a configured assistant. Your WebSocket client still needs to capture and send image frames. After session.created, send snapshots as conversation.item.create frames with "buffer": true and only input_image content:
Use a JPEG, PNG, or WebP base64 data URL, or an HTTPS image URL. The base64 content above is abbreviated. Optional item.captured_at records the capture time in ISO 8601 format; if omitted, Telnyx uses the receipt time. Image items require buffer: true; send text prompts as separate items. Optional item.tags labels the image source, for example "tags": ["camera"] or "tags": ["screenshare"]. Tags apply to every image in the item. Send up to 5 tags, each 1–64 characters; untagged is reserved for frames sent without tags. Frames are buffered per tag. See Tag frames for tag matching and multiple-source examples. The vision function accepts action: "look" (the default) or action: "list", and an optional tag. A look without tag retrieves the most recent frame of any tag; a look with tag retrieves the most recent matching frame. Listing returns available tags and when each last received a frame, without showing an image. For an untagged demo, instruct the assistant to call vision with action: "look" and omit tag; it does not need to list tags first. Continue streaming microphone audio or send a text turn, such as “What does this error message mean?” Buffered images do not trigger responses. The assistant decides when to call vision and inspect recent frames while answering. Frames older than max_frame_age_secs (10 seconds by default, measured from receipt) are ignored. Keep sending frames for static content too; freshness applies per frame, including when selecting a tag. Buffered images are held in memory for the conversation and are not added to conversation history, transcripts, or webhooks. Downscale snapshots before sending, for example to 640×480 JPEG, and start with one frame every 1–5 seconds. Keep the entire JSON message below 1 MiB, including base64 overhead. Oversized messages return frame_too_large; malformed buffered items return invalid_frame without closing the socket. See the Vision guide for tool settings, client capture examples, other transports, and retention details, or the conversation WebSocket reference for the complete frame schema.

Tool calls

Assistants can use two kinds of tools during a realtime conversation. Both are configured on the assistant — see the Tools Library.

Server-side tools: observe

Webhook and MCP tools execute on Telnyx. The socket surfaces them as informational frames so you can show tool activity in your UI, but you don’t execute or respond to them:

Client-side tools: execute and respond

When the assistant invokes a client-side tool, Telnyx sends a conversation.item.created frame containing a function_call item. Run the tool locally, then return the result with a conversation.item.create frame carrying a function_call_output item that references the same call_id. The assistant continues once the result arrives, or after the tool times out.

Error handling

Errors arrive as error frames. Some errors are non-fatal and leave the session open; others close the connection after the frame is sent.
If the connection closes, reconnecting starts a new conversation — a fresh conversation_id is issued in session.created.

Learn more

  • Conversation WebSocket reference — The full frame-by-frame reference, under Assistants API → Conversation WebSocket in the sidebar
  • Vision — Configure the vision tool and stream camera or screen-share snapshots
  • Client-Side Tools — Configure tools that execute in your application
  • Transcription Settings and Interruption Settings — Tune how the assistant hears and yields
  • Voice Assistant Quickstart — Create and configure an assistant in the Portal