Voice Agent live sessions use JSON text frames over WebSocket. Input and output audio is Base64-encoded PCM16 inside JSON events.
Connect
Open a WebSocket connection to:
wss://api.zoom.us/v2/aiservices/voice-agent/live
Include the voice-agent.zoom.v1 subprotocol and a Zoom AI Services JWT in the Authorization: Bearer header of the upgrade request.
import WebSocket from 'ws';
const socket = new WebSocket(
'wss://api.zoom.us/v2/aiservices/voice-agent/live',
['voice-agent.zoom.v1'],
{
headers: {
Authorization: `Bearer ${process.env.ZOOM_ACCESS_TOKEN}`,
},
);
Configure the session
The server sends session.created after the connection opens. Your first client event must be session.update.
{
"type": "session.update",
"session": {
"type": "realtime",
"model": "gemma-4",
"output_modalities": ["audio"],
"instructions": "You are a concise and friendly support agent.",
"audio": {
"input": {
"format": {
"type": "audio/pcm",
"rate": 16000
},
"transcription": {
"model": "zoom_speech",
"language": "en"
},
"turn_detection": {
"type": "server_vad",
"threshold": 0.5,
"prefix_padding_ms": 300,
"silence_duration_ms": 500,
"create_response": true,
"interrupt_response": true
}
},
"output": {
"format": {
"type": "audio/pcm",
"rate": 16000
},
"voice": "jenna",
"speed": 1.0
}
},
"max_output_tokens": "inf"
}
}
The server confirms the effective configuration with session.updated.
Core settings
| Field | Default | Description |
|---|---|---|
model | gemma-4 | Conversational model used by the agent. |
output_modalities | ["audio"] | Set to ["audio"] for speech or ["text"] for text. The modes cannot be combined. |
instructions | General assistant instructions | Persistent instructions for the session. |
max_output_tokens | "inf" | Maximum tokens generated for each response. A response that reaches the limit ends with status incomplete. |
truncation | "auto" | How conversation history is trimmed. See Conversation history. |
audio.input.transcription.language | en | Language used to transcribe input speech. Only en is supported. |
audio.input.noise_reduction | null | Set to { "type": "zoom_adaptive_noise_reduction" } to reduce background noise in input audio, or null to disable it. |
audio.output.voice | jenna | Voice used for audio responses. See Available voices. |
audio.output.speed | 1.0 | Speaking-rate multiplier from 0.25 to 1.5. |
Audio formats
| Direction | Supported formats | Default rate |
|---|---|---|
| Input | PCM16 (audio/pcm), G.711 µ-law (audio/pcmu), or G.711 A-law (audio/pcma) | 16 kHz for PCM16 |
| Output | PCM16 (audio/pcm) at 16 kHz only | 16 kHz |
Set audio.output.format.rate to 16000. Other output sample rates are not supported.
Turn detection
Set audio.input.turn_detection.type to server_vad or semantic_vad, or set audio.input.turn_detection to null:
| Mode | Behavior |
|---|---|
server_vad | Ends a turn after a configured duration of silence. |
semantic_vad | Considers whether the utterance sounds complete before ending the turn. |
null | Disables automatic turn detection. The client commits audio and creates responses. |
For automatic modes, create_response controls whether the API starts an agent response after each turn. interrupt_response controls whether new user speech interrupts a response in progress.
For server_vad, configure these fields in audio.input.turn_detection:
| Field | Default | Range | Description |
|---|---|---|---|
threshold | 0.5 | 0.0–1.0 | Speech-detection sensitivity. |
prefix_padding_ms | 300 | 1–10000 ms | How much audio before detected speech to include in the turn. |
silence_duration_ms | 500 | 1–10000 ms | How long to wait in silence before ending the turn. |
create_response | true | true or false | Whether to start an agent response automatically when a turn ends. |
interrupt_response | true | true or false | Whether new user speech interrupts a response in progress. |
idle_timeout_ms | null | 5000–30000 ms | If set, the server sends input_audio_buffer.timeout_triggered when the user stays silent this long after the agent speaks. |
semantic_vad accepts all of the server_vad fields above, plus:
| Field | Default | Range | Description |
|---|---|---|---|
timeout_ms | 2000 | Greater than 0 ms | How long to wait on an utterance that sounds unfinished before ending the turn anyway. |
two_pass_threshold | 0.2 | Greater than 0.0 and less than 1.0 | Confidence required before an utterance is treated as complete. |
ignore_backchannel | false | true or false | Whether to ignore short acknowledgments such as "uh-huh" or "yeah" instead of treating them as user turns. |
Conversation history
Set truncation to control how history is trimmed as it approaches the model's token limit. Truncation removes the oldest messages first.
| Value | Behavior |
|---|---|
"auto" | Trims history automatically as it approaches the limit. |
"disabled" | Never trims history. Requests can fail when history exceeds the limit. |
{ "type": "retention_ratio", "retention_ratio": 0.5 } | Trims history back to this fraction of the allowed input tokens, from 0.0 to 1.0. |
Stream input audio
Send audio continuously, including silence, in input_audio_buffer.append events.
{
"type": "input_audio_buffer.append",
"audio": "<base64-encoded audio>"
}
In manual turn mode, end a user turn by sending these events in order. If another response is active, wait for its response.done before sending response.create:
{
"type": "input_audio_buffer.commit"
}
{
"type": "response.create"
}
Use input_audio_buffer.clear to discard audio that has not been committed.
Start another response
Only one response can be active at a time. Before sending response.create for another response, wait for response.done for the active response. If you send response.create while a response is still active, the server returns a nonfatal error indicating that there is an active response. Wait for response.done, then retry.
Receiving response.output_audio.done or response.function_call_arguments.done does not mean the response has ended. Use response.done to determine when another response can start.
Handle server events
Voice Agent emits events as input is detected and responses are generated.
| Event | Description |
|---|---|
session.created | Connection established with the default session configuration. |
session.updated | A session.update was applied. |
input_audio_buffer.speech_started | Speech was detected in the input stream. |
input_audio_buffer.speech_stopped | The detected speech turn ended. |
input_audio_buffer.timeout_triggered | The user stayed silent for idle_timeout_ms after the agent spoke. |
conversation.item.input_audio_transcription.completed | The user's input transcript is ready. |
response.created | An agent response started. |
response.output_item.added | A response output item was added, including its item.id. |
response.output_audio.delta | A Base64-encoded chunk of agent speech. |
response.output_audio.done | All audio for the response output has been sent. |
response.output_audio_transcript.delta | An incremental transcript of agent speech. |
response.output_text.delta | Incremental output for a text response. |
response.function_call_arguments.done | A function call and its complete arguments are ready. |
response.done | The response ended with status completed, cancelled, failed, or incomplete (token limit reached). |
error | A request validation or service error occurred. |
Server events are wrapped in an envelope with a type and a payload object. payload contains the complete event, including its type, so read the event from payload. The event examples on this page show the event inside payload.
socket.on('message', (data) => {
const envelope = JSON.parse(data.toString());
const event = envelope.payload ?? envelope;
if (event.type === 'response.output_audio.delta') {
playPcm16(Buffer.from(event.delta, 'base64'));
}
if (event.type === 'error') {
console.error(event.error);
}
});
Cancel or truncate a response
Use the server-generated IDs from response events:
response.created.response.ididentifies the response. Pass it asresponse_idinresponse.cancel.response.output_item.added.item.ididentifies an output item. Audio events also includeitem_id; use the ID of the item whose audio you are truncating inconversation.item.truncate.
When a user interrupts audio that is already playing:
- Send
response.cancelto stop generation. - Stop local playback and record the last audio offset the user heard.
- Send
conversation.item.truncateso the conversation history matches the played audio.
{
"type": "response.cancel",
"response_id": "resp_abc123"
}
{
"type": "conversation.item.truncate",
"item_id": "item_abc123",
"content_index": 0,
"audio_end_ms": 1500
}
Use function calling
Add function definitions to session.tools and set tool_choice to auto (the model decides), none, required (call at least one tool), or a specific function, such as { "type": "function", "name": "get_order_status" }.
{
"type": "session.update",
"session": {
"tools": [
{
"type": "function",
"name": "get_order_status",
"description": "Get the current status of an order.",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string"
}
},
"required": ["order_id"]
}
}
],
"tool_choice": "auto"
}
}
The server sends response.function_call_arguments.done when the function's arguments are complete:
{
"type": "response.function_call_arguments.done",
"event_id": "event_abc123",
"response_id": "resp_abc123",
"item_id": "item_abc123",
"output_index": 0,
"call_id": "call_abc123",
"name": "get_order_status",
"arguments": "{\"order_id\":\"123\"}"
}
Parse arguments as JSON and execute the named function in your application. Return its result as a function_call_output item using the event's call_id. The call_id links the result to the function call; it is not the response or item ID.
{
"type": "conversation.item.create",
"item": {
"type": "function_call_output",
"call_id": "call_abc123",
"output": "{\"status\":\"shipped\",\"estimated_delivery\":\"Friday\"}"
}
}
Wait for response.done for the response that requested the function before sending response.create to request the agent's next response.
{
"type": "response.create"
}
Available voices
Set audio.output.voice to one of these ten voice IDs. The default is jenna.
| Voice | Voice ID |
|---|---|
| Alex | alex |
| Ariel | ariel |
| Ava | ava |
| Brooke | brooke |
| Hazel | hazel |
| Jenna | jenna |
| Mark | mark |
| Reid | reid |
| Scott | scott |
| Tony | tony |
Handle errors
The error.type field is invalid_request_error for recoverable client errors or server_error for service failures. The error.message field describes the failure.
{
"type": "error",
"event_id": "event_abc123",
"error": {
"type": "invalid_request_error",
"message": "invalid client event: session.audio.output.format.rate: Input should be 16000"
}
}
Invalid settings, unknown voices, unsupported formats, and missing fields produce an error event; correct the request before retrying it. A rejected session.update does not disconnect the client. Correct the configuration and send session.update again on the same connection; a successful update returns session.updated.
An error indicating that there is an active response is also nonfatal. Wait for that response's response.done before retrying response.create.