A self-hostable gateway that captures, retries, and replays inbound webhooks.
whook sits in front of your application. Providers point at it instead of at you. It captures every inbound webhook the instant it arrives, returns a fast 2xx so the provider is happy, then forwards the event to one or more destinations with automatic retries. Every request is stored durably, so you can list, inspect, and replay anything that came in.
What you get
- Durable capture before the provider is acknowledged, so no event is ever lost.
- Exponential-backoff retries with a budget, then dead-letter for what never lands.
- Fan-out to many destinations, each with its own filter, retries, and status.
- A durable record of every request, queryable and replayable from an API or UI.
- Pluggable signature verification (Stripe, GitHub), idempotent capture, metrics.
- One static Go binary. SQLite built in, Postgres when you outgrow it.
The problem
Services like Stripe, GitHub, and Shopify notify you by sending an HTTP POST to a URL you give them. A payment succeeds, a pull request opens, an order is placed, and they POST the details to your server. That POST is the webhook, and it arrives exactly once, at a moment you do not control.
Point a provider straight at your application and it is fragile:
- If your app is down, deploying, or briefly crashing when the webhook lands, the event is lost. Many providers retry weakly or not at all.
- When processing fails, the original request is already gone, so debugging is blind.
- A provider usually allows one destination URL, but billing, email, and analytics may all need the same event, which forces hand-written fan-out glue.
- There is no simple way to replay a past webhook to reproduce a bug or recover.
How it works
Capture is decoupled from delivery. 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.
Reliable delivery. A failed delivery is retried on a deterministic exponential
schedule (a 429 honors its Retry-After). When the retry budget is exhausted the
delivery is dead-lettered, so it stops consuming resources and becomes visible for
inspection and replay.
Fan-out. One captured event resolves to every matching destination as an independent delivery track, each with its own filter and status. A failing destination never blocks the others.
Getting started
Install
Docker (prebuilt multi-arch image, recommended):
docker run -p 8080:8080 -v whook-data:/data ghcr.io/edaywalid/whook:latest
Docker Compose (builds the full stack from source):
cp .env.example .env # set WHOOK_ADMIN_TOKEN and WHOOK_SECRET_KEY
docker compose up --build -d
From source (Go 1.25+):
go install github.com/edaywalid/whook/cmd/whook@latest
whook
The gateway listens on http://localhost:8080 and stores events in a local SQLite
file by default. See Deployment for Postgres and scaling.
Send your first webhook
# 1. Register a source (the 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://your-app.example.com/webhooks"}'
# 3. Send a webhook to the gateway
curl -X POST localhost:8080/ingest/stripe \
-H 'Content-Type: application/json' \
-d '{"type":"payment.succeeded","amount":4900}'
# 4. Inspect captured events
curl localhost:8080/events
Open http://localhost:8080/ui for the dashboard: it lists events, links to a
per-event detail page (payload, delivery state, attempt history, replay button),
and has a dead-letter view.
Documentation
- Configuration: every environment variable, auth, secrets, retention, and rate limiting.
- HTTP API: endpoints, sources, destinations, the filter spec, and replay.
- Deployment: Docker, the GHCR image, Compose, building from source, and Postgres.
- Contributing: how to build, test, and submit changes.
Tech stack
- Go, single static binary
- SQLite by default (pure-Go driver, no cgo) or Postgres for scale, behind one storage interface
- Standard library HTTP, server-rendered dashboard
- dbmate-authored migrations, embedded and applied on startup
- Landing page in
web/(TanStack Start, React 19, deployed at whook-gateway.netlify.app)
Comments