How the proxy stays out of your way
The few rules we follow on the webhook hot path so that Hookman adds almost nothing to a request's latency.
A webhook proxy has one job that matters more than any other: don’t make webhooks worse. Providers like Stripe time out and retry, so anything we add to the path is paid for by every delivery. Here are the rules the proxy, a Cloudflare Worker, follows.
Config comes from KV, and only KV
When a webhook arrives for hookman.dev/w/acme/payments, the Worker reads one key, route:acme/payments, from Workers KV. It holds everything needed to route: the rules and the URL of the active deployment. The databases never come into it.
That’s deliberate. The dashboard and API keep the real data in Postgres, and when you publish a change they write the result into KV. The proxy only ever reads.
No auth on the way in
Webhooks come from third parties who can’t carry our credentials. Signature checking is your application’s job and it still gets to do it, because we forward the request exactly as it came.
Read the body once
Payload-based routing needs the JSON body. When a rule needs it, we parse it once and keep it. When the routing key is in a header or the query string, there’s no need to read the body first, so the lookups run in parallel.
Mirror the response
Whatever your deployment answers, the sender sees: same status, same body, same headers, with a few x-hookman-* markers added. If the deployment can’t be reached at all, there is nothing to mirror, so the sender gets a 502 and we keep the payload for replay.
Everything else happens afterwards
Analytics, log writes and payload storage use waitUntil, so they run after the response has gone. A slow log write never delays a delivery. Fan-out copies work the same way.
Why it matters
None of this is clever. It’s a short list of things not to do on the hot path, written down so we keep to them as the product grows. Features like inboxes live in separate Workers for the same reason: they can’t slow a webhook down if they’re not on its path.