Introduction
Realtime conversation establishes a persistent connection over WebSocket (a protocol that keeps a long-lived, bidirectional connection between client and server), streams your audio or text input to the conversational model in realtime, and the model pushes back text and speech replies incrementally. It suits voice assistants, realtime Q&A, spoken practice, and other scenarios that require back-and-forth interaction. Like realtime transcription, it runs over WebSocket, but the two serve different purposes:
Available model:
- gpt-realtime-2.1: conversational speech model, supports audio and text input, and outputs text and speech replies in realtime.
Quick Start
Connection endpoint
model=gpt-realtime-2.1: required. The model is fixed by the URL parameter at connection time and cannot be changed during the session (see the constraints below).- Note the difference from transcription: the conversation endpoint does not take
intent=transcription.
Authentication
Pass the key in a standard HTTP header during the handshake:Audio format requirements
Audio input and output currently support a single format. Convert your audio before sending:- Encoding: PCM16 (16-bit signed integer, little-endian)
- Sample rate: 24000 Hz
- Channels: mono
audio/pcm@24000. Declaring any other format (such as G.711/µ-law) for input or output is rejected and the session is closed.
Unlike transcription, conversation sessions do support voice activity detection (turn_detection / VAD). When enabled, the model decides when an utterance ends and triggers a reply; when disabled (set to
null), you control when to commit audio and when to request a reply. Choose either as needed.Session configuration (session.update)
After the connection is established, the client can send onesession.update frame to configure conversation parameters such as the voice, system instructions, and whether VAD is enabled. The conversation model is already anchored by the connection URL, so you can start talking without sending session.update. Send it when you need a custom voice or instructions.
Configuration parameters
string
required
Session type. For conversation it is
realtime.string
System instructions that set the model’s role, tone, and answer constraints.
string[]
Output modalities, either
["audio"] or ["text"]: with ["audio"] (the default) the model outputs speech and the reply text arrives through response.output_audio_transcript.delta; with ["text"] it outputs text only, delivered through response.output_text.delta. Any other combination (for example ["audio", "text"]) is rejected with an error event.object
required
Input audio format, fixed at
{ "type": "audio/pcm", "rate": 24000 }.object | null
Voice activity detection. Pass
{ "type": "server_vad" } to enable automatic turn detection; pass null to disable it and commit audio and request replies manually from the client.object
required
Output audio format, fixed at
{ "type": "audio/pcm", "rate": 24000 }.string
Voice for the reply audio. It cannot be changed once the first reply starts: after the session enters a generating state, a
voice sent again is ignored (other settings still apply). Set the voice before requesting the first reply if you need a specific one.The following capabilities are not supported in this release and close the session when configured (close code
1008): enabling inline transcription inside a conversation session (audio.input.transcription, reason input_transcription_not_supported), injecting audio (reason item_audio_not_supported) or images (reason image_input_not_supported) through conversation items, and any content item type other than text (reason unsupported_content_part). Send all audio through the input_audio_buffer.append channel.Sending input
Sending audio
Cut PCM16 audio into small chunks (for example one chunk per 100ms), base64-encode them, and send them continuously with theinput_audio_buffer.append event:
Sending text
You can also inject a text message directly and request a reply:Receiving replies
The server keeps pushing events. Key event types:event
Confirmation that the session was created or its configuration updated. You can start sending audio and text once
session.created arrives.event
A conversation item has been written: each user input and each model reply adds one item.
event
With VAD enabled, the server has detected that the user started or stopped speaking. A
speech_started event usually means the user is interrupting the model; see Interruption and truncation.event
A reply has started generating.
event
The reply’s output item has started and finished. The
item.id field in the added event is the conversation item ID you reference when truncating audio later.event
An incremental chunk of the reply audio (base64-encoded PCM16) and its completion marker, which you can play as it arrives.
event
The incremental transcript matching the reply audio sentence by sentence, plus its completion marker; the
delta field holds the newly added text. When audio output is enabled, take the reply text from this event, for example to show captions while the audio plays.event
An incremental chunk of a text-only reply and its completion marker, emitted only when you set the output modality to text only (
output_modalities: ["text"]).event
A reply has finished. This event carries the token usage for the turn (
usage), which is what billing is based on.event
Confirmation that the truncation request took effect; see Interruption and truncation.
event
An error event, with an error code and description. A problem with the request itself (for example an invalid
output_modalities value) returns a single error event and the session stays usable; policy issues (such as changing the model or running out of balance) close the session.Pick the right event for the reply text. By default (output includes audio) the model pushes only
response.output_audio_transcript.delta and does not push response.output_text.delta; set the output modality to text only and the text moves to response.output_text.delta. Listen for both in either mode so you never miss text (see run output below).Interruption and truncation
When the user starts speaking while the model is talking, content that has been generated but not yet played back conflicts with the user’s next sentence. On a WebSocket connection the client handles playback, so the client also completes the cleanup after an interruption. With VAD enabled, the server pushesinput_audio_buffer.speech_started once it detects that the user has started speaking. On receiving that event, the client should:
- Stop local playback immediately and record how far into the reply it had played (in milliseconds).
- Send
conversation.item.truncateto remove the unplayed audio from the conversation, so the model does not treat it as spoken in the next turn.
item_id: the conversation item ID of this reply, taken fromitem.idin theresponse.output_item.addedevent.content_index: the index of the audio content part, always0.audio_end_ms: how much audio to keep, in milliseconds, based on where playback actually reached.
conversation.item.truncated once the request is processed. Truncation only affects this reply’s audio and its transcript; the session itself is unaffected and you can continue with the next turn. With the OpenAI SDK, call conn.conversation.item.truncate(item_id=..., content_index=0, audio_end_ms=...).
With VAD disabled (for example push-to-talk), pressing the button is the interruption: send response.cancel to cancel the in-progress reply and truncate as described above, then on release send input_audio_buffer.append, input_audio_buffer.commit, and response.create in that order.
Complete examples
Three approaches are shown below; pick one:- Official OpenAI SDK (recommended): no need to hand-write WebSocket frames; point
websocket_base_url(the SDK’s WebSocket base URL parameter) at the gateway and reuse the official library. - Official OpenAI Agents SDK: the official agent framework’s realtime voice form; swap the
urlinmodel_configfor the gateway address. - Raw websockets: no SDK, exchange frames directly over the protocol. Fewest dependencies and easiest to debug.
Why pass the model name at connection time? The AiHubMix gateway needs the
model at the moment of the WebSocket handshake to select the model provider, authenticate, and reserve quota, while session.update only arrives after the handshake completes. So with an SDK you must pass model explicitly to connect() (the SDK puts it into the URL query); without it the gateway rejects the connection during the handshake and no connection is established. Unlike transcription, the conversation endpoint does not need intent=transcription.Reusing official examples
Most realtime conversation examples OpenAI publishes rely only on the SDK’s base URL parameter, so you can reuse them by pointing the address at the AiHubMix endpoint:Run output (measured in production)
The following is the real output of the OpenAI SDK example against theaihubmix.com production environment with model gpt-realtime-2.1. The session enables server_vad, and both prompts and audio are English.
Text input
Measured confirmation: by default (output includes audio) the reply text arrives character by character only from
response.output_audio_transcript.delta, and response.output_text.delta never appears; response.done carries the turn’s usage; the handshake takes about 2 to 4 seconds, the quota reservation cost of establishing the session.Billing
- Billed by token: a conversation session returns the token usage for each turn (
usage) with theresponse.doneevent, and billing is based on it. Usage is metered separately by component, including audio input, audio output, text input, and text output. The unit price of each component follows the live listed price on the model detail page. - Settled as you go: this is a long-lived connection, and charges are deducted in realtime with each turn as the session runs, with no single settlement at the end. Establishing a session first makes a quota reservation of about one minute of usage (an admission check only, not an actual charge), and the remaining reservation is released when the session ends. Your account’s available balance must therefore cover at least about one minute of usage for a session to be established.
- Each realtime conversation billing record can be reviewed entry by entry in Usage and billing.
Limits and constraints
- Session duration: a WebSocket connection lasts at most 62 minutes, after which the server closes it (close code
1000, reasonsession_duration_limit); reconnect in segments if you need longer. - Idle disconnect: when neither the client nor the model has any activity for 5 minutes, the server closes the session (close code
1008, reasonidle_timeout). Activity from either side resets the timer, so a long reply that keeps streaming is not interrupted. - Insufficient balance: when establishing a session, if the available balance does not cover the roughly one-minute reservation, the handshake is rejected outright (HTTP 403) and no session is established; during a session, if the balance runs out, the established connection is closed immediately.
- Server-side only: direct browser connections are not supported (the
Originheader is validated); integrate from your server. - Model locked: the model is fixed in the connection URL, and changing it with
session.updateduring the session is rejected and closes the session. - Format locked: audio input and output support only
audio/pcm@24000mono; other formats are rejected. - Voice locked:
voicecannot be changed after the first reply starts; set it before requesting the first reply. - Not yet supported: inline transcription inside a conversation session, injecting audio or image content through conversation items, and any content item type other than text.
- One reply at a time: a session allows only one in-progress reply; sending another
response.createbefore the current turn finishes is rejected and closes the session (close code1008, reasonresponse_already_active).
Common errors
Last updated: 2026-09-21