The session.created and session.updated events
On this page
Updated November 3, 2026, based on the xAI Speech to Speech page and Voice API reference. The reference doesn’t detail the contents of the
sessionobject in these events; the ID to keep is the conversation’s,conversation.id, which session resumption relies on. Sources: Speech to Speech · Voice API.
Server session events
As you work with the Voice Agent API, the server keeps you informed about the session’s state through JSON events. The first ones you receive are session.created, when the connection opens, and conversation.created. Then comes session.updated, after each configuration you send.
session.created
The server sends this event automatically as soon as the WebSocket connection opens. According to the reference, it contains the session configuration, but its fields aren’t detailed. Rather than assuming default values, log it once to see what the server returns, then send your own configuration.
What to do when session.created arrives
Your app should:
- Log the event, for debugging
- Immediately send your
session.updatewith the configuration you want - Flush the audio buffer you filled while waiting for the connection
The third point comes from xAI’s advice for reducing perceived latency: start capturing the mic as soon as the screen opens, without waiting for the connection, keep the samples in memory, then send them as soon as the connection is ready and the configuration has been sent.
ws.on("message", (data) => {
const event = JSON.parse(data);
switch (event.type) {
case "session.created":
console.log("Session created:", event.session);
// Send the configuration
ws.send(JSON.stringify({
type: "session.update",
session: {
instructions: "You are a voice assistant.",
voice: "rex",
turn_detection: { type: "server_vad" }
}
}));
// Then send the audio captured while waiting for the connection
flushMicBuffer(ws);
break;
}
});
session.updated
This event acknowledges your session.update: the session is configured. For some settings, the documentation states that the applied value is sent back here, as with the pronunciation replacement table (replace). An invalid configuration, on the other hand, is rejected with an error event that describes the problem; the session stays open and keeps its previous configuration.
Check the configuration
Compare the returned values with the ones you sent: that’s how you catch a setting that was ignored.
case "session.updated":
const s = event.session;
console.log("Configured voice:", s.voice);
console.log("Number of tools:", s.tools?.length ?? 0);
break;
case "error":
console.error("Configuration rejected:", event);
break;
conversation.created
The server also sends a conversation.created event, which carries the conversation ID:
{
"type": "conversation.created",
"conversation": {
"id": "conv_xyz789"
}
}
Keep this ID: it’s what makes session resumption possible. By default, a connection loses the conversation history when the WebSocket closes. If you send "resumption": {"enabled": true} in session.update, the server caches every turn. When you reconnect, reopen the WebSocket with ?conversation_id=<id> and enable resumption again: the previous turns are replayed before your first new turn. The history is dropped after 30 minutes of inactivity.
The full startup sequence
To sum up, here’s the order of events when a voice session starts, as the reference describes it:
- The WebSocket opens
- You receive
session.created(server → client) - You receive
conversation.created(server → client) - You send
session.update(client → server) - You receive
session.updated(server → client)
Key takeaways
session.createdarrives when the connection opens and contains the session configuration, whose fields the reference doesn’t detailsession.updatedacknowledges your configuration; an invalid configuration returns anerrorevent- Capture the mic before the connection and flush the buffer as soon as it’s ready
conversation.createdcarries theconversation.id, which you keep for session resumption- Resumption requires
resumption.enabledon every connection, and the history expires after 30 minutes of inactivity