Skip to content

Failed Deliveries (Dead-Letter UI) ​

When an outbound webhook delivery fails — and exhausts its retries — it lands in the Failed deliveries tab on /developer. This tab is your visibility into "what didn't ship + why."

What counts as a failure ​

A delivery is a failure if, after the full retry schedule (1m → 5m → 25m, 3 attempts), the destination is still non-2xx. Specifically:

HTTP statusTreatment
2xxSuccess — never a failure
3xxTreated as success on 2xx follow (we don't auto-follow redirects on POST — endpoint should not 3xx)
4xxTerminal — no retries, immediate failure (bad request shape, auth missing, etc.)
5xxRetried per schedule, then failure
Network errors (timeout, DNS, TLS)Retried per schedule, then failure

So a single 5xx blip is auto-recovered. A persistently broken endpoint piles up in dead-letter.

What you see ​

The Failed deliveries tab shows a table:

ColumnUse
Eventlead.created, note.created, etc.
SubscriptionThe destination URL config that triggered the delivery
Attempted atWhen we last tried
HTTP statusThe final error code (or "network error")
ErrorThe body / error message returned
AttemptWhich retry was the final (typically 3)
ActionsReplay / Dismiss

Click a row to expand the full request body, response headers, and any error trace.

Replay ​

The Replay button (paper-plane icon) re-queues a fresh delivery. We:

  1. Mark the old delivery as dismissed_at = NOW() (so it disappears from the failed list).
  2. Insert a brand-new delivery record + dispatch it.
  3. The new delivery goes through the normal retry schedule.

Idempotency note: the destination receives the same payload as before (same id, same event, same data). On your side, dedupe by id is critical — replaying after a successful manual fix could otherwise double-process.

Dismiss ​

The Dismiss button (trash icon) sets dismissed_at = NOW() without retrying. The row disappears from the active failed list (it's a soft delete — still in webhook_deliveries for audit).

Use Dismiss when:

  • The destination is genuinely gone (you're switching to a new endpoint)
  • You manually re-processed the event some other way
  • You don't care about this specific failure

Bulk actions ​

For mass cleanups (e.g., your endpoint was down for 24h and there are 500 failed deliveries), select all rows + bulk Replay or bulk Dismiss. Replay all goes serially through the queue, so a flood of bulk replays won't DoS your endpoint.

Filters ​

The Failed tab supports filters:

  • Subscription — narrow to one destination URL
  • Event — narrow to a specific event type
  • Status — narrow to 5xx vs 4xx vs network errors
  • Date range — failures within X

Useful for "what's been failing this week" investigations.

Why a delivery shows here vs in the regular delivery log ​

The regular delivery log (under each subscription) shows every delivery, including successes. The Failed tab is the subset that failed terminally. Both back the same webhook_deliveries table — the Failed tab filters WHERE status NOT IN (2xx range) AND dismissed_at IS NULL.

Permission ​

The Failed deliveries tab is gated by api.manage (admins auto). The Replay and Dismiss actions are gated the same way.

Common causes of failures ​

PatternLikely cause
Lots of 401s from one endpointDestination auth changed (rotated API key, etc.). Update endpoint to accept anonymous + validate by HMAC.
422 with detailed bodyYour endpoint rejects valid Kinderpedia payloads. Inspect; fix endpoint logic.
500 spikes timed to deploysYour endpoint was redeploying. Replays after deploy succeed.
Network errors onlyFirewall / IP whitelist blocking Kinderpedia's egress IPs.
200s in logs but Kinderpedia shows failuresYour endpoint returns 200 late (timeout >30s). Speed up the handler.

What's stored ​

Each delivery row carries:

  • The full request body (what we sent)
  • The destination URL + secret hint (no full secret, just the last 4 chars)
  • The full response headers + body (so you can see exactly what the destination said)
  • All retry attempt timestamps

Storage retention is 30 days for successful deliveries, 90 days for failed deliveries (longer so you have time to investigate).

Audit ​

Every Replay + Dismiss action fires an audit_logs entry:

  • webhook_delivery_replayed — delivery id, subscription, who clicked
  • webhook_delivery_dismissed — same shape

Kinderpedia Admissions Help Center