How this model actually behaves
Everything here was measured against the live API, not copied from the documentation. Where the two disagree, this page describes what the API did.
gpt-live-transcribe · webrtc · verified aug 2026
The life of a piece of text
Why transcript text starts grey, when it turns solid, and why it ends up split into separate sections.
- 0 ms
(you start speaking)Audio flows continuously over the peer connection. No client event is needed to begin — adding the microphone track is enough.
(transcript empty) - ~200 ms
conversation.item.input_audio_transcription.deltaThe first delta arrives and opens a new item_id. Text is provisional, so it renders grey and italic.
Hello ▍
item_E8Sm…3b · streaming
- ~400 ms
…deltaDeltas keep landing roughly every 200 ms, each appending to the same item.
Hello, can you ▍
item_E8Sm…3b · streaming
- ~1.2 s
…delta ×NStill one item, still grey. Nothing has been finalised yet — and nothing will be until a commit is sent.
Hello, can you hear me clearly ▍
item_E8Sm…3b · streaming
- +1.5 s of silence
→ input_audio_buffer.commitThis app decides the utterance is over and commits. The API never does this on its own for this model.
Hello, can you hear me clearly ▍
item_E8Sm…3b · streaming
- +~10 ms
input_audio_buffer.committedconversation.item.addedconversation.item.doneBookkeeping acknowledging the commit. The transcript still shows grey text — these three carry no transcript.
Hello, can you hear me clearly ▍
item_E8Sm…3b · streaming
- +300–700 ms
conversation.item.input_audio_transcription.completedThe final transcript replaces the accumulated grey text. Punctuation and casing are often corrected here. This item is now a finished section.
Hello, can you hear me clearly?
item_E8Sm…3b · finalised
- next audio
…delta (new item_id)Speaking again opens a fresh item below the finished one. That is why the transcript is a stack of sections rather than one paragraph.
Hello, can you hear me clearly?
item_E8Sm…3b · finalised
This is the next ▍
item_E8Sn…7c · streaming
The 1.5-second rule is ours, not the API's
This model has no turn detection. It will happily stream deltas into a single item forever — it never decides that a sentence ended. Somebody has to draw the boundary, and since the API will not, this app does: after 1.5 seconds without a new delta, it sends input_audio_buffer.commit, which is what produces the final transcript.
The consequence you will actually notice
If you pause for more than 1.5 seconds in the middle of a sentence, that sentence is cut into two sections. Nothing is lost, but the split is real and it happened on the client. If you find yourself thinking “why did it break my sentence there”, this is why.
The trade-off is symmetrical: a shorter timeout finalises text sooner but fragments natural speech more; a longer one keeps sentences whole but leaves text provisional for longer. 1.5 s is a starting point, not a discovered optimum. It lives in IDLE_COMMIT_MS in use-live-transcribe.ts.
Events that arrive
Observed on the oai-events data channel during a normal run.
| Event | When | Meaning |
|---|---|---|
| session.created | Once, ~170 ms after the data channel opens | Session is live and ready for audio. |
| conversation.item.input_audio_transcription.delta | Every ~200 ms while you speak | Incremental text for the open item. Appends to whatever came before on the same item_id. |
| input_audio_buffer.committed | Immediately after we send a commit | Acknowledges the commit. Carries no transcript. |
| conversation.item.added | Immediately after a commit | The item is now part of the conversation. No transcript. |
| conversation.item.done | Immediately after a commit | The item is closed. Still no transcript. |
| conversation.item.input_audio_transcription.completed | 300–700 ms after a commit | The final, corrected transcript for that item. This is the only event that finalises text. |
Events that never arrive
Waiting on any of these will hang forever.
| Event | Why not |
|---|---|
| input_audio_buffer.speech_started | Requires turn detection, which this model rejects outright. |
| input_audio_buffer.speech_stopped | Same — no server-side VAD exists for this model. |
| conversation.item.input_audio_transcription.completed (unprompted) | Never arrives on its own. Verified: after audio stopped, five seconds passed with zero completions until a commit was sent. |
Because there is no speech_started, the “Transcribing” indicator and the speech-onset timestamp are both derived locally — the former from recent delta arrivals, the latter from the microphone level crossing a calibrated silence floor.
Numbers to expect
From a real run on a Logitech C925e, en+ro, delay: low, noise_reduction: near_field.
| Stage | Typical | Note |
|---|---|---|
| Ephemeral token mint | ~400 ms | Round trip to our server, which calls OpenAI. |
| Microphone grant | 50–450 ms | Near-instant once permission is remembered. |
| SDP exchange | ~500 ms–1 s | POST /v1/realtime/calls. |
| ICE + DTLS | ~600 ms | Until pc.connectionState is connected. |
| Total setup | 1.6–2.0 s | start() through session.created. |
| Delta cadence | ~200 ms | Steady while speech continues. |
| Commit → completed | 300–700 ms | Observed 603, ~500 and ~320 ms across three utterances. |
One caveat on benchmarking: the headline “time to first word” is measured from speech onset, not from start(). Measured from start() it would mostly record how long you spent looking at the screen before talking — seconds of variance that bury the few hundred milliseconds separating the delay settings.
US vs EU endpoints
Latency figures preliminary (n=6 per endpoint). EU-does-not-transcribe: confirmed with real speech.
eu.api.openai.com accepts the entire chain on a non-approved account — mint 200, SDP 201, data channel open, session.created, RTP flowing — and never produces a single delta. Confirmed with real speech on a physical microphone, not just the synthetic test signal. The documented enterprise gating evidently applies at inference rather than at authentication: instead of a 403 at the door you get a flawless handshake and eternal silence — the silent-failure archetype this project keeps meeting. The irony: the EU media path is closer (73–81 ms RTT vs 170–183 ms on US), and unusable.
| Measurement | api.openai.com | eu.api.openai.com |
|---|---|---|
| Resolved IPs | 172.66.0.243, 162.159.140.245 | identical — same Cloudflare anycast |
| TLS handshake | ~66 ms | ~62–74 ms |
| Mint, median of 6 | 265 ms (250–419) | 284 ms (203–1859) |
The medians are effectively equal. EU showed a much fatter tail, but one 1.86 s outlier in six probes is not a finding. Both hostnames hit the same edge, so any real difference lives in routing from that edge to the origin — which TLS timings cannot see. That is precisely why the selector exists: accumulate a few dozen runs in the history and read those medians instead of trusting six probes.
The more informative number is the ICE round-trip reported by the connection test. HTTPS terminates at a nearby CDN edge, but RTP goes to real media servers — so the media path can be far away even when the API edge answers in 66 ms.
What the model will not give you
Hard constraints. No configuration unlocks these.
- No word-level timestamps. Use a file-transcription model if you need them.
- No speaker labels.
gpt-4o-transcribe-diarizeis the model for diarisation. - No confidence scores. Logprobs can be requested via
include: ["item.input_audio_transcription.logprobs"], but that is per-token likelihood, not a transcript confidence. - No turn detection. Sending
turn_detectionis rejected with “Turn detection is not supported for this transcription model.”
| Model | Price | Use |
|---|---|---|
| gpt-live-transcribein use | $0.017 / min | Streaming deltas from live audio. What this experiment uses. |
| whisper-1 | $0.006 / min | Batch transcription of completed files. |
| gpt-transcribe | $0.0045 / min | Async transcription; in realtime only after a committed turn. |
Live streaming costs roughly three times batch transcription. If text is not needed until the recording ends, the cheaper models are the right call.
Troubleshooting
Ordered by how much time each one has already cost.
Connected, but no text ever appears
Check the Outbound audio panel first. If packetsSent is 0, no audio is leaving the browser at all. If packets are flowing but audioLevel is ~0, the microphone is capturing silence — wrong input device, muted at the OS level, or a hardware mute switch. This is the failure mode that looks exactly like a broken API, and it is not one: the session config has been verified working end to end.
Text appears but accuracy is poor
Look at the input level. A webcam microphone measured audioLevel 0.0027 — audible, but very quiet. At that level whole phrases came back mangled and “OK” was transcribed as a caress. Move closer, raise the input gain in the OS, or switch to a headset. Also try far_field noise reduction for a laptop or room microphone, and raise delay to give the model more context per chunk.
Text stays grey and never finalises
A commit was never sent. That only happens if the data channel closed, or if deltas are still arriving so the 1.5 s idle timer keeps resetting. Pressing Stop always forces a final commit.
A sentence got split in two
Expected — you paused for more than 1.5 s. See above.
Yellow “unexpected” tags in the event log
Those mark event types never observed before. The routine commit trio — input_audio_buffer.committed, conversation.item.added, conversation.item.done — is known and is no longer flagged. Anything still highlighted is genuinely new and worth reading.