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
{
"type": "credit.received",
"id": "evt_01J9...",
"data": { }
}| Field | Type | Meaning |
|---|---|---|
| type | string | What happened. See the event catalogue. |
| id | evt_… | Stable for the life of the event. A replay reuses it — de-duplicate on this. |
| data | object | The 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.
attempt 1 2 3 4 5 6 7 8 9
delay 30s 2m 10m 30m 1h 3h 6h 12h 24h
↓
DEAD_LETTEREDPausing
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.
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.