Peer-to-peer media with WebRTC
Open in the interactive app: practice, quiz, mock interview →In one minute
- WebRTC gives browsers and apps real-time audio, video and data channels, ideally peer-to-peer over UDP with mandatory encryption.
- It does not define signalling: you build your own channel (usually WebSocket) to exchange session descriptions and network candidates.
- ICE, with STUN and TURN, finds a working path through NATs and firewalls; a minority of sessions (10 to 20% is the figure usually quoted, far more on locked-down corporate networks) need a TURN relay.
- Media runs over SRTP with congestion control that adapts bitrate to the network, trading quality for latency.
- Beyond two or three participants, peer-to-peer meshes collapse and you need a media server, usually an SFU.
How it works
Signalling and SDP
Each peer creates an offer or answer in SDP format describing codecs, media tracks, encryption fingerprints and ICE credentials. Your application carries these between peers over any channel. WebRTC is agnostic here, which is why every product has its own signalling server handling rooms, auth and presence. The one place a standard now exists is broadcast ingest: WHIP (RFC 9725, March 2025) sends the SDP offer as an HTTP POST and gets the answer back in the response, so an encoder such as OBS can publish to any compliant media server (RFC 9725).
The stack underneath is all standardised and was refreshed around 2018 to 2020, so older articles cite superseded numbers: ICE is RFC 8445 (replacing 5245), STUN is RFC 8489 (replacing 5389), and TURN is RFC 8656 (replacing 5766). The browser API itself is a W3C Recommendation, last revised in March 2025 (W3C).
ICE, STUN and TURN
Most devices sit behind NAT, so a peer does not know its public address. ICE gathers candidates:
- Host: the device's local addresses. Since Chrome 76 (2019), browsers hide private IPs behind random
.localmDNS names unless the page already has camera or microphone permission (Chrome PSA). - Server reflexive: the public IP and port as seen by a STUN server, learned by sending it a request.
- Relay: an address on a TURN server that forwards traffic for you.
Peers exchange candidates (trickle ICE sends them as they are found), pair them, sort pairs by priority, and run connectivity checks: STUN binding requests sent directly on each pair. The first successful high-priority pair is nominated. Host and reflexive pairs work across most home NATs. Symmetric NATs and strict corporate firewalls defeat hole punching, and then only the relay works, often over TCP or TLS on port 443.
Media transport
Media is encrypted with SRTP using keys negotiated by DTLS over the same path. Audio is typically Opus at 20 to 40 kbps; video VP8, VP9, H.264 or AV1 at 0.3 to 2.5 Mbps. Congestion control estimates available bandwidth from packet delay trends and loss and adjusts the encoder bitrate every few hundred milliseconds. Loss is handled with NACK retransmissions for video, forward error correction for audio, and jitter buffers, all tuned to keep end-to-end delay below roughly 150 to 300 ms. Data channels run SCTP over DTLS and can be reliable or unreliable, ordered or unordered (RFC 8831).
Topologies for groups
In a mesh, each participant sends its stream to every other one: upload grows linearly with participants. An SFU (selective forwarding unit) receives one upload per participant and forwards streams to others without decoding, often choosing among simulcast layers (for example 180p, 360p, 720p) per receiver based on their bandwidth. An MCU decodes and mixes into one stream, which is cheap for clients but very CPU-heavy on the server.
Down the stack
Layer 1The contract
The WebRTC API promises an encrypted connection between two endpoints that both consented to it, with media delivered in real time on a best-effort basis: late data is worth less than missing data, so the stack trades completeness for latency. Data channels let you choose per channel between reliable and unreliable, ordered and unordered delivery. Connection state is reported through events (checking, connected, disconnected, failed), and nothing guarantees a path exists at all; without TURN, some pairs of networks simply cannot connect.
Layer 2The mechanism
Candidates appear in SDP as lines like a=candidate:1 1 udp 2130706431 192.168.1.20 54400 typ host: foundation, component, transport, priority, address, port and type. Connectivity checks are STUN Binding requests carrying USERNAME (the two ICE ufrags joined), MESSAGE-INTEGRITY (HMAC-SHA1 keyed with the ICE password), PRIORITY, and ICE-CONTROLLING or ICE-CONTROLLED with a tie-breaker; the controlling side adds USE-CANDIDATE to nominate. Pairs move through Frozen, Waiting, In-Progress, Succeeded and Failed states (RFC 8445), and consent checks repeat about every 5 seconds (randomised by plus or minus 20%) so a peer that stops answering loses consent, and the sender must stop, after 30 seconds (RFC 7675). DTLS then runs over the nominated pair, each side verifying the certificate fingerprint from the SDP, and exports SRTP keys. Media rides RTP, with a 12-byte header holding sequence number, timestamp and SSRC. RTCP feedback carries NACKs, PLI keyframe requests and transport-wide congestion control reports that drive a delay-based bandwidth estimator. TURN relays use Allocate, CreatePermission and ChannelBind, after which data uses a 4-byte ChannelData header instead of a 36-byte Send indication (RFC 8656).
Layer 3The machine
Packets are kept near 1,200 bytes to avoid IP fragmentation across unknown paths. An Opus frame of 20 ms is 50 to 100 bytes of payload, while RTP, the SRTP auth tag, UDP and IPv4 headers add about 50 bytes, so audio pays roughly 40% header overhead at 50 packets per second. Video at 1.2 Mbps is about 125 full packets per second, and a keyframe can be ten times a delta frame, producing bursts that congestion control must absorb. NATs expire idle UDP bindings in 30 to 120 seconds, which is why STUN keepalives flow every 15 to 25 seconds; ICE says agents should use 15 seconds and must not go lower (RFC 8445). Jitter buffers hold 20 to 200 ms of audio, adapting to measured arrival variance. Encoding runs on hardware encoders where available, because a 720p software encode can take a full CPU core on a phone.
Why it's built this way
- Conversation tolerates about 150 ms of one-way delay but not stalls, so media uses UDP, limited retransmission and adaptive bitrate.
- IPv4 scarcity put most devices behind NAT, so ICE probes many paths and keeps TURN as a last resort.
- Browsers must not become tools for sending traffic to arbitrary hosts, so connectivity checks need credentials and encryption is mandatory.
- Network quality varies wildly within one call, so congestion control adjusts every few hundred milliseconds and SFUs pick simulcast layers per receiver.
- Upload bandwidth is scarcer than download, so group calls send one stream up to a server rather than one per peer.
Diagram
sequenceDiagram
participant A as "Peer A"
participant S as "Signalling server"
participant T as "STUN/TURN"
participant B as "Peer B"
A->>T: STUN binding request
T-->>A: your public addr 203.0.113.5:40100
A->>S: offer SDP + candidates
S->>B: offer
B->>T: allocate relay
B->>S: answer SDP + candidates
S->>A: answer
A->>B: ICE connectivity checks on candidate pairs
B-->>A: check succeeds, pair nominated
A->>B: DTLS handshake, then SRTP mediaWorked example
A telehealth product runs 5,000 concurrent one-to-one video calls at 1.2 Mbps per direction, and plans group sessions of 8.
TURN sizing: if 15% of calls fall back to relay, that is 750 calls. A relayed call carries both directions in and out of the TURN server: 750 x 2 x 1.2 Mbps = 1.8 Gbps in and 1.8 Gbps out. At a typical cloud egress price of around $0.05 to $0.08 per GB, 1.8 Gbps for an 8-hour business day is about 6.5 TB per day, or roughly $330 to $520 daily, so TURN servers are placed in regions near users and on providers with cheaper bandwidth.
Group sessions: in a mesh of 8, each participant uploads 7 streams at 1.2 Mbps, 8.4 Mbps, which most home uplinks and phone batteries cannot sustain. With an SFU and simulcast, each participant uploads about 1.2 + 0.5 + 0.15 = 1.85 Mbps once, and downloads 7 streams chosen by layout, perhaps one 720p speaker at 1.2 Mbps and six 180p tiles at 0.15 Mbps, about 2.1 Mbps. At about 17 Mbps out per room, one 10 Gbps SFU node handles a few hundred rooms.
In the wild
- Discord, 2018: WebRTC with the peer-to-peer parts removed. Discord served more than 2.6 million concurrent voice users from 850+ voice servers in 13 regions, with over 220 Gbps and 120 million packets per second of egress. Every client connects to a Discord relay, so native apps drop ICE, replace DTLS/SRTP with Salsa20 encryption and SDP with a join message of about 1,000 bytes; browsers still get standard WebRTC through synthesized SDP. Routing through a custom C++ SFU hides users' IPs from each other, a defence against DDoS (Discord).
- Chrome, 2019: private IPs disappear from host candidates. Websites had been using WebRTC to harvest users' private IP addresses, likely for tracking. Chrome replaced them with random mDNS hostnames, rolling out to 50% of users and then fully with Chrome 76 in August 2019, exempting pages with camera or microphone permission. Google measured about 2% fewer successful Chrome-to-Chrome peer-to-peer connections, with larger effects on some third-party ICE stacks (Chrome PSA, IETF draft).
Trade-offs
| Option | Strengths | Weaknesses | Choose when |
|---|---|---|---|
| Peer-to-peer mesh | No media servers, lowest latency | Uplink grows with N, poor beyond 3 to 4 | One-to-one calls |
| SFU | Scales to dozens, simulcast per receiver | Server bandwidth, more downlink per client | Group video, webinars with interaction |
| MCU | One stream per client, easy recording | Heavy server CPU, added latency | Low-power clients, legacy endpoints |
| TURN always | Predictable connectivity, hides IPs | Bandwidth cost and added hop | Strict privacy or enterprise networks |
Pitfalls in production
- No TURN, or TURN only on UDP. Calls fail on corporate networks. Deploy TURN over UDP, TCP and TLS on 443.
- Static TURN credentials. Leaked credentials turn your relay into a free bandwidth proxy, and an open relay can also reach your internal network: restrict which peer addresses a TURN server will relay to. Use short-lived, HMAC-derived credentials from your backend.
- Parsing candidates as IP addresses. mDNS host candidates arrive as
uuid.localnames, and some third-party ICE stacks lost direct connectivity when Chrome switched in 2019. Accept hostnames, or rely on server-reflexive and relay candidates. - Ignoring ICE restarts. A phone switching from Wi-Fi to cellular drops the call. Detect connection state changes and trigger an ICE restart.
- Mesh for groups. Works in testing with 3 people, fails with 6. Move to an SFU before group sizes grow.
- Measuring only setup success. Calls connect but are unusable. Collect stats (round-trip time, jitter, packet loss, frames dropped) per call and alert on quality.
Leadership lens
- Decide early whether to build on an open-source SFU, a managed provider, or plain peer-to-peer; the operational burden differs by an order of magnitude.
- TURN bandwidth is often the largest infrastructure cost; model it with your relay rate before launch.
- Ask in review: how does this behave on a lossy 3G link and behind a corporate proxy?
- Recording, compliance and moderation usually force media through a server, which affects the topology choice.
Check yourself
Why can two peers both behind home NATs often connect without TURN?
Show answer
Each learns its public mapping via STUN, and simultaneous connectivity checks open NAT bindings in both directions, so packets get through.
Why does an SFU scale better than a mesh for the sender?
Show answer
The sender uploads one set of streams to the SFU instead of one copy per participant, so upload stays constant as the room grows.
What is signalling and why does WebRTC leave it to you?
Show answer
It is the exchange of SDP offers, answers and candidates. Apps already have their own identity, rooms and transport, so the standard avoids prescribing one.
Sources and further study
Primary
- W3C: WebRTC Recommendation: the browser API, connection states and statistics.
- RFC 8489: STUN: the binding request, attributes and message integrity used by ICE checks.
- RFC 7675: STUN usage for consent freshness: why media stops 30 seconds after a peer goes silent.
- RFC 9725: WHIP: standard HTTP signalling for publishing a WebRTC stream.
Systems docs
- Chromium: Hiding local IPs with mDNS (PSA): what changed in host candidates and who is exempt.
- Discord: WebRTC at 2.5 million concurrent voice users: which parts of WebRTC a large SFU deployment keeps and replaces.
Books
- High Performance Browser Networking, Ilya Grigorik, chapter 3 "Building Blocks of UDP": UDP and NAT, and how STUN, TURN and ICE get packets through (read free).
- Computer Networking: A Top-Down Approach (8th ed.), Kurose and Ross, chapter 4 "The Network Layer: Data Plane": NAT and middleboxes, the reason ICE exists.
Other curricula
- roadmap.sh: Backend: covers WebSockets, SSE and polling under "Real-Time Data" but not WebRTC, so peer media is left to specialist material.
- donnemartin/system-design-primer: explains when to choose UDP for real-time media but stops before NAT traversal.
Watch
- WebRTC Crash Course (Hussein Nasser): NAT, STUN, TURN, ICE and SDP explained with a demo.
- WebRTC in 100 Seconds (Fireship): fast overview plus a minimal video chat build.
Free resources
- WebRTC for the Curious: a free book on every protocol layer, written by implementers.
- RFC 8445: ICE: candidate gathering, priorities and connectivity checks.
- RFC 8656: TURN: how relays allocate and forward traffic.
- MDN: WebRTC API: browser API reference and guides.
- High Performance Browser Networking: WebRTC: Ilya Grigorik's chapter on transport and performance.