VOICE / resume-a-speech-to-speech-session

Voice

Resume a Speech to Speech session

Resume a Speech to Speech session

Opt in with session.resumption.enabled: true, save conversation.created.conversation.id, then reconnect with ?conversation_id= so prior turns replay before your next utterance.

Opt in and save the id

import json, os, websockets

MODEL = "grok-voice-latest"

async def connect(resume_id=None):
    url = f"wss://api.x.ai/v1/realtime?model={MODEL}"
    if resume_id:
        url += f"&conversation_id={resume_id}"

    async with websockets.connect(
        url,
        additional_headers={"Authorization": f"Bearer {os.environ['XAI_API_KEY']}"},
    ) as ws:
        await ws.send(json.dumps({
            "type": "session.update",
            "session": {"resumption": {"enabled": True}},
        }))

        async for raw in ws:
            event = json.loads(raw)
            if event["type"] == "conversation.created":
                saved_id = event["conversation"]["id"]
                # persist saved_id for the next reconnect
            # ... handle turns

Without the opt-in, closing the WebSocket drops conversation history.

Reconnect

  1. Reopen wss://api.x.ai/v1/realtime?model=…&conversation_id={saved}.
  2. Send session.update with resumption.enabled: true again — resume must opt in both ways or nothing replays.
  3. Cached turns arrive as conversation.item.created before your first new turn.

What is replayed

Persisted and replayed on resume:

  • User and assistant transcripts
  • Assistant tool calls
  • Your function_call_output results

History expires after 30 minutes of inactivity.

Pitfalls

  • Forgetting resumption.enabled: true on the second connect skips replay even with a valid conversation_id.
  • Do not assume audio bytes are cached — plan for transcript/tool context only.
  • SIP paths can also pass a saved id; see the SIP how-to for call_id + conversation_id together.
  • Console API credits are separate from SuperGrok's weekly pool on grok.com.