Webhooks delivers Formance platform events to user-configured HTTP endpoints with HMAC signing, durable persistence, bounded retries, delivery visibility, and replay.
| Command | Role |
|---|---|
serve |
Runs the authenticated HTTP API. Pass --worker to embed the worker. |
worker |
Consumes broker events and dispatches webhook deliveries. |
migrate |
Applies PostgreSQL schema migrations. |
backfill-deliveries |
One-shot upgrade command that imports outstanding data from the pre-deliveries attempts table. |
Kafka / NATS
│
▼
Consumer ── transactionally inserts pending deliveries ──▶ PostgreSQL
│ │
└──────────── ACK after commit ▼
Dispatcher
│
▼
Customer endpoints
The worker has one processing model:
- The broker consumer normalizes an event and inserts one
pendingdelivery for every matching active config. (event_id, config_id)is unique, so broker redelivery does not duplicate persisted work.- The broker message is acknowledged only after the PostgreSQL transaction commits.
- The dispatcher claims due deliveries with
FOR UPDATE SKIP LOCKED. - The HTTP result and the delivery transition are committed atomically with an append-only attempt record.
See docs/architecture.md, docs/message-processing.md, and docs/retry-mechanism.md for details.
configsstores webhook subscriptions, endpoints, event filters, activation state, and signing secrets.deliveriesstores one current-state row per event and config.delivery_attemptsstores the append-only history of outbound HTTP calls without copying signing secrets.replay_requestsstores short-lived idempotency records for replay commands.
Delivery states are pending, delivering, succeeded, failed, and cancelled.
GET /deliverieslists delivery metadata with cursor pagination and omits payloads.GET /deliveries/{id}returns one delivery including its payload.GET /deliveries/{id}/attemptsreturns its attempt history.POST /deliveries/{id}/replayrequeues one failed or pending delivery.POST /deliveries/replayrequeues a bounded page of deliveries.
Replay commands require Idempotency-Key. Failed deliveries receive a fresh retry generation; pending deliveries are only expedited.
The runtime never reads or writes the old attempts queue. Upgrades from versions that used it must be coordinated by the Operator:
- Stop all old workers and wait for their termination.
- Apply the new schema migrations.
- Run
webhooks backfill-deliveriesuntil it completes. - Deploy the new Webhooks version.
- Recreate the workers.
The backfill is resumable and idempotent. It remains in the binary only as an upgrade adapter; there is no runtime pipeline selector and no supported mixed-worker state.
- Network errors, timeouts,
408,429, and5xxare retryable. - Other
4xxresponses are terminal. - Backoff is exponential from
--min-backoff-delayto--max-backoff-delay. --max-attemptsand--abort-afterbound each retry generation.Retry-Afteris honored without bypassing the configured bounds.
nix develop --impure --command just pre-commit
nix develop --impure --command just testsThe E2E suite runs against PostgreSQL and NATS and exercises the generated Go SDK in pkg/client.