Skip to main content
The Realtime WebSocket API provides bidirectional audio over a single connection. It uses a focused realtime event subset for connecting ThunderPhone agents to your telephony stack. One WebSocket connection represents one call. Realtime calls appear in call history and are billed like any other call at your product’s per-minute rate.

Create a managed realtime session

POST /v1/realtime/sessions creates a LiveKit room and returns a scoped participant token. Authenticate with a secret API key and provide either a saved agent_id or an inline config, but not both.
Inline configuration accepts only prompt, voice, product, tools, primary_language, additional_languages, background_track, telephony, placeholders, outbound, and call_events. prompt is required. Unknown keys are rejected with a validation error rather than silently ignored.
The operation returns 201 Created with the room credentials:

Endpoint

The realtime endpoint shares its host with the REST API — the same api.thunderphone.com base URL and API key work for both.

Connect and authenticate

Authenticate the WebSocket upgrade with your secret API key:
Browser clients that cannot set an upgrade header can pass the key in the query string instead:
A query-string key can appear in browser history and network logs. Use the Authorization header whenever your WebSocket client supports it, and never expose a secret API key in public client code.
After the connection opens, the server sends session.created. Configure the call with session.update; the server confirms the accepted settings with session.updated.

Configure the session

Send the GA nested session shape before streaming audio. The first session.update starts the call. When instructions is omitted or empty, ThunderPhone uses You are a helpful voice assistant.

Session fields

session.config accepts only voice, product, tools, primary_language, additional_languages, background_track, telephony, placeholders, outbound, and call_events. The call prompt comes from session.instructions; there is no separate prompt key over the WebSocket. Unknown keys are rejected with a validation error rather than silently ignored. Set telephony to false for the same wideband or browser-style input tuning selected by session.audio.input.noise_reduction.type set to near_field. All audio is mono. PCM audio is signed 16-bit little-endian at 16000 or 24000 Hz; G.711 (audio/pcmu μ-law, audio/pcma A-law) is 8000 Hz, matching telephony trunks — no client-side transcoding needed. Input and output formats are configured independently, but neither can change after the call starts. After the call starts, agent configuration is immutable: instructions, voice, tools, product, and other session.config fields cannot change. Bridge-local transport options such as binary_output remain changeable. For compatibility, flat instructions, voice, input_audio_format, and output_audio_format fields are also accepted on session.update. New integrations should use the nested shape above. Inline sessions can define custom functions under session.config.tools. The function schema is available to the agent; your WebSocket client remains responsible for executing the function and returning its output.
Custom tools on saved-agent sessions continue to use the routing configured on the saved agent; they are not delivered to the WebSocket client.

Inline sessions are client-steered

With inline configuration, ThunderPhone adds no automatic conversation behaviors. The agent never speaks first, there are no silence check-ins or timeouts, and by default nothing is spoken while a response is being prepared. If you want progress speech during slow responses — for example while the agent waits on one of your tool results — set placeholders in the session config to a list of short phrases in your own words (e.g. {"placeholders": ["One moment.", "Still checking."]}). They are spoken in the session voice, cycling only when a response takes long enough to need them. If the caller goes quiet, the session remains open until your client prompts the agent or hangs up. Silence-behavior config keys are not accepted and are rejected with a validation error. To have the agent open the call, configure the session, append a system or user message, and explicitly request one response:
session.created is sent as soon as the WebSocket connects. session.updated is sent only after the configuration has been accepted and the session fully provisioned, so it is the signal that the call is live; a rejected configuration produces an error event instead, and the session stays usable for a corrected retry. You do not need to wait, though: message items and response.create sent early are buffered and delivered in order once the agent is ready, so the opening sequence above is safe to send immediately after session.update. Sessions created with agent_id instead retain the behaviors configured on the saved agent, including its greeting order and silence check-ins.

Use a saved agent

To use an existing agent, connect with its id:
The saved agent starts immediately, so a session.update with instructions is not required. Set its wire audio before minting with the optional input_audio_format, output_audio_format, input_rate, and output_rate query parameters. Formats accept audio/pcmu, audio/pcma, g711_ulaw, g711_alaw, or pcm16; PCM rates accept 16000 or 24000 (G.711 is always 8000 Hz). For example:
Agent instructions and voice come from the saved agent.

