Webhooks

Ingest commits, delivery drains. An event is written in the same transaction as the thing that caused it, so it cannot be lost by a crash between the two — and it is retried until it lands or dead-letters.

The envelope

json
{
  "type": "credit.received",
  "id": "evt_01J9...",
  "data": { }
}
FieldTypeMeaning
typestringWhat happened. See the event catalogue.
idevt_…Stable for the life of the event. A replay reuses it — de-duplicate on this.
dataobjectThe payload for that type. Money is always { currency, value }.

Headers carry the signature: nineveh-signature: t=<unix ms>,v1=<hex>. The body is byte-stable per event id, so a re-delivery verifies identically to the first attempt.

What counts as delivered

2xx within ten seconds. Everything else is a failed attempt: 3xx, 4xx, 5xx, timeout, TLS failure, DNS failure. A 410 Gone is special — it disables the endpoint rather than retrying it, on the grounds that you have told us it is finished, not failing. A disabled endpoint has to be re-verified, not resumed.

The retry schedule

Nine attempts over roughly 47 hours, each with ±20% jitter so a fleet of workers does not retry in lockstep. After the ninth, the event is dead-lettered and someone is told.

text
attempt   1     2     3      4      5     6     7     8      9
delay    30s    2m   10m    30m     1h    3h    6h   12h    24h
                                                          ↓
                                                   DEAD_LETTERED
The retry is of the notification, never of the payment. The money moved when the credit landed and the ledger already says so. Nothing about a failing endpoint can move it again, and nothing about a successful retry moves it twice.

Pausing

Pause an endpoint during a deploy or an incident. Events queue; nothing dead-letters while paused, however long it lasts. Resume and they drain in order. A suspended product behaves the same way.

Replay

Replay one event by id, or every dead letter at once. A replay reuses the event id and increments the attempt count — it is another attempt at the same event, not a new event. Your idempotency on the id is what makes that safe, which is why it is the first thing the Quickstart asks you to build.

http
POST /v1/deliveries/evt_01J9.../replay
POST /v1/deliveries/replay-dead-letters

Ordering

Events for the same account are delivered in the order they occurred. Different accounts may interleave — do not rely on a global order, and do not rely on receiving an event for account A before one for account B just because it happened first.

Health

The console shows, per endpoint: success rate over the last 24 hours, backlog, oldest undelivered event, and the last 2xx. The success rate is over attempts, not events — an event that landed on the sixth try is one success and five failures, because that is what your endpoint actually did.

Debugging a failure

Every attempt records the status code, latency, an error class, and a capped snippet of what your endpoint returned, with anything that looks like an account number or a token masked out. If your 500 had a useful body, it is on the delivery detail screen.