session.update to configure, input.audio to stream mic audio, tool.result to respond to tool calls, and session.end to cleanly end the call. The server streams everything else back, including session.ended on every clean teardown.
Event flow
A typical voice agent session moves through the events in this order:Client → Server
input.audio
Stream PCM16 audio to the agent.
See Audio format for the full format specification.
session.update
Configure the session. Send immediately on WebSocket connect (before session.ready). Can also be sent mid-conversation to update most fields. See Mutability after session.ready for which fields can change once the session is established.
session.ready, only a subset of fields can be changed; changing greeting, session.output.voice, or session.output.format raises immutable_field. session.output.volume is mutable mid-session.
session.resume
Reconnect to an existing session using the session_id from a previous session.ready. Preserves conversation context across dropped connections.
Sessions are preserved for 30 seconds after every disconnection before expiring. If the session has expired, the server returns a
session.error with code session_not_found or session_forbidden. Start a fresh connection without session.resume.session_id from session.ready on the first connection, then send session.resume as the first message when reconnecting:
session.end
End the session cleanly. Send this when the call is over and you don’t intend to reconnect. The server emits a final session.ended and closes the WebSocket; the session_id is dead immediately and cannot be resumed.
- The server emits
session.ended. - The server closes the WebSocket.
- The
session_idis dead. Sendingsession.resumewith it returnssession_not_found.
session.resume from a new connection, and that 30-second window is billable. session.end short-circuits the grace window and stops billing immediately.
tool.result
Send a tool result back to the agent. Send this when reply.done is the latest event you’ve received (and nothing has happened since). The simplest pattern is to accumulate on tool.call and drain inside the reply.done handler. See Client-side tools.
reply.create
Ask the agent to generate a reply right now, optionally with custom instructions. Useful for delivering status updates during long-running hold-mode tool calls, or any time you want the agent to speak without a user utterance triggering it.
The agent generates a normal reply (
reply.started → reply.audio → transcript.agent → reply.done) using the provided instructions on top of the existing system prompt and conversation history.
Server → Client
session.ready
Session is established and ready to receive audio. Save session_id for reconnection. Start sending input.audio only after this event.
session.updated
Sent after session.update is applied successfully.
session.ended
Final event emitted on every clean teardown, right before the server closes the WebSocket. Always handle it.
When you’ll see it:
- You sent
session.end. - The session hit
max_session_duration_seconds. - The server hit an unrecoverable error.
- You disconnected and the 30-second grace window expired.
- Got
session.endedbefore the WebSocket closed: clean server-initiated close. Done. Don’t try to resume. - WebSocket closed without
session.ended: probably a network drop. If you want to keep going, reconnect and callsession.resumewith your savedsession_idwithin 30 seconds.
input.speech.started
Turn detection determined the user has started speaking.
input.speech.stopped
Turn detection determined the user has stopped speaking.
transcript.user.delta
Partial transcript of what the user is saying, updating in real-time.
Live user transcripts pause while a
hold-mode tool is in flight and resume once the hold ends. Anything the user said during the hold is preserved in the conversation context.transcript.user
Final transcript of the user’s utterance.
reply.started
Agent has begun generating a response.
reply.audio
A chunk of the agent’s spoken response as base64 PCM16. Decode and play immediately.
transcript.agent
Full text of the agent’s response, sent after all audio for the response has been delivered. If the agent was interrupted, interrupted is true and text contains only what was actually spoken before the interruption.
reply.done
Agent has finished speaking. The optional status field indicates why the reply ended.
tool.call
Agent wants to call a registered tool. arguments is a dict, ready to use directly as-is.
See Tool calling for the full pattern.
session.error
Session or protocol error. The payload always includes type, timestamp, code, and message. Some errors (like session.update validation failures) also include a param field naming the offending field.
session.ready. The WebSocket closes after these with the indicated close code.
Session resume errors
Sent when
session.resume fails. The WebSocket closes after these.
Agent startup errors
Sent after the WebSocket is accepted but before
session.ready.
Client message errors
Sent on the open socket when an inbound message is invalid. The session stays alive (except
session_expired).
Live session errors
If the server cancels the session due to an internal error, the WebSocket closes with code
1011 without any session.error payload. In browsers, pre-handshake failures (like UNAUTHORIZED) surface as a close event with code 1006. You won’t receive a session.error. Always fetch a fresh token immediately before each connection attempt.Interruptions
When the user speaks mid-response (barge-in), the server stops the agent and emitsreply.done with status: "interrupted" and transcript.agent with interrupted: true. The decision is semantic. Back-channels like “uh-huh” don’t trigger an interruption.
On reply.done with status: "interrupted":
- Flush your local audio playback buffer.
- Discard any pending
tool.resultaccumulators from the just-ended reply. - Restart the playback stream so it’s ready for the next response.