Retries & Auto-Disable
Kirim guarantees at-least-once delivery via an 8-attempt retry
pipeline with exponential backoff. After the 8th attempt fails,
the delivery is marked dead. After 20 consecutive failed
deliveries, the subscription is auto-disabled.
Retry schedule
Section titled “Retry schedule”| Attempt | Delay before |
|---|---|
| 1 | immediate |
| 2 | 10 s |
| 3 | 30 s |
| 4 | 2 m |
| 5 | 10 m |
| 6 | 1 h |
| 7 | 6 h |
| 8 | 24 h |
Each delay carries ±20% jitter to avoid thundering herds. Per-attempt HTTP timeout: 10 s.
After attempt 8 fails, the delivery row status flips to dead and
sits in the dead-letter queue until you either:
- Replay it via the API or dashboard, or
- Wait for the daily purge (30 days after creation).
What counts as failure?
Section titled “What counts as failure?”| Response | Retried? |
|---|---|
| 2xx (200, 201, 204, …) | No — success |
| 3xx | YES — Kirim does not follow redirects |
| 4xx (most) | NO — marked dead immediately, no further attempts |
4xx 408 (request timeout) | YES |
4xx 429 (rate limited) | YES — Kirim honours Retry-After if present |
| 5xx | YES |
| Network error / DNS / TLS / connection refused | YES |
| Kirim timeout (>10 s) | YES |
The “4xx (most) → dead immediately” rule reflects the reality that 4xx usually means customer mis-configuration (wrong URL, bad auth, malformed verification). Retrying won’t help — fix the config, then replay.
Recommended endpoint behaviour
Section titled “Recommended endpoint behaviour”-
Ack within 1-2 seconds. Persist the raw payload (and the
X-Kirim-Event-Id) inside a quick DB write, then return 200. Hand off heavy processing to your own queue.app.post('/kirim-webhook', async (req, res) => {await persistRawEvent(req.headers['x-kirim-event-id'], req.body)res.status(200).send('ok')// Async processing kicks off via your own worker.}) -
Return 503 if you’re overloaded rather than 200-then-drop. 503 is treated as a retryable failure; you’ll get the same payload again after the backoff.
-
Never return 2xx for a payload you couldn’t process. That breaks the at-least-once contract on your side.
Auto-disable
Section titled “Auto-disable”After 20 consecutive failed deliveries on the same subscription
(regardless of the retry path each took), Kirim flips
subscription.status to disabled:
{ "id": "wbs_…", "status": "disabled", "disabled_reason": "auto_disabled_max_consecutive_failures", "consecutive_failures": 20, "...": "..."}While disabled, no new events fan out to this subscription. New events that would have been sent are simply not enqueued — they are not buffered for later delivery, because that would create a thundering herd the moment you re-enable.
Org admins receive an email notification on auto-disable.
Re-enabling
Section titled “Re-enabling”Once you’ve fixed your endpoint, re-enable via the dashboard or:
curl -X PATCH https://api-kckit.kirim.chat/v1/webhook_subscriptions/wbs_… \ -H "Authorization: Bearer $KIRIM_KEY" \ -H "Content-Type: application/json" \ -d '{ "status": "active" }'Re-enabling resets consecutive_failures to 0. Future deliveries
resume immediately. Replays of the existing dead deliveries are
opt-in — they don’t fire automatically.
Pausing manually
Section titled “Pausing manually”To temporarily stop deliveries without losing the subscription (e.g. during a planned maintenance window):
curl -X PATCH https://api-kckit.kirim.chat/v1/webhook_subscriptions/wbs_… \ -H "Authorization: Bearer $KIRIM_KEY" \ -H "Content-Type: application/json" \ -d '{ "status": "paused" }'Paused subscriptions don’t accumulate dead deliveries — Kirim drops
events that would have fanned out to them. Resume with
{ "status": "active" }.
Replay
Section titled “Replay”Replay a single dead delivery:
curl -X POST https://api-kckit.kirim.chat/v1/webhook_deliveries/wbd_…/replay \ -H "Authorization: Bearer $KIRIM_KEY"Replay every dead delivery for a subscription matching a filter:
curl -X POST https://api-kckit.kirim.chat/v1/webhook_deliveries/bulk_replay \ -H "Authorization: Bearer $KIRIM_KEY" \ -H "Content-Type: application/json" \ -d '{ "subscription_id": "wbs_…", "status": "dead", "created_after": "2026-05-20T00:00:00Z" }'Cap: 1000 deliveries per bulk call. Paginate via
created_after/created_before for larger backfills.
Observability
Section titled “Observability”The dashboard’s Developers → Webhook Deliveries page shows every delivery (last 30 days), filterable by subscription, status, event type, date range. Each row exposes:
- Attempt count + last/next attempt timestamps
- Response status + first 1 KB of the response body
- Full payload (pretty-printed JSON, copy-to-clipboard)
- Replay button
The same data is queryable via GET /v1/webhook_deliveries — see the
API Reference.