Skip to main content

Delivery & retries

Delivery guarantees

Delivery is at-least-once. Slothbox attaches a stable webhook-id to each event and reuses it verbatim across every retry, so your endpoint may see the same event more than once. Dedupe on webhook-id to make handling idempotent.

Respond 2xx within 20 seconds — each attempt has a hard 20-second deadline covering connect, TLS, and the response. Any non-2xx response, a timeout, or a connection error counts as a failed attempt.

Retries

Failed deliveries are retried with an escalating backoff over roughly 8–9 hours. The waits before each retry are 30s, 2m, 10m, 30m, 1h, 2h, 5h — each jittered down by up to half — for eight attempts in total; after the last one the delivery is marked exhausted. (Endpoints with ordered delivery retry on a shorter cadence.) Across every attempt the webhook-id stays the same; the webhook-timestamp header is re-stamped each time, so keep any timestamp tolerance relative to that header.

Auto-disable

Every delivery that exhausts all of its retries bumps the endpoint's consecutive-failure count; any successful delivery resets it. When the count reaches 5 and the failing streak has lasted at least 24 hours (measured from the streak's first exhausted delivery — so a brief outage, even one that exhausts a few deliveries, won't disable a healthy endpoint), Slothbox disables the endpoint and stops delivering to it, including retries already in flight. The org owners are emailed. Slothbox also emits a webhook.endpoint.disabled event, which fans out to any other enabled endpoint in your org that subscribes to it (or to *) — the just-disabled endpoint no longer receives anything. Re-enable a disabled endpoint from Settings → Webhooks (or by PATCH-ing it with { "status": "enabled" }).

Replay

Every past delivery is kept in the delivery log. To replay one over the API, list the deliveries for the endpoint to find its deliveryId:

curl https://api.slothbox.dev/organizations/org_123/webhooks/whe_01HXYZ/deliveries \
-H "Authorization: sk_..."
{
"deliveries": [
{ "deliveryId": "msg_01HXYZ", "eventType": "environment.started", "status": "exhausted", "attemptCount": 8 }
],
"cursor": "..."
}

Then redeliver it (or replay any delivery straight from Settings → Webhooks):

curl -X POST \
https://api.slothbox.dev/organizations/org_123/webhooks/whe_01HXYZ/deliveries/msg_01HXYZ/redeliver \
-H "Authorization: sk_..."
{ "webhookId": "msg_NEW01HXYZ" }

A replay re-sends the original event's type and data as a brand-new delivery — with a fresh webhook-id and webhook-timestamp, not the original ones (the response above is that new id). A consumer that de-duplicates on webhook-id will therefore treat a replay as a new event and process it again, which is usually what you want when replaying a delivery your endpoint missed or mishandled.

Rotating the signing secret

Rotate an endpoint's signing secret from Settings → Webhooks, or over the API:

curl -X POST \
https://api.slothbox.dev/organizations/org_123/webhooks/whe_01HXYZ/secret/roll \
-H "Authorization: sk_..."
{ "secret": "whsec_...", "overlapSeconds": 86400 }

By default the old secret keeps verifying for 24 hours (the overlapSeconds above), during which deliveries are signed with both secrets (a space-delimited list in the webhook-signature header). Deploy the new secret within that window and you can roll without downtime — see rule 5 of verifying.

To cut over immediately instead — invalidating the old secret right away (overlapSeconds of 0) — pass { "expireOld": true } in the request body. Use this only if you suspect the old secret is compromised.

Ordered delivery (optional)

By default, deliveries are not ordered — because each event retries independently, a failed event can land after later ones that succeeded first. If you need strict in-order delivery per endpoint, enable Ordered delivery on the endpoint. Two things to know:

  • Ordering is per-endpoint and reflects the order Slothbox committed the underlying changes. It is not a global or cross-resource total order.
  • A stuck delivery holds up later ones for that endpoint until it succeeds or exhausts its retries. To keep that head-of-line blocking short, ordered endpoints retry on a short fixed cadence (about every 3 minutes, with the same eight-attempt cap) instead of the multi-hour curve — a failing ordered delivery is marked exhausted after roughly 20 minutes, not 9 hours.

Most integrations don't need this; timestamp plus idempotency on webhook-id is usually enough.