Skip to main content
A webhook delivery fails if your endpoint:
  • Doesn’t return within 5 seconds
  • Returns a non-2xx HTTP status (3xx, 4xx, 5xx)
  • Refuses the TLS handshake
  • Closes the connection mid-response

Backoff schedule

Dispatcher config — max_attempts: 6, backoffs: [5s, 30s, 5m, 30m, 2h]: After the 6th failure, the delivery is marked failed and written to the dead-letter queue. The long tail covers temporary receiver deploys, TLS renewals, and short provider incidents without turning webhook delivery into an unbounded background job. Source: crates/dispatcher-rs/src/webhook.rs.

What “delivery succeeded” means

Any 2xx response counts as success. We don’t inspect the body. Common success patterns:
or:
Both are equally fine.

Detecting missed events

If your endpoint was down for the full retry envelope and the alert ran out of retries, inspect the webhook delivery log:
A common pattern: poll /v1/me/webhooks/$ID/deliveries every few minutes as a safety net. Idempotency (using x-pegana-event-id) means you can re-process a stale event without duplicating action.

Manual replay

If you want missed dead-letter events delivered again, hit the replay endpoint:
Replay re-attempts dead-letter rows for that subscription. Successful replay does not delete the dead-letter row; receiver-side dedup makes repeated replay safe.

TLS errors

If your TLS handshake fails (expired cert, mismatched SAN, weak cipher), the delivery counts as a failed attempt. We don’t differentiate handshake failures from HTTP errors in the retry schedule. If delivery history shows repeated handshake failures, you have a real TLS problem. We don’t accept self-signed certs.

Timeouts

Each attempt has a 5-second deadline for the full request/response cycle. Read slow? Sleeping in your handler? Doing heavy work in-band? You’ll timeout. Recommended pattern:
ACK within 100ms, then handle async. The webhook is the trigger, not the work.

Self-hosted: tune the schedule

If you’re running your own Pegana instance, the retry schedule is in crates/dispatcher-rs/src/main.rs under the WebhookConfig initialization (look for max_attempts: 6). To shorten or extend the window, change both max_attempts and the backoff array in crates/dispatcher-rs/src/webhook.rs.

Next

TypeScript example

Cloudflare Worker reference.

Rust example

axum reference.

Python example

FastAPI reference.