All projects Tooling · Open source

BridgeHook

The browser tab is the tunnel. There is nothing to install because there is nothing to install.

0binaries to install — the agent is a webpage holding an SSE stream
30sCPU limit per Worker request, which is the whole reason for the Durable Object
3signature schemes actually verified — Stripe, GitHub and Shopify
01The problem it picks at

Every webhook tunnel makes you install something — an ngrok binary, cloudflared, an npm package, an SSH client. That is fine until you are on a locked-down laptop, or demoing in thirty seconds, or teaching somebody who should not have to learn a package manager before they can test a Stripe webhook.

The observation underneath BridgeHook is that the browser is already a perfectly good tunnel agent. It can hold a stream open, and it is allowed to reach localhost from an HTTPS page — so the thing you were going to install is a page you already have open.

02How it actually works The Durable Object is not a flourish. It is the only reason this runs for nothing.
Stripe / GitHub / Shopifythe sender, unchangedCloudflare Workerpublic /hook/:channel endpointDurable Objectholds the SSE, hibernates idleNeon Postgresevery event, request and responseYour browser tabthe agent: receive, forward, replylocalhost:3000your dev server, untouched

A Worker has a 30-second CPU limit per request, so an SSE stream held in a Worker would simply die. The stream lives in a per-channel Durable Object instead, which holds writers indefinitely and hibernates when idle — which is what makes leaving a bridge open all day cost nothing. The relay is a dumb pipe: store the event, hold the stream, correlate request with response.

03The protocol is four calls Which is why the browser is not special.
Create the channel

POST /api/channels returns a channel id and a secret. The secret is generated in the browser, SHA-256 hashed, and only the hash is sent.

Open the stream

GET /hook/:channelId/events holds an SSE connection open against the Durable Object, authenticated by that hash in constant time.

Forward it

The agent fetch()es http://localhost:3000 with the path, method, headers and body it was handed.

Hand the response back

POST /hook/:channelId/response correlates by id, so the original sender gets your dev server’s real answer rather than a fabricated 200.

We are not the first to relay webhooks over SSE. smee.io has done this since 2017 and inspired a lot of the wire shape here.

the BridgeHook README, on prior art
04What it does that a tunnel does not The tunnel is the boring half.

A tunnel moves bytes. This one keeps them and lets you argue with them.

  • Every event is capturedFull request and response detail in Neon Postgres — method, path, headers, body, status and latency — so the feed is a record rather than a tail.
  • Replay, and edit-then-replayRe-fire any captured event at localhost, or change the headers, body or method first. Reproducing a webhook bug stops involving the sender entirely.
  • Signature verificationStripe, GitHub and Shopify are verified against a signing secret you paste once, with constant-time comparison. Slack is detected and shown but not yet verified; Clerk and Linear are in the secrets manager only.
  • Mock-response modeAnswer the sender with a canned response without forwarding at all — which is what you want when the dev server is not running.
  • Copy as cURLTurn any captured webhook into a terminal command, which is usually the fastest route into a debugger.
05The security shape Worth stating precisely, because “a public URL into my laptop” deserves it.
The secret never leaves the browser

It is generated client-side and hashed before transmission. The relay stores a hash, compares in constant time, and could not replay your traffic if it wanted to.

The relay holds no key to your machine

It can deliver bytes to a tab that holds the channel secret. It cannot run code on your machine, and it cannot reach your machine at all on its own.

Close the tab and the bridge dies

There is no daemon left behind and no background process to remember to kill — which is the flip side of the same design that removed the install.

06Honest state

Built and open, not yet hosted — and the README is ahead of the code in two places.

  • Not deployedFour workspaces are built — the relay Worker at about 2,500 lines, the React dashboard, a Chrome extension and a Tauri desktop shell — but bridgehook.dev does not currently resolve. You can run the whole stack yourself; there is no hosted instance to point you at.
  • One releasev0.0.1. The README advertises Homebrew, Scoop and Snap channels for the desktop app; those do not exist yet.
  • No testsThere is CI, linting and typechecking, and no test files anywhere in the repository. That is the largest gap in it.
  • Six providers claimed, three verifiedCorrected on this page rather than repeated. The README is being fixed.
  • Chrome moved the goalpostsSince Chrome 142, a public page reaching localhost triggers a one-time Local Network Access prompt — so the extension, which sits outside the page sandbox, is now the sensible default rather than the tab.

The install was never load-bearing.

Everything that made a webhook tunnel feel like infrastructure — the binary, the daemon, the account — turned out to be removable. What is left is a page, a stream, and a database that remembers what came through.