
Resume a SIP conversation across calls
Continue transcripts and tool results from an earlier SIP Speech to Speech call on a later dial-in when the same person calls back within the retention window and your agent should remember the prior turns. Official SIP Phone Calls FAQ documents session resumption: you opt in on both the original session and the resuming session with resumption.enabled set to true inside session.update. History expires after 30 minutes of inactivity. Restore happens as soon as that session.update is processed; replayed turns arrive as conversation.item.created events. There is no dedicated resumption-complete event.
What you need
SIP phone registration and webhook handling from Handle SIP phone calls with Speech to Speech, an inference API key (ephemeral client secrets are not supported for SIP call_id sessions), and durable storage in your app for the first call’s call_id so you can pass it as conversation_id on the next WebSocket. Neighboring jobs include Transfer a SIP call with refer when you hand off live instead of waiting for a callback, Write a speech-to-speech system prompt for instructions content, and List registered SIP phone numbers for line inventory. More voice jobs live on the Voice hub.
Enable resumption on the first call
After the incoming webhook gives you data.call_id, open wss://api.x.ai/v1/realtime?call_id={call_id} with your API key and immediately send session.update with resumption enabled. Save that call_id.
import asyncio
import json
import os
import websockets
async def answer_with_resumption(call_id: str):
async with websockets.connect(
f"wss://api.x.ai/v1/realtime?call_id={call_id}",
additional_headers={"Authorization": f"Bearer {os.environ['XAI_API_KEY']}"},
) as ws:
await ws.send(json.dumps({
"type": "session.update",
"session": {
"voice": "eve",
"instructions": "You are a helpful phone support agent.",
"turn_detection": {"type": "server_vad"},
"resumption": {"enabled": True},
},
}))
await ws.send(json.dumps({"type": "response.create"}))
# Persist call_id in your datastore for the next dial-in.
print("save conversation_id=", call_id)
async for raw in ws:
event = json.loads(raw)
print(event.get("type"))
asyncio.run(answer_with_resumption("00000000-0000-0000-0000-000000000000"))
Resume on the later call
When the same number (or any later SIP call you want to attach) fires a new webhook, open the new socket with both query parameters: the new call_id and conversation_id set to the saved prior call_id. Send the same style of session.update again with resumption.enabled: true so restore runs and future reconnects keep saving.
wss://api.x.ai/v1/realtime?call_id={new_call_id}&conversation_id={saved_call_id}
import json
# After websockets.connect(...?call_id=NEW&conversation_id=SAVED...)
await ws.send(json.dumps({
"type": "session.update",
"session": {
"voice": "eve",
"instructions": "You are a helpful phone support agent.",
"turn_detection": {"type": "server_vad"},
"resumption": {"enabled": True},
},
}))
# Replayed turns appear as conversation.item.created — no separate "resume done" event.
Keep the prior call_id only as long as you need resume; after 30 minutes of inactivity the cached history is gone and a new conversation starts cleanly.
Pitfalls
Enabling resumption only on the second call without the first session’s opt-in leaves nothing to restore. Forgetting to persist the first call_id makes conversation_id impossible to set. Waiting for a dedicated resume-complete event stalls your agent — docs say restore is done when session.update is processed and items stream as conversation.item.created. Expecting history after more than 30 minutes of idle time fails silently into a fresh conversation. Using an ephemeral browser client secret on a SIP call_id socket is unsupported; authenticate SIP joins with the team API key.