Guide ~6 min read WebRTC

How Peer-to-Peer Sync Works Over WebRTC

WebRTC is best known for video calls, but the same machinery — a direct, encrypted connection between two browsers or devices — is what lets a serverless app like Driftboard sync data straight between peers. Here's what's actually happening underneath.

Two separate planes: signaling and data

It helps to split WebRTC into two jobs that are easy to conflate. The signaling plane is how two peers first find each other and agree on connection details — network addresses, media/data capabilities, and security keys. The data plane is the actual peer-to-peer channel that opens once that handshake is done. Only the first one typically touches a server at all.

The handshake: offer, answer, ICE

To connect, one peer creates a session description (an "offer") and the other responds with an "answer" — these get exchanged through a lightweight signaling server, usually over a WebSocket. Alongside that, both sides gather ICE candidates — a list of possible network paths, discovered via STUN servers that simply tell a device what its own public IP and port look like from the outside. WebRTC then tries those candidate paths and picks the one that actually works, which is most often a direct connection once NAT traversal succeeds.

Crucially, the signaling server only ever relays connection metadata — session descriptions and candidate addresses. It never sees the data that eventually flows over the connection itself, and it isn't involved at all once the two peers are talking directly.

The data channel: encrypted by default

Once connected, WebRTC's RTCDataChannel carries arbitrary binary data over SCTP, wrapped in DTLS — meaning the channel is encrypted end-to-end between the two peers by the protocol itself, not as an optional add-on. Neither the signaling server nor anything else sitting between two directly-connected peers can read the payload.

How Driftboard wires this to a CRDT

Driftboard uses y-webrtc, a provider that connects a Yjs CRDT document to exactly this kind of connection. In practice:

terminal
$ npm run electron:dev
# two peers, one workspace
 workspace created driftboard-8f2a
 invite code RQ7-K2M-91X
$ peer joins with code
 WebRTC channel open, encrypted
 synced 14 cards, 3 lists — 210ms

The honest limits of a mesh

Two peers have to be online at overlapping times for a change to travel over the data channel — there's no server relaying updates while everyone's offline. Each peer still has its own full, usable local copy at all times via IndexedDB, and the next time two peers are online together, their documents sync automatically. It just means real-time sync is opportunistic rather than guaranteed, which is the trade Driftboard makes in exchange for having no backend at all.

It's also worth noting that a fully connected mesh — every peer directly connected to every other peer — gets expensive as the peer count grows, which is why this architecture fits small, trusted groups far better than large teams.

The public signaling servers Driftboard uses by default are optional — anyone can run their own with npx y-webrtc-signaling and point the app at it, since the signaling server never touches board data either way.

Try the sync yourself

Two devices, one invite code, no server holding your data in between.