Overview
WebRTC Demystified
Demo
Pros and Cons
So why was WebRTC even created?
We built it because we wanted to transmit real-time media (audio, video, and even data) between browsers in a low-latency, standardized way.
Why not just use a server? Because relying on a central server adds unnecessary latency. Data would have to travel:
Client A → Server → Client B
and back again.
Instead, with WebRTC, clients can talk directly to each other (peer-to-peer).
Imagine X wants to connect to Y. Neither knows anything about the other’s network setup.
Gather information: Each peer first collects all possible ways they might be reachable on the internet (public IPs, ports, candidate addresses, etc.).
Exchange information: This is called signaling. WebRTC itself does not care how this exchange happens. It could be via WhatsApp, Telegram, Instagram, WebSocket, or even manual copy-paste.
X shares his connection info with Y.Y does the same for X.Find the best path: After exchanging, they negotiate the shortest, most optimal path for direct communication.
And finally, X and Y connect directly.

To truly understand WebRTC, we need to break down the core networking concepts behind it.
If you have a public IP address, life is easy. You can listen on a port, share your public IP + port with someone, and they connect directly. That’s exactly what happens when you spin up a cloud server (like an AWS EC2 instance).
But most of us are not that lucky. Almost everyone is behind a NAT.
192.168.0.10 or 10.0.0.5).3.3.3.3).4.4.4.4:80), your router translates your private IP into its public IP.This translation is called NAT.
The problem: If someone outside wants to connect to you, they don’t know your private IP. They only see your router’s public IP. Unless your router explicitly forwards the traffic to your device (port forwarding), the connection will fail.
This is where STUN, TURN, and ICE come in.
STUN servers help a peer discover its public-facing IP and port.
3.3.3.3:59723.Now you know how you appear to the outside world.
But NATs are tricky. Sometimes the mapping changes, or inbound connections aren’t allowed. That’s when you need something stronger.
If peers cannot connect directly (for example, due to strict NATs or firewalls), they fall back to using a TURN server.
A TURN server acts as a relay:
X → TURN → Y
This ensures the connection works, but at the cost of extra latency and bandwidth usage (since the media flows through the server).
ICE is the framework that WebRTC uses to figure out the best possible connection path.
Each peer collects candidates (possible connection endpoints) from:
They exchange these candidates via signaling.
They test different paths and select the most efficient one (lowest latency, direct if possible).
So ICE is basically the brain that coordinates NAT traversal.
Once peers know how to connect, they still need to agree on what to exchange and in what format.
This is where SDP (Session Description Protocol) comes in.
X) describes supported codecs, media types (audio, video, data), and candidate addresses.Y) accepts or adjusts these.The exchange of SDP messages is done via signaling (remember: WebRTC doesn’t dictate how).
For this demo, we won’t create any HTML or JS files. Instead, we’ll use the browser developer console in two separate tabs or windows. One will act as Client 1, and the other as Client 2.
Open the console in the first browser tab and run:
// Create a local peer connection
const lc = new RTCPeerConnection();
// Create a data channel (this is how we'll communicate)
const dc = lc.createDataChannel("channel");
// Listen for events on the data channel
dc.onopen = () => console.log("Client 1: Data channel open!");
dc.onmessage = e => console.log("Client 1 received:", e.data);
// Whenever a new ICE candidate is found, print the updated SDP
lc.onicecandidate = () =>
console.log("Client 1 SDP (copy this to Client 2):", JSON.stringify(lc.localDescription));
// Create and set the local offer
lc.createOffer()
.then(offer => lc.setLocalDescription(offer))
.then(() => console.log("Client 1: Offer created and set!"));At this point, you’ll see a JSON object logged in the console that looks like:
{"type":"offer","sdp":"v=0..."}Copy that JSON (the whole object) — you’ll need it for Client 2.
Open the console in the second browser tab and run:
// Paste the offer from Client 1 here:
const offer = {"type":"offer","sdp":"..."};
// Create a remote peer connection
const rc = new RTCPeerConnection();
// Handle ICE candidates
rc.onicecandidate = () =>
console.log("Client 2 SDP (copy this to Client 1):", JSON.stringify(rc.localDescription));
// Handle the incoming data channel
rc.ondatachannel = e => {
rc.dc = e.channel;
rc.dc.onopen = () => console.log("Client 2: Data channel open!");
rc.dc.onmessage = e => console.log("Client 2 received:", e.data);
};
// Set the remote offer, then create and set an answer
rc.setRemoteDescription(offer)
.then(() => rc.createAnswer())
.then(answer => rc.setLocalDescription(answer))
.then(() => console.log("Client 2: Answer created and set!"));Now you’ll see another JSON object in Client 2’s console that looks like:
{"type":"answer","sdp":"v=0..."}Copy that JSON and bring it back to Client 1.
In Client 1’s console, paste the answer:
// Paste the answer from Client 2 here:
const answer = {"type":"answer","sdp":"..."};
// Apply it to Client 1's connection
lc.setRemoteDescription(answer)
.then(() => console.log("Client 1: Remote description set!"));At this point, both clients should log “Data channel open!”
Now you can send messages between the two clients!
From Client 1:
dc.send("Hello from Client 1!");From Client 2:
rc.dc.send("Hi Client 1, this is Client 2!");Each side will see the messages appear in its console. 🎉
This is the simplest possible WebRTC example you can run directly in your browser console.
WebRTC is powerful because it enables direct, real-time communication between browsers without needing plugins or heavy server infrastructure.
But it’s not magic—it’s built on a stack of clever networking techniques (NAT traversal, STUN, TURN, ICE, SDP, signaling).
Once you understand those building blocks, WebRTC stops feeling mysterious and starts feeling like an elegant engineering solution.
Phew! That was a lot to unpack, wasn't it?
If you made it this far—thank you for reading! I hope this post gave you something valuable to think about or try out.
If you found it helpful, insightful, or even just a little fun, I'd really appreciate it if you shared it on social media. It helps more than you think!
Book a 15-minute intro call below 👇