Skip to main content
Lesson 4 of 16 · Voice Agent API: real-time conversations

The session.created and session.updated events

Lesson 4 of 16 Updated November 3, 2026 Developer
On this page
  1. Server session events
  2. session.created
  3. session.updated
  4. conversation.created
  5. The full startup sequence
  6. Key takeaways

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 session object 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:

  1. Log the event, for debugging
  2. Immediately send your session.update with the configuration you want
  3. 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:

  1. The WebSocket opens
  2. You receive session.created (server → client)
  3. You receive conversation.created (server → client)
  4. You send session.update (client → server)
  5. You receive session.updated (server → client)

Key takeaways

  • session.created arrives when the connection opens and contains the session configuration, whose fields the reference doesn’t detail
  • session.updated acknowledges your configuration; an invalid configuration returns an error event
  • Capture the mic before the connection and flush the buffer as soon as it’s ready
  • conversation.created carries the conversation.id, which you keep for session resumption
  • Resumption requires resumption.enabled on every connection, and the history expires after 30 minutes of inactivity