Client events

Send client events as JSON text frames unless otherwise noted.

Append audio

Send audio at its natural pace rather than uploading an entire recording at once. You can send each chunk as a raw binary frame instead; no session flag is required for binary input.

Send text guidance

Message items are appended to the conversation and never trigger a response by themselves. Use user for a completed caller utterance and system, with developer accepted as an alias, for silent guidance. A response begins either from server turn detection on audio or from an explicit response.create. Message items are acknowledged with conversation.item.added followed by conversation.item.done; both echo the client-provided item.id, or a generated id when it is absent. Text is not converted into caller audio.
For silent guidance, set "role": "system" in the same message shape. Assistant-role message injection is unsupported.

Request a response

Send response.create to trigger exactly one response using the conversation so far. If a response is already in progress, the server emits an error event with code conversation_already_has_active_response and does not start another. After handling a custom function call, the client must send a function_call_output item with the received call_id. The output can be a JSON string or object. The default deadline is 30 seconds. If the deadline expires, the conversation continues with an error result so the agent can recover. Every function call names one of your declared tools and expects a function_call_output; platform actions arrive as call.* events instead (see below) and take no output.

Server events

Server events are JSON text frames. Each JSON event includes a unique event_id.

Conformance notes

ThunderPhone supports PCM at 16000 Hz as an extension. The OpenAI GA schema pins audio/pcm to 24000 Hz, so strict validators of session.created and session.updated should also allow rate 16000. The normal audio response sequence is:
Audio and transcript deltas can be interleaved within the response.

Function calls

Function calls arrive as function_call output items and always name one of the tools your client declared. Read the function name and call_id from response.output_item.added, accumulate argument deltas, and act after response.function_call_arguments.done. Platform actions are never function calls — they arrive as call events.

Call capabilities

Every session’s agent can end the call and can deliberately stay silent for a turn while the far side is still talking — fundamental phone-call behaviors, always on. Sessions with outbound: true additionally wait out hold queues, and — with call_events enabled — press keypad digits to navigate menus. Transfers also require call_events: the agent decides to transfer, and your client executes it on your telephony stack from the call.transfer instruction. Capabilities that instruct your client are only granted when the event channel exists to carry the instruction; there is nothing else to configure or declare. A set of function names is reserved for the platform and rejected in your tools with a validation error: end_call, transfer_call, send_keypad_input, no_response, wait_on_hold, search_knowledge_base, play_sound, speak_uninterruptible.

Call events

Platform call actions and state changes are delivered as dedicated events in the call.* namespace — a ThunderPhone extension to the realtime event set. They are an explicit opt-in: set call_events: true in the session config to receive them. They are notifications: nothing is sent in reply, and there is no call_id or output. Handle every type below in your event loop — clients ported from other realtime APIs often switch on known event types and would otherwise drop them.

Outbound calls

Set outbound: true in the session config when the session is an outbound call — the agent dialed out, so the far side may be an automated menu, a hold queue, voicemail, or a screener rather than a person. This enables the engine’s outbound call handling: the agent recognizes and navigates automated systems reliably, responds immediately when a human picks up so they are not met with dead air, and applies cost-saving measures that keep the price down while the call is working through menus, waiting on hold, or handling voicemail. It is off by default; sessions without it treat the far side as a human caller. Menu navigation and hold handling need no further configuration — steer them through your instructions:
A standalone function call follows this sequence:

Using the official OpenAI SDK

Existing OpenAI Realtime integrations work by changing only the WebSocket base URL and the API key. Pass the base ending at /v1 — the SDK appends /realtime itself, mirroring wss://api.openai.com/v1:
The model argument is echoed back but does not change behavior; the agent configuration comes from the session instructions or a saved agent.

Minimal Python client

Install websockets, set THUNDERPHONE_API_KEY, and provide an uncompressed mono pcm16 WAV file sampled at 16000 Hz:

Protocol notes

  • Turn detection uses server-side voice activity detection and is always on. Clients do not need to commit or clear the input audio buffer.
  • Transcript events are emitted as per-utterance segments, not token-by-token deltas. Use the completed and done events as final text.
  • Unknown or unsupported client event types produce an error event without closing the WebSocket. The client can correct the request and continue.