Talk to an AI Assistant over WebSocket
Real-time voice conversation with a Telnyx AI Assistant. The client streams PCM16 audio to Telnyx and receives lifecycle, transcription, and assistant-audio frames in return.
Add avatar=true to the connection URL to request an avatar session using the assistant’s saved avatar_settings. Omitting avatar or setting it to false keeps the connection voice-only, even when avatar settings are saved.
Authentication
Requires authentication via a Bearer token (Telnyx API v2 key).
{
"type": "session.update",
"session": {
"assistant": {
"dynamic_variables": {
"customer_name": "Ada",
"account_tier": "pro"
}
}
}
}{
"type": "input_audio_buffer.append",
"audio": "eW91ciBiYXNlNjQgZW5jb2RlZCBwY20xNiBhdWRpbw=="
}{
"type": "conversation.item.create",
"item": {
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": "What are your business hours?"
}
]
}
}{
"type": "response.cancel",
"response_id": "resp_9c1f4a2b"
}{
"type": "avatar.media.ready"
}{
"type": "avatar.ice_candidate",
"candidate": {
"candidate": "candidate:1 1 UDP 2122260223 192.0.2.1 50000 typ host",
"sdpMid": "0",
"sdpMLineIndex": 0
}
}{
"type": "avatar.webrtc.offer",
"offer": {
"type": "offer",
"sdp": "<local SDP offer>"
}
}{
"type": "session.created",
"session": {
"conversation_id": "3E6F995F-85F7-4705-9741-53B116D28237",
"assistant_id": "assistant-0f4e8b2a",
"audio": {
"input": {
"format": {
"type": "audio/pcm",
"rate": 16000
},
"turn_detection": {
"type": "server_vad"
}
},
"output": {
"format": {
"type": "audio/pcm",
"rate": 24000
}
}
}
}
}{
"type": "input_audio_buffer.speech_started"
}{
"type": "input_audio_buffer.speech_stopped"
}{
"type": "conversation.item.input_audio_transcription.completed",
"transcript": "What are your business hours?"
}{
"type": "response.created",
"response": {
"id": "resp_9c1f4a2b"
}
}{
"type": "response.output_audio.delta",
"response_id": "resp_9c1f4a2b",
"item_id": "item_5d2e7f10",
"delta": "eW91ciBiYXNlNjQgZW5jb2RlZCBwY20xNiBhdWRpbw=="
}{
"type": "response.output_audio_transcript.delta",
"response_id": "resp_9c1f4a2b",
"item_id": "item_5d2e7f10",
"delta": "We are open from 9am "
}{
"type": "response.output_audio.done",
"response_id": "resp_9c1f4a2b"
}{
"type": "response.done",
"response": {
"id": "resp_9c1f4a2b",
"status": "completed"
}
}{
"type": "conversation.item.created",
"item": {
"type": "function_call",
"call_id": "call_4b8e12a7",
"name": "get_weather",
"arguments": "{\"city\":\"Chicago\"}"
}
}{
"type": "response.tool_call.started",
"tool_call": {
"id": "call_7a3f21b8",
"name": "get_business_hours",
"arguments": "{\"location\":\"downtown\"}"
}
}{
"type": "response.tool_call.completed",
"tool_call": {
"id": "call_7a3f21b8",
"name": "get_business_hours"
},
"status": "success"
}{
"type": "error",
"error": {
"code": "invalid_audio",
"message": "Invalid base64-encoded audio payload"
}
}{
"type": "avatar.error",
"error": {
"code": "avatar_unavailable",
"message": "Avatar session is unavailable",
"reason": "avatar_preparation_timeout",
"retryable": true,
"fatal": true
}
}{
"type": "avatar.webrtc.answer",
"answer": {
"type": "answer",
"sdp": "<SDP answer>"
}
}{
"type": "avatar.session.ready",
"ice_configuration": {
"iceServers": []
},
"audio": {
"encoding": "pcm_s16le",
"sample_rate_hz": 24000,
"channels": 1
}
}Identifier of the AI Assistant to converse with.
Telnyx API v2 Bearer token authentication.
Query parameters passed when opening the WebSocket connection.
Client-to-Telnyx frame that supplies per-session dynamic variables. Optional for voice-only connections; required with avatar=true.
Send it as the first frame, before streaming audio; the conversation starts as soon as it arrives for voice-only sessions, or after a short timeout with no client-supplied variables if it is never sent in voice-only mode. Avatar sessions do not start without this frame. Variables are nested under session.assistant.dynamic_variables.
Only type is required: a bare frame with no session object is valid and starts voice-only conversations immediately with no client-supplied variables. With avatar=true, this frame begins avatar preparation; the conversation waits for avatar.media.ready after WebRTC negotiation and playback. Sending session.update after the session has started 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.
Client-to-Telnyx frame carrying a chunk of microphone audio.
The audio field is a base64-encoded PCM16 payload at the sample rate requested with the input_sample_rate query parameter. Frames larger than 1 MiB are rejected with a frame_too_large error.
Client-to-Telnyx frame that adds a conversation item. Three item types are supported: a user message with input_text content (injects a completed user turn, which the assistant answers exactly as it would a spoken one); a buffered user message with "buffer": true and input_image content (stores image frames, such as webcam or screen-share captures, for the assistant's vision tool without triggering a response); and a function_call_output that returns the result of a client-side tool function_call previously requested by Telnyx. There is no separate frame to request a response — the assistant owns turn-taking. An item of any other shape is rejected with an invalid_item error, and a malformed buffered item with an invalid_frame error.
Client-to-Telnyx frame that cancels the in-progress assistant response (client-initiated barge-in without audio).
Include response_id to target a specific response; omit it to cancel the current one. Cancelling flushes playback and Telnyx emits a response.done frame with status cancelled. Cancelling a stale or already-finished response is a no-op.
Client-to-Telnyx acknowledgment after applying the answer, establishing the peer connection, receiving live audio/video tracks and confirming the media receiver is ready. This acknowledgment starts the conversation and releases the greeting; session.created follows. Do not send microphone audio before session.created.
Client-to-Telnyx trickle ICE candidate. Send candidates after the offer; send candidate:null when gathering completes. Only the documented envelope and candidate fields are accepted. Server candidates are supplied in the SDP answer; there is no server ICE-candidate event.
Client-to-Telnyx SDP offer. Send exactly one offer after avatar.session.ready; audio and video transceivers must be recvonly. Renegotiation is not supported on the same connection.
Confirms the conversation has started and reports the negotiated audio formats. For voice-only connections this is the first session frame. With avatar=true it follows avatar signaling and the client avatar.media.ready acknowledgment.
Sent when server-side voice-activity detection determines the user has started speaking. Edge-triggered.
Sent when server-side voice-activity detection determines the user has stopped speaking. Edge-triggered, and never sent without a preceding speech-started.
Transcript of the audio the user streamed for the current turn.
Marks the start of an assistant turn. The response.id correlates the audio and transcript deltas that follow.
A chunk of the assistant''s synthesized speech, as base64-encoded PCM16 at the output rate announced in session.created.
A chunk of the text transcript of the assistant''s spoken response, streamed alongside the audio deltas.
Sent when the assistant has finished streaming audio for the turn.
Marks the end of an assistant turn, including its final status.
Telnyx-to-client frame requesting a client-side tool call.
When the assistant invokes a client_side_tool, Telnyx sends a function_call item that the client executes locally. The client returns 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.
Informational frame sent when the assistant invokes a server-side tool (for example a webhook or MCP integration). Telnyx executes tools server-side, so these frames are observational only: the client displays tool activity but does not execute or respond to the call.
Informational frame sent when a server-side tool invocation finishes, with its outcome.
Sent when an error occurs. Some errors are non-fatal and leave the session open; others close the connection after the frame is sent.
Avatar failure with code avatar_unavailable. Before session.created, error.fatal is true and Telnyx closes the WebSocket with code 1011; stop capture and close the peer connection. After session.created, error.fatal is false: the WebSocket remains open and the conversation continues audio-only. Stop WebRTC playback, close the peer connection, keep streaming microphone PCM and play subsequent response.output_audio.delta frames at the output format from session.created. Do not replay previously received audio. The reason is a sanitized cause identifier. Retryability and an optional delay describe retrying the avatar on a new connection, not renegotiating the current session. No provider credentials are exposed.
Telnyx-to-client SDP answer to the offer. The media receiver uses this remote description to establish synchronized audio and video reception.
Telnyx-to-client frame sent once the avatar is prepared, before session.created. The ice_configuration supplies ICE servers for the receive-only media connection. The audio field describes the avatar service audio format, not the microphone input rate.
Was this page helpful?
{
"type": "session.update",
"session": {
"assistant": {
"dynamic_variables": {
"customer_name": "Ada",
"account_tier": "pro"
}
}
}
}{
"type": "input_audio_buffer.append",
"audio": "eW91ciBiYXNlNjQgZW5jb2RlZCBwY20xNiBhdWRpbw=="
}{
"type": "conversation.item.create",
"item": {
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": "What are your business hours?"
}
]
}
}{
"type": "response.cancel",
"response_id": "resp_9c1f4a2b"
}{
"type": "avatar.media.ready"
}{
"type": "avatar.ice_candidate",
"candidate": {
"candidate": "candidate:1 1 UDP 2122260223 192.0.2.1 50000 typ host",
"sdpMid": "0",
"sdpMLineIndex": 0
}
}{
"type": "avatar.webrtc.offer",
"offer": {
"type": "offer",
"sdp": "<local SDP offer>"
}
}{
"type": "session.created",
"session": {
"conversation_id": "3E6F995F-85F7-4705-9741-53B116D28237",
"assistant_id": "assistant-0f4e8b2a",
"audio": {
"input": {
"format": {
"type": "audio/pcm",
"rate": 16000
},
"turn_detection": {
"type": "server_vad"
}
},
"output": {
"format": {
"type": "audio/pcm",
"rate": 24000
}
}
}
}
}{
"type": "input_audio_buffer.speech_started"
}{
"type": "input_audio_buffer.speech_stopped"
}{
"type": "conversation.item.input_audio_transcription.completed",
"transcript": "What are your business hours?"
}{
"type": "response.created",
"response": {
"id": "resp_9c1f4a2b"
}
}{
"type": "response.output_audio.delta",
"response_id": "resp_9c1f4a2b",
"item_id": "item_5d2e7f10",
"delta": "eW91ciBiYXNlNjQgZW5jb2RlZCBwY20xNiBhdWRpbw=="
}{
"type": "response.output_audio_transcript.delta",
"response_id": "resp_9c1f4a2b",
"item_id": "item_5d2e7f10",
"delta": "We are open from 9am "
}{
"type": "response.output_audio.done",
"response_id": "resp_9c1f4a2b"
}{
"type": "response.done",
"response": {
"id": "resp_9c1f4a2b",
"status": "completed"
}
}{
"type": "conversation.item.created",
"item": {
"type": "function_call",
"call_id": "call_4b8e12a7",
"name": "get_weather",
"arguments": "{\"city\":\"Chicago\"}"
}
}{
"type": "response.tool_call.started",
"tool_call": {
"id": "call_7a3f21b8",
"name": "get_business_hours",
"arguments": "{\"location\":\"downtown\"}"
}
}{
"type": "response.tool_call.completed",
"tool_call": {
"id": "call_7a3f21b8",
"name": "get_business_hours"
},
"status": "success"
}{
"type": "error",
"error": {
"code": "invalid_audio",
"message": "Invalid base64-encoded audio payload"
}
}{
"type": "avatar.error",
"error": {
"code": "avatar_unavailable",
"message": "Avatar session is unavailable",
"reason": "avatar_preparation_timeout",
"retryable": true,
"fatal": true
}
}{
"type": "avatar.webrtc.answer",
"answer": {
"type": "answer",
"sdp": "<SDP answer>"
}
}{
"type": "avatar.session.ready",
"ice_configuration": {
"iceServers": []
},
"audio": {
"encoding": "pcm_s16le",
"sample_rate_hz": 24000,
"channels": 1
}
}