Video calls, screen sharing and peer-to-peer data in a browser tab all run on WebRTC. This series is for the developers who build on it and the product owners who pay for it, as of 30 Oct 2026. Part 1: what it is and how a call connects. Sources are at the bottom.
TL;DR
- WebRTC = browser APIs plus IETF protocols for real-time audio, video and data, without plug-ins.
- An official standard since January 2021: W3C Recommendation plus the IETF RFC 8825 series.
- Signaling is yours to build. The spec deliberately leaves it out.
- ICE tries direct paths first; TURN relays when nothing else works, and the relay operator pays for the bandwidth.
What is WebRTC?
MDN: WebRTC "enables Web applications and sites to capture and optionally stream audio and/or video media, as well as to exchange arbitrary data between browsers without requiring an intermediary", "without requiring that the user install plug-ins." webrtc.org says the project is supported by Apple, Google, Microsoft and Mozilla, with JavaScript APIs in the major browsers and a native library for Android and iOS. libwebrtc calls itself "a free, open software project".
From code drop to standard
0131 May 2011
Google open-sources WebRTC
"real-time voice and video on the web", to be integrated in Chrome
0227 Oct 2011
First W3C Working Draft
03Jan 2021
IETF RFC 8825 series
Overview, security considerations, security architecture
0426 Jan 2021
W3C Recommendation
"now an official standard", royalty-free
0513 Mar 2025
Latest W3C Recommendation
Includes candidate amendments
W3C publication history, the 2021 W3C/IETF press release and Harald Alvestrand's 2011 announcement.
Browser support
| Browser | RTCPeerConnection | RTCDataChannel |
|---|---|---|
| Chrome | 56 (prefixed from 23) | 24 |
| Firefox | 44 (prefixed from 22) | 22 |
| Safari | 11 | 11 |
| Edge | 15 | See MDN |
The core APIs
getUserMedia(): camera and mic. Secure contexts only (HTTPS, localhost), always asks permission, and iframes need allow="camera; microphone". Its spec, Media Capture and Streams, is a W3C Candidate Recommendation Draft.RTCPeerConnection: the connection between two peers, with RTCRtpSender/RTCRtpReceiver per track.RTCDataChannel: arbitrary data over the same connection.RTCSessionDescription,RTCIceCandidateandRTCStatsReport: the offer/answer, network candidates and stats.
How a WebRTC call connects
- 01
Capture
getUserMedia() returns tracks after the user says yes.
- 02
Offer
Caller: createOffer(), setLocalDescription(), send the SDP over your signaling.
- 03
Answer
Callee: setRemoteDescription(offer), createAnswer(), send it back.
- 04
ICE
Both sides trade candidates (host, server-reflexive, relayed) and test pairs.
- 05
Secure media
DTLS handshake, then SRTP media and SCTP data channels.
Signaling is bring-your-own
webrtc.org: "Passing SDP objects to remote peers is called signaling and is not covered by the WebRTC specification." MDN: "You can use anything you like, from WebSocket to fetch() to carrier pigeons", and the server "doesn't need to understand or interpret the signaling data". RFC 8825 puts signaling protocols "outside the scope".
What travels is SDP, which MDN calls "not truly a protocol, but a data format". JSEP (RFC 9429, April 2024, replacing RFC 8829) defines the offer/answer dance in JavaScript. A signaling server is ordinary web backend work, the kind our web development team builds.
ICE, STUN and TURN
- ICE (RFC 8445): "ICE works by exchanging a multiplicity of IP addresses and ports", then tests them. Candidates are host, server-reflexive or relayed.
- STUN (RFC 8489) tells a peer its public address. "STUN is not a NAT traversal solution by itself."
- TURN (RFC 8656) relays the media; it "comes at a high cost to the provider of the TURN server", so "it is best to use a TURN server only when a direct communication path cannot be found". MDN: needed behind symmetric NAT.
A minimal peer connection
// signalingChannel = your WebSocket, fetch(), anything
const pc = new RTCPeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
});
signalingChannel.addEventListener('message', async (msg) => {
if (msg.answer) await pc.setRemoteDescription(msg.answer);
if (msg.iceCandidate) await pc.addIceCandidate(msg.iceCandidate);
});
pc.addEventListener('icecandidate', (e) => {
if (e.candidate) signalingChannel.send({ iceCandidate: e.candidate });
});
pc.addEventListener('connectionstatechange', () => {
if (pc.connectionState === 'connected') console.log('connected');
});
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
signalingChannel.send({ offer });Do I need a TURN server?
01Users behind symmetric NAT or strict firewalls?
YES → Yes: run TURNNO ↓
02Must every call connect, on any network?
YES → Yes: TURN fallbackNO ↓
03Must peers' IPs stay hidden from each other?
YES → TURN, relay-onlyNO ↓
STUN alone may do; test real networks
A TURN entry is just { urls: 'turn:turn.example.com', username: 'USER', credential: 'PASS' } in iceServers. The cost is bandwidth: RFC 8656 says the server "typically needs a high-bandwidth connection". Part 2 covers encryption, codecs and media servers.
The whole series
- Part 1: What WebRTC is and how a call connects (this post)
- Part 2: Encryption, codecs, and SFU vs MCU vs mesh
- Part 3: WHIP, E2EE, MoQ, security and debugging
