Do you need a TURN server to use the OpenAI Realtime API over WebRTC? It is the first question a WebRTC-experienced developer asks, and the answer is stranger than yes or no: on the direct browser-to-OpenAI path you do not — and you could not add your own TURN even if you wanted to.
But the moment you build the architecture most production voice agents actually ship, TURN comes back onto the critical path.
We map the real connection topologies, walk the WebRTC handshake with a complete working example we executed against the live API, and give you a decision table for exactly when a TURN server is mandatory versus irrelevant.
TL;DR: The OpenAI Realtime API runs over WebRTC, WebSocket, or SIP (OpenAI docs, 2026-07-17). On the direct browser-to-OpenAI WebRTC path you do not need your TURN server: OpenAI uses public endpoints, host candidates only, and a TCP/443 fallback, so you can't add TURN anyway. The moment you own a WebRTC leg (browser to your server, or a Python
aiortcagent), TURN over TLS on 443 is mandatory on restrictive networks.
Does the OpenAI Realtime API need a TURN server?
No — not on the direct path, and yes — the instant you add your own server.
If your browser talks straight to OpenAI, OpenAI owns the WebRTC endpoint. It publishes public, reachable addresses and handles restrictive networks with its own TCP/443 fallback. There is no TURN server for you to configure.
If you put your own server in the middle — to hold your API key, add tools and guardrails, record calls, or swap providers — you now own a browser-to-your-server WebRTC connection. That leg needs your STUN and TURN, exactly like any other WebRTC app.
Most real production voice agents are the second case. So the honest answer is "usually yes — but probably not for the reason you'd expect, and not on the leg you'd expect."
What the OpenAI Realtime API is in 2026
The Realtime API is OpenAI's low-latency, speech-to-speech interface for building a realtime voice AI agent. It went generally available on 2025-08-28 alongside the first production model, gpt-realtime (OpenAI, accessed 2026-07-17).
As of 2026-07-17, the catalog lists gpt-realtime-2 as the default realtime model, with gpt-realtime-2.1 and a cheaper gpt-realtime-2.1-mini as the current point releases, plus specialized gpt-realtime-translate and gpt-realtime-whisper models (developers.openai.com, accessed 2026-07-17). OpenAI ships these fast — four point releases in about eleven months — so pin a specific model and date it rather than trusting "the latest."
The API is reachable over three transports, and OpenAI gives explicit guidance on each. This choice decides which leg of your system owns NAT traversal, so read the table with that lens.
| Transport | OpenAI's stated use (verbatim) | Endpoint |
|---|---|---|
| WebRTC | "Use for browser and mobile clients that capture or play audio directly." | POST /v1/realtime/calls |
| WebSocket | "Use when your server already receives raw audio from a media pipeline, call system, or worker." | wss://api.openai.com/v1/realtime |
| SIP | "Use for telephony voice agents." | SIP into /v1/realtime
|
Table: OpenAI Realtime API transports and guidance, quoted from the official Realtime guide (developers.openai.com, accessed 2026-07-17).
The load-bearing takeaway: WebRTC is the client-edge transport, WebSocket is the server-side transport. Where your audio originates tells you which transport to use — and whether a browser is a WebRTC peer at all.
How the browser-to-OpenAI WebRTC connection actually works
The direct WebRTC flow skips the signalling server you would normally build. There is no WebSocket handshake to negotiate the call; OpenAI uses plain HTTP for the SDP exchange (OpenAI WebRTC guide, accessed 2026-07-17).
It runs in four moves, and the code below is the complete flow. We executed it end-to-end against the live API on 2026-07-17; the field names, status codes, and connection states that follow were observed.
First, your backend mints a short-lived client secret so your real API key never touches the browser.
// server.js (Node 18+) — your standard API key stays server-side.
const r = await fetch("https://api.openai.com/v1/realtime/client_secrets", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
session: {
type: "realtime",
model: "gpt-realtime-2.1-mini", // pin a model and date it
audio: { output: { voice: "marin" } },
},
}),
});
const token = await r.json(); // → { value: "ek_…", expires_at, session }
// hand token.value to the browser; it expires quickly by design
Second, the browser creates an RTCPeerConnection, attaches the mic, opens the events channel, and POSTs its raw SDP offer to OpenAI — which returns the SDP answer in the HTTP response body with a 201 Created.
// browser — fetch the ephemeral key from YOUR backend, never OpenAI directly
const { value: EPHEMERAL_KEY } = await (await fetch("/token", { method: "POST" })).json();
const pc = new RTCPeerConnection(); // note: no iceServers passed — this is the whole point
pc.ontrack = (e) => { audioEl.srcObject = e.streams[0]; }; // model audio out
const mic = await navigator.mediaDevices.getUserMedia({ audio: true });
pc.addTrack(mic.getAudioTracks()[0], mic); // your mic in
const events = pc.createDataChannel("oai-events"); // JSON events channel
events.onmessage = (e) => {
const ev = JSON.parse(e.data); // session.created, response.*, input_audio_buffer.*
if (ev.type === "response.output_audio_transcript.done") console.log(ev.transcript);
};
await pc.setLocalDescription(await pc.createOffer());
const resp = await fetch("https://api.openai.com/v1/realtime/calls", {
method: "POST",
body: pc.localDescription.sdp,
headers: { Authorization: `Bearer ${EPHEMERAL_KEY}`, "Content-Type": "application/sdp" },
});
await pc.setRemoteDescription({ type: "answer", sdp: await resp.text() }); // 201 + answer SDP
Third, session updates, tool calls, and transcripts flow as JSON over the data channel named oai-events, using the same schema as the WebSocket API. Fourth, audio is just a normal media track in each direction (OpenAI WebRTC guide, accessed 2026-07-17).
Here is what our live run observed, in order: the secret minted as { value, expires_at, session }; the SDP exchange returned 201 Created; ICE went checking → connected with no ICE servers configured; oai-events opened; and the model answered our first response.create out loud. OpenAI's server-side voice activity detection then took further turns off the incoming audio stream — the full round trip, working.
Notice what is missing: you never pass iceServers to that RTCPeerConnection. That omission is deliberate, and it is the key to the entire TURN question.
The architecture question: where do STUN and TURN come in?
On the direct browser-to-OpenAI path, STUN and TURN do not come in at all — from your side. OpenAI terminates WebRTC server-side at publicly reachable endpoints and returns host candidates only, with no STUN or TURN server (webrtcHacks teardown of the GA gpt-realtime stack, dated 2025-09-23).
The teardown found OpenAI advertising multiple public Azure datacenter endpoints and connecting clients directly to them over UDP on port 3478 and TCP on port 443 — with 443/TCP added at GA specifically to pass firewalls that block UDP and non-web ports (webrtcHacks, 2025-09-23).
That design has a clean consequence. Because OpenAI's endpoint is public and ships its own TCP/443 fallback, the browser-to-OpenAI hop traverses most NATs and many corporate firewalls without any TURN server on your side. Our executed run is the proof in miniature: ICE reached connected with no ICE servers configured at all.
And you could not add one if you wanted to. OpenAI controls the answer SDP, so there is no place to inject your relay. If WebRTC muscle memory has you reaching for an iceServers block here, there is nothing for it to do — on this topology a TURN server is just not necessary
This is why direct-path failures reported in OpenAI's community forums read as transient service issues, not NAT problems — the direct path rarely fails on NAT because OpenAI engineered the firewall escape hatch into its own endpoint.
When you DO need TURN: the moment you own a WebRTC leg
Here is the turn. Most production voice agents do not send browser audio straight to OpenAI. They insert a server in the middle — and that server changes everything about connectivity.
Why add a server? To keep your API key off the client, add tools and guardrails, run server-side voice activity detection and barge-in, record or transcribe, bridge telephony, or swap the model provider without shipping a new client. All sensible reasons — and all of them create a second WebRTC connection that you own.
On the browser-to-your-server leg, you are the WebRTC endpoint. That means you own NAT traversal. A public-IP media server handles most users through host and server-reflexive candidates — but users on symmetric NAT, UDP-blocked corporate, hospital, or bank networks, or restrictive Wi-Fi cannot connect without a TURN relay, ideally TURN over TLS on port 443 so it looks like ordinary HTTPS.
This is not my claim alone. Python's aiortc uses a standard RTCConfiguration with iceServers, and the same NAT rules apply on its browser-facing leg.
So the decision is not "does OpenAI Realtime need TURN." It is "does my architecture put a WebRTC leg under my control." Here is that decision as a table.
| Topology | Who owns the client-edge WebRTC leg | TURN needed? | Notes |
|---|---|---|---|
| A. Direct browser → OpenAI | OpenAI (public endpoint, host-only) | No — and you can't add it | OpenAI's own TCP/443 handles restrictive networks. Simplest path. |
| B. Browser → your media server → OpenAI | You | Yes — STUN + TURN, ideally TURNS/443 | The dominant production pattern. Enterprise networks fail without TURN. |
C. Python/aiortc agent ↔ browsers |
You (the aiortc endpoint) |
Yes, on the browser-facing leg | Server-side WebRTC in Python; same NAT rules apply. |
| D. Telephony / SIP → OpenAI SIP | Your SBC/gateway (SIP, not WebRTC) | N/A for SIP | TURN reappears only if a WebRTC softphone leg exists. |
| E. Server already has the audio → OpenAI WebSocket | Nobody (no browser leg) | No | WebSocket, no ICE at all. |
The Python and server-side path
Python builders hit this split constantly, so it deserves its own section. There are two very different Python paths, and only one of them touches ICE.
If your server already has the audio — from a telephony system, a media pipeline, or a worker — use the WebSocket transport with the openai Python SDK. There is no browser peer, no ICE, and no TURN.
If your Python service must be a WebRTC peer — for example a headless agent that browsers connect to directly — you use aiortc, "WebRTC and ORTC implementation for Python using asyncio" (aiortc, accessed 2026-07-17). Now you own the browser-facing leg, and you are back in topology C: STUN and TURN required.
Configuring ICE in aiortc is a standard RTCConfiguration. Point it at your relay, preferring TURNS on 443 for locked-down networks:
from aiortc import RTCConfiguration, RTCIceServer, RTCPeerConnection
config = RTCConfiguration(iceServers=[
RTCIceServer(urls="stun:openrelay.metered.ca:80"),
RTCIceServer(
urls="turns:openrelay.metered.ca:443?transport=tcp",
username="<from your TURN credential API>",
credential="<short-lived secret>",
),
])
pc = RTCPeerConnection(configuration=config) # your browser-facing peer now has a relay
This is exactly Metered Python SDK fits. metered-realtime (PyPI v1.0.0, async, built on aiortc) is the SDK for building that browser-facing WebRTC leg in Python, and it auto-injects Open Relay TURN so a Python agent that peers with browsers gets NAT traversal without you standing up coturn.
To be precise about what it is: metered-realtime is the transport layer under your agent, not an OpenAI Realtime client. Your agent still talks to OpenAI over WebSocket or WebRTC; metered-realtime handles the browser-facing WebRTC peer and its relay.
Why your voice agent fails on office and hospital Wi-Fi
This is the failure that many devs face, and it maps exactly onto the topology table. When a WebRTC AI agent "works on my machine" but dies on a customer's corporate network, the broken leg is almost always the one you own.
Corporate, hospital, and bank networks block outbound UDP and non-standard ports, and many run deep packet inspection that drops traffic on 443 that is not genuine TLS. Symmetric NAT breaks the direct peer path on top of that. Your host and server-reflexive candidates all fail, and the call never connects.
TURN over TLS on port 443 is the escape hatch. It performs a real TLS handshake and looks identical to an HTTPS request, so it survives both the firewall and the DPI. For a deeper treatment of why 443 and TURNS specifically are what get through. you can test the TURN over TLS in TURN server testing tools like: TURN server testing
Latency matters here too, because this is voice. A relay three regions away adds audible delay, so a production TURN service with relays near your users — not a single box — is what keeps relayed calls sounding real. This is the same relay reality behind every TURN server for AI agents
agent audio is real-time media, and real-time media behind a corporate firewall needs a good relay. And when a relayed call still drops mid-session — networks change, Wi-Fi roams — WebRTC reconnection handling is what gets the user back without a page refresh.
The fix has two speeds. Open Relay gives you 20 GB/month of free TURN with ports 80, 443, and TURNS out of the box
METERED TURN — for the leg you own (facts dated 2026-07-17)
- Metered TURN product: 500 MB free trial, then paid.
Production tiers (https://metered.ca/stun-turn, verified 2026-07-03)
- Growth $99 / 150 GB, Business $199 / 500 GB, Enterprise $499 / 2 TB, custom above. Usage is metered as ingress + egress.
- 31+ regions, 100+ edge PoPs for low-latency relayed voice.
- Ports 80 / 443 / TURNS, dynamic per-session credentials, per-credential analytics, 24/7 human support.
Metered's managed TURN service is the same relay function across 31+ regions with fixed, allowlistable IPs and region pinning — the connectivity most enterprise voice deployments end up needing.
What OpenAI Realtime costs
Cost is the other thing that surprises builders, so here are the current list prices. These are OpenAI's published figures per 1M tokens unless noted (developers.openai.com pricing, accessed 2026-07-17).
| Model | Audio in | Audio out | Text in / out |
|---|---|---|---|
gpt-realtime-2.1 |
$32.00 | $64.00 | $4.00 / $24.00 |
gpt-realtime-2.1-mini |
$10.00 | $20.00 | $0.60 / $2.40 |
gpt-realtime-translate |
— | — | $0.034 / minute |
gpt-realtime-whisper |
— | — | $0.017 / minute |
For per-minute intuition, user audio runs roughly 600 tokens per minute and assistant audio roughly 1,200 tokens per minute. Independent measurements suggest a typical agent costs around $0.18–$0.46 per minute uncached, dropping to roughly $0.04–$0.10 per minute with prompt caching, trimmed tool outputs, and server-side VAD (third-party 2026 measured-session write-ups, accessed 2026-07-17).
Treat those per-minute figures as independent estimates, not OpenAI's own numbers — methodology varies. The list prices above are the facts; the per-minute ranges are directional.
Putting it together: a reference architecture
Stack the pieces and the production shape is clear. A browser captures audio and connects over a WebRTC leg to your backend; your backend runs VAD, tools, and guardrails, then talks to OpenAI; and a TURN relay sits on the browser-facing leg for the users who need it.
You own two things in that picture that OpenAI does not give you: the browser-facing WebRTC leg (which needs TURN) and the signalling for it. If you would rather not wire the backend leg yourself, our free, open-source SDK LLMRTC (@llmrtc/llmrtc-core, -backend, -web-client; Apache 2.0) is a batteries-included version of this backend — browser ⇄ WebRTC ⇄ Node backend ⇄ providers.
LLMRTC is provider-agnostic and lists OpenAI among its supported providers — its OpenAILLMProvider, OpenAIWhisperProvider, and OpenAITTSProvider are swappable by config (llmrtc.org, accessed 2026-07-17) — and its own docs recommend Open Relay TURN for production. Built by our team, it is the "don't hand-roll the media backend" option for a build AI voice agent project.
One more piece you own: signalling for that browser-to-server leg. If you build it yourself, Metered Realtime is free managed signalling with an MIT-licensed open-source client, so you can start free instead of standing up your own WebSocket layer. It is the natural companion to the relay — the two things OpenAI's direct path handles for you, and you handle yourself the moment you own a leg.
That is the whole architecture in one honest sentence: OpenAI gives you the model and a public endpoint; you give yourself the media leg, its relay, and its signalling — and TURN lives on that leg, not on OpenAI's.
Frequently asked questions
Does the OpenAI Realtime API need a TURN server?
Not on the direct browser-to-OpenAI path. OpenAI terminates WebRTC at public endpoints with host candidates only and a TCP/443 fallback, so that leg traverses most networks without your TURN — and you cannot add one (webrtcHacks, 2025-09-23). You need TURN the moment you own a WebRTC leg, such as browser to your media server, where users on restrictive networks fail without a relay.
WebRTC or WebSocket for the OpenAI Realtime API?
Use WebRTC for browser and mobile clients that capture or play audio directly, and WebSocket when your server already has raw audio from a media pipeline, call system, or worker (OpenAI guidance, accessed 2026-07-17). WebRTC is the client-edge transport and involves ICE; WebSocket is the server-side transport with no ICE and no TURN.
Why does my OpenAI Realtime WebRTC agent fail on a corporate network?
Because the failing leg is one you own, not the OpenAI leg. Corporate, hospital, and bank networks block UDP and non-standard ports and inspect port 443, so your browser-to-your-server WebRTC connection cannot use host or server-reflexive candidates. TURN over TLS on port 443 is the fix — it looks like ordinary HTTPS and survives deep packet inspection.
How do I connect to the OpenAI Realtime API from Python?
Two ways. If your server already has the audio, use the openai Python SDK over WebSocket — no ICE, no TURN. If your Python service must be a WebRTC peer that browsers connect to, use aiortc with an RTCConfiguration that includes STUN and TURN ICE servers, because you now own NAT traversal on the browser-facing leg.
Is the OpenAI Realtime API generally available, and which model should I use?
Yes. It reached GA on 2025-08-28 with gpt-realtime (OpenAI, accessed 2026-07-17). As of 2026-07-17 the catalog lists gpt-realtime-2 as default with gpt-realtime-2.1 and gpt-realtime-2.1-mini as current point releases. Pin a specific model and date it, because OpenAI ships new realtime models every few months.
How much does the OpenAI Realtime API cost per minute?
OpenAI prices gpt-realtime-2.1 at $32 per 1M audio-input tokens and $64 per 1M audio-output tokens, with the mini at $10 and $20 (developers.openai.com, accessed 2026-07-17). Independent 2026 measurements suggest roughly $0.18–$0.46 per minute uncached, falling to about $0.04–$0.10 with caching and trimmed outputs — estimates, not OpenAI figures.
The bottom line
The OpenAI Realtime API over WebRTC does not need a TURN server on the direct path — OpenAI built the firewall escape hatch into its own public endpoints, and you cannot add your own relay there. That is the part existing guides simply do not cover.
But production voice agents put a server in the loop, and that creates a WebRTC leg you own. On that leg, users behind symmetric NAT and UDP-blocked enterprise networks fail without TURN over TLS on port 443 — the same connectivity problem every serious WebRTC app eventually meets.
So build the direct path when you can, and the moment you own a media leg, put a real relay under it: free on Open Relay, or move to Metered's managed TURN service when you need 31+ regions, fixed IPs, and per-session credentials for relayed voice that actually connects.
About the author: This guide was written by James Bordane an Open Source enthusiast










Top comments (1)
Thanks for reading. I hope you like the article