← all posts

How peer-to-peer web apps actually work

2026-05-20

The first time someone hits one of my p2claw apps their browser opens a peer-to-peer connection to the mac mini in my basement, through their NAT and through mine, and starts speaking HTTP over a WebRTC data channel. Once the connection is established there's no server in between.

Here's how it works.

The setup

I've got an app running on my mini at localhost:3000, the agent may have kicked it off with something like npm run start & or equivalent. It doesn't matter we just need something listening on localhost. I also have a small daemon running on the mini that I installed via the p2claw skill, we call this daemon the box agent. The box agent does four things:

That's the steady state. The box is online, has an identity, is reachable for handshakes, it exposes apps.

What happens when someone visits

Now my wife taps the link to the app from her phone via sim:

  1. DNS. Her browser resolves dog-purplecrab.p2claw.com. It points at our edge which handles TLS termination for the parent domain.
  2. Edge serves a bootstrap. The edge doesn't know anything about the dog app. What it knows is "this is a peer subdomain, return the bootstrap." The bootstrap is a tiny HTML page with about 50KB of JS. The bootstrap is the same for every p2claw URL. Static, public, identical for everyone.
  3. Bootstrap parses the URL. From dog-purplecrab.p2claw.com, it extracts the alias (purplecrab) and the app name (dog). It calls a small endpoint on our coordination service: "I want to talk to peer purplecrab."
  4. Signaling. The coordination service notifies my mini (via the control connection) that someone wants to connect. It also gives my wife's browser TURN info in case the direct path fails. Both sides start exchanging WebRTC handshake messages, SDP offers and answers, ICE candidates, through the coordination service. The coordination service is just a relay for these blobs.
  5. Hole-punching. Each side has a list of possible network addresses where the other might be reachable. They start firing UDP packets at each other simultaneously. With high probability, both NATs see outbound packets first, open mappings, and then accept the inbound packets that follow. Most of the time on residential connections, this just works. When it doesn't, traffic falls back through a TURN relay (we use Cloudflare's), which forwards encrypted bytes it cannot read.
  6. WebRTC data channel opens. Now my wife's browser and my mini have a reliable, ordered, encrypted byte stream between them. Think of it as a TCP socket that happens to traverse NATs. WebRTC handles encryption automatically with DTLS; the keys are negotiated peer-to-peer.
  7. But what happens when the app makes a request back to the server? We need something to intercept and route these through webrtc. The bootstrap installs a Service Worker scoped to dog-purplecrab.p2claw.com. Then it reloads the page.
  8. Reload navigates "into" the app. Her browser tries to fetch dog-purplecrab.p2claw.com/. The Service Worker intercepts the fetch. Instead of going to the network, it sends an HTTP-shaped frame over the WebRTC data channel: "GET /, here are my headers."
  9. The box translates and serves. On my mini, the box agent receives the frame, makes a local HTTP request to localhost:3000/, gets the response, frames it, sends it back over the data channel. The app on :3000 doesn't know anything about p2claw it's just a regular web app.
  10. Service Worker returns the response. The browser thinks it just made a normal HTTP request. React renders. The dog app loads.

Subsequent fetches [JS bundles, for /api/puppy/name, for an image] get intercepted the same way. The whole app behaves as if served from a normal origin. The transport underneath is a WebRTC data channel running over hole-punched UDP between my computer and her phone.

The economics

Here's why this matters beyond the technical novelty. In the conventional app hosting model [Vercel, Netlify, Cloudflare Tunnel, etc.] every byte of your app's traffic flows through the host's infrastructure. They pay for that bandwidth. As your app gets popular, their costs go up linearly. This is why every host has bandwidth caps, fair-use policies, and overage bills.

p2claw doesn't have this property. The data flows from the visitor's browser to your box, directly, over your residential ISP's bandwidth (which you're paying for anyway), or through your VPS's network [Hetzner VPSs give up to 20TB of free egress!]. Our infrastructure serves a bootstrap, brokers the handshake, and that's it. Our cost per active app is low and fixed. This makes permanent free URLs a thing. From an economics perspective p2claw's architecture is closer to Tailscale which can also support N free VPNs because of the way its costs are structured.

What our infrastructure sees and doesn't

If privacy is important to you:

Because of the architecture, we couldn't read your app traffic if we wanted to.

architecture

LOL, the future

WebRTC has been in browsers since 2013. Service Workers since 2014. Hole-punching since the early 2000s. TURN since forever.

We associate webrtc with video calls. Service workers were built for offline apps. We think of NAT punch through in relation to BitTorrent and games.

The pieces have all been there for a while, p2claw just brings them together suit the needs of AI coding agents or the modern vibecoder.

In the words of Justin Uberti [one of the creators of webrtc] on x.com last week:

LOL, the future is hard to predict.

lol-the-future

So that's what p2claw is, a thing that uses a bunch of standards slightly off-label to give us: standard web apps talking to standard browsers, with no install for the visitor, via a peer-to-peer data path, with permanent URLs, for free.