Never lose a webhook again.
whook captures every inbound webhook the instant it lands, returns a fast 2xx, then delivers with retries and replay.
Built for the webhooks you already receive
Pointing a provider straight at your app is fragile.
The provider sends once, at a moment you do not control. Miss it and the event is gone for good.
The event is gone when your app is down
A deploy, a crash, a slow restart. The provider sends once, retries weakly or not at all, and the "payment succeeded" never lands.
Failures leave no trace
When processing throws, the raw request is already gone. You debug blind, with no record of what the provider actually sent.
One URL, many consumers
Billing, email, and analytics all want the same event, but the provider allows a single destination. So you hand-write fan-out glue.
No way to replay
You fixed the bug. Now you need yesterday’s event back, and there is no button for that.
How it works
Capture is decoupled from delivery. That is the whole trick.
An inbound request is saved durably before it is acknowledged. Delivery happens afterward, on whook’s own schedule, so a destination being down never costs you an event.
- 01
Capture
The raw request is written to disk the instant it arrives, then whook returns a fast 2xx.
- 02
Store
Every event is kept durably with its headers, body, and source, queryable and replayable.
- 03
Deliver
Workers forward to your services on their own schedule, retrying until success or dead-letter.
Retries that back off, then dead-letter.
Transient failures are retried on a deterministic exponential schedule. A 429 honors its Retry-After. When the budget runs out, the delivery is dead-lettered so it stops burning resources and becomes visible.
- 4xx that will never succeed are marked permanent, not retried.
- The pending queue lives in the database, so it survives a restart.
- Replay a dead-lettered event once you have fixed the cause.
One event, many destinations.
A captured event fans out to every matching destination as its own delivery track. Billing, email, and analytics each get their own status, so one failing consumer never blocks the others.
- Per-destination filters route only the events each service wants.
- Each track keeps its own attempt history and dead-letter state.
Everything you would otherwise hand-roll.
The pieces you normally bolt together yourself, in one process you run next to your app.
Durable capture
saved before 2xxEvery request is written to disk before the provider is acknowledged, so an event is never lost to a restart.
Backoff retries
exponential + budgetFailed deliveries retry on a deterministic schedule until they succeed or exhaust the retry budget.
Dead-letter and replay
recover anytimeExhausted deliveries are set aside, queryable, and replayable once you have fixed the cause.
Signature verification
Stripe, GitHubPluggable per-provider verifiers. Forged requests are captured for inspection but never delivered.
Fan-out with filters
one event, manyAn event resolves to many destinations, each with its own filter, retries, and dead-letter state.
Idempotent capture
Idempotency-KeyA provider dedup key collapses re-sent events into one, even under a concurrent race.
Prometheus metrics
GET /metricsIngest rate, write latency, delivery outcomes, and dead-letter volume, ready to scrape.
One static binary
docker compose upA single Go binary with SQLite built in. Postgres when you outgrow it. Nothing else to run.
Quickstart
Point your provider at whook instead.
Register a source, add a destination, and send. Everything that comes in is captured, listed, and replayable.
Read the docs# 1. Register a source (your provider integration)$ curl -X POST localhost:8080/sources -d '{"name":"stripe"}' # 2. Point it at one or more destinations$ curl -X POST localhost:8080/destinations \> -d '{"source":"stripe","url":"https://app.internal/hooks"}' # 3. Send a webhook to whook$ curl -X POST localhost:8080/ingest/stripe \> -d '{"type":"payment.succeeded","amount":4900}'{"event_id":"evt_9f3a21c8b4e07d56"} # 4. Inspect and replay anything that arrived$ curl localhost:8080/eventsOwn your inbound webhooks.
Self-host whook next to your app. One binary, your database, your data. No accounts, no per-event pricing.
# pull, configure, run
$ docker compose up -d
whook listening on :8080
- Single Go binary
- SQLite or Postgres
- Prometheus metrics
- MIT licensed