Skip to content
Webhooks

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.

AttemptDelay before
1immediate
210 s
330 s
42 m
510 m
61 h
76 h
824 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).
ResponseRetried?
2xx (200, 201, 204, …)No — success
3xxYES — 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
5xxYES
Network error / DNS / TLS / connection refusedYES
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.

  • 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.

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.

Once you’ve fixed your endpoint, re-enable via the dashboard or:

Terminal 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": "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.

To temporarily stop deliveries without losing the subscription (e.g. during a planned maintenance window):

Terminal 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 a single dead delivery:

Terminal window
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:

Terminal window
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.

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.