Skip to main content
Webhook delivery is retried four times over about 36 minutes, and the job.complete that follows a review approval is sent once. After that the event is gone. These endpoints exist so that being unreachable for an hour is recoverable rather than permanent. Three tools, in the order you reach for them: dead letters are the short, actionable list of events that ran out of retries and were never delivered — the “what did I miss?” answer; deliveries is the full attempt-by-attempt log for diagnosing why; and replay sends an event again.

GET /webhooks/dead-letters

Every event that exhausted all four attempts and was never delivered, newest first. This is the list to reconcile against after an outage — one entry per lost event, not one per attempt. Outstanding entries only by default; a later successful delivery (including a replay) clears an entry automatically, so an empty list means nothing is outstanding.

POST /webhooks/dead-letters/[id]/replay

Resend one dead-lettered event.
Unlike the per-job replay below, this resends the exact bytes we stored when the event was dead-lettered — precisely the message you missed, not one rebuilt from the job’s current state. A successful resend clears the entry (resolved_via: "replay"); a resend that fails again leaves it outstanding, and replaying an already-cleared entry returns 409.

GET /webhooks/deliveries

Every delivery attempt we have made for your organisation, newest first.
status_code and error tell you different things. A status_code means your endpoint answered and rejected it — the problem is in your handler. An error with no status code means we never got a reply at all — a timeout, DNS, or TLS failure — and the problem is in front of your handler.
Ordered newest-first, unlike GET /jobs. That endpoint is a sync feed where ascending order keeps a watermark safe; this is a log you read when something has just broken, so the recent end is the useful one.

POST /jobs/[id]/replay-webhook

Send a webhook again for one job.
Optionally name the event; otherwise we pick the one the job’s current state warrants — job.awaiting_review if it is waiting for a reviewer, job.complete if it has finished.
Response:
You always get 200 if the replay ran; delivered reports what your endpoint did with it. Those are different failures and conflating them would be unhelpful on the one endpoint you reach for when things are already broken.

It rebuilds the payload

The delivery log records what happened to each attempt, not the bytes we sent, so a replay reconstructs the payload from the job as it stands now. Normally that is what you want — current truth rather than a stale copy — but a replayed job.complete can differ from the one you missed if the job changed in between, for example because a reviewer corrected a line and the timings were re-derived.

The destination cannot be changed

Replay always sends to the webhook_url the job was submitted with. There is no parameter to redirect it — accepting one would turn an authenticated recovery tool into a way to point our servers at an arbitrary host. To change where a job’s webhooks go, submit the next job with the new URL.

Cost

None. No transcription, no alignment, no vendor call — one database read and one outbound request. Replay draws on the read rate limit, not the submit limit.

Errors