What you get
A complete service you can read in an afternoon, not a framework. Your logic goes in one function, handle(); everything around it is already done and tested.
sender --POST--> /webhook/{source}
1. read the body (size limit)
2. verify the HMAC signature -> 401 if wrong, nothing stored
3. dedupe by delivery id -> the same event twice is handled once
4. insert into SQLite -> 202 Accepted, fast
worker ----------> handle(event) retries with backoff, dead after N attempts
Features
- Three signature schemes. GitHub style, Stripe style (with a replay window) and Standard Webhooks, the scheme Polar uses. Constant-time comparison.
- Secret rotation.
WEBHOOK_SECRETtakes a comma-separated list, so old and new secrets are both accepted during a rotation and no events are dropped. - Deduplication. By delivery id (the
webhook-idheader is used automatically for Standard Webhooks) or, if there is none, by a hash of the body. Backed by a uniqueness constraint, so it survives restarts. - Durable queue. SQLite in WAL mode; the request is acknowledged after one commit. Exponential backoff, dead-letter after
MAX_ATTEMPTS, and recovery of events left inprocessingby a crash. - Concurrency.
WORKERS=Nworkers; an event is never given to two workers. - Operations.
server replayfor dead events,server purgeandRETENTION_DAYSfor old rows,/metricsin Prometheus format, a JSON access log with request ids that never contains payloads, headers or secrets. - Safe handlers.
worker.Permanent(err)parks a payload that can never succeed without burning retries. A shutdown mid-handler returns the event to the queue without losing an attempt. - Examples. A Polar
order.paidhandler and a generic JSON router, both with tests. Dockerfile and systemd unit included.
Pure Go (no cgo). Dependencies: the standard library and modernc.org/sqlite.
Measured
Method: the included load tester (make loadtest) against the compiled binary on an Apple Silicon laptop, 32 connections, 512-byte payloads, real HTTP over loopback, default durability (WAL, synchronous=FULL), 5,000 events per run, three runs.
| What | Result |
|---|---|
| Events accepted over HTTP (verify, durable insert, 202), GitHub style | about 7,200 to 9,200 per second (p50 2.7 ms, p99 about 17 ms) |
| Same, Standard Webhooks | about 9,400 per second |
| End to end, from first request to empty queue (no-op handler), 1 worker | about 3,100 to 3,200 per second |
| Queue drain, handler that sleeps 5 ms (like a network call), 1 / 8 / 32 workers | about 163 / 1,463 / 5,264 per second |
The database is the main cost, not HTTP: all SQLite access goes through one connection, so extra workers pay off when your handler waits on something such as a network call or another database.
How it was tested
make test runs go vet and 41 tests with the race detector. They cover tampered, stale, future and malformed signatures, the Standard Webhooks known-answer vector from the reference Go library, secret rotation, 401 storing nothing, duplicate deliveries, oversize and wrong-method requests, retries and backoff, dead-letter, crash recovery, ordering, many workers never processing an event twice, replay, purge and upgrading a 0.1.0 database. The compiled binary, built from the packaged zip, was also exercised with real signed HTTP requests for all three schemes, including a replay of the same delivery.
Who it is for
For developers who need to receive webhooks from Polar, Stripe, GitHub or similar and want the unglamorous parts done right: signatures, retries, dedupe and a queue that survives a crash.
It is built for one server: a single process, a single SQLite writer, events handled in order by default. Delivery is at-least-once, so your handler should be idempotent.
FAQ
Does it verify Polar webhooks?
Yes. Polar uses Standard Webhooks, which is supported, and the starter includes an example handler for Polar order events.
Can I use only the signature code?
Yes. It is available separately as a small MIT-licensed module, webhooksig; see the free tools page.
Do I need CGO or a C compiler?
No. The SQLite driver is pure Go.
Which Go version?
Go 1.24 or newer.
What is the license?
Use it in your own projects. Do not resell the source as a competing starter.
Changelog
- 0.2.1: SQLite driver updated to v1.44.0 (Go 1.24 or newer required).
- 0.2.0: Standard Webhooks, secret rotation, multiple workers, replay and purge commands,
/metrics, access log, Polar example, measured numbers. - 0.1.0: first release.
Read more
- Webhook signature checks that fail or quietly pass in Go
- A durable webhook retry queue in SQLite
- Webhook idempotency and dedupe for SQLite and Postgres
- Verify Standard Webhooks (Polar) signatures in Go
- Receiving signed webhooks in Go with a durable queue
Get it
Payment, tax and invoices are handled by Polar as merchant of record. You receive a download link after checkout.