Webhooks Overview
Kirim pushes events to your server via signed HTTP POSTs. Subscribe once, manage subscriptions via the API or the dashboard, and let Kirim handle retries, dead-letter queueing, and signing-secret rotation.
Two payload formats
Section titled “Two payload formats”Webhook deliveries fall into two shapes, distinguished by the
X-Kirim-Source header:
X-Kirim-Source: meta — Meta passthrough
Section titled “X-Kirim-Source: meta — Meta passthrough”Events that originate inside Meta (a customer sends a WhatsApp message, a delivery status callback fires) are forwarded verbatim. The HTTP body is exactly the JSON Meta sent to Kirim, after Meta-signature stripping and Kirim-signature attachment.
Why passthrough? Your existing WhatsApp Cloud API parser keeps working unchanged. Kirim just adds reliable delivery (retries, dead-letter, signature) on top of Meta’s raw stream.
X-Kirim-Source: kirim — Kirim envelope
Section titled “X-Kirim-Source: kirim — Kirim envelope”Events that originate inside Kirim (a conversation gets assigned, a contact is created via the dashboard) use a normalised envelope:
{ "id": "evt_01HXYZABCDEFGHJKMNPQRSTVWX", "type": "conversation.assigned", "created_at": "2026-05-23T10:00:00Z", "data": { "conversation": { "id": "cnv_…", "...": "..." }, "assignee": { "user_id": "usr_…", "team_id": "…" } }}Why an envelope? These events have no Meta-side equivalent — there’s no canonical wire format to mirror, so Kirim defines one.
Headers on every delivery
Section titled “Headers on every delivery”Content-Type: application/jsonUser-Agent: Kirim-Webhook/1.0X-Kirim-Source: meta # "meta" or "kirim"X-Kirim-Event: message.receivedX-Kirim-Event-Id: wamid.HBgN... # Meta wamid when source=meta, evt_<id> when source=kirimX-Kirim-Delivery-Id: wbd_… # Unique per delivery attemptX-Kirim-Attempt: 1 # 1-based attempt counter (1-8)X-Kirim-Signature: t=1716480000,v1=<hex>,v1=<hex>Delivery contract
Section titled “Delivery contract”- At-least-once. Kirim guarantees every event is delivered to a
healthy subscription at least once. Dedupe on your side using
X-Kirim-Event-Id(see Dedupe below). - Order is not guaranteed. Meta’s source events arrive in roughly
causal order, but parallel delivery means you may see
message.status: deliveredbeforemessage.status: sent. Treat status as a state machine, not an event sequence. - 8 retries with exponential backoff (10 s → 24 h) before dead-letter. See Retries & Auto-Disable.
- 2xx response = success. Any 2xx (200, 201, 204, …) marks the
delivery
succeeded. 3xx is treated as failure (we don’t follow redirects). - 10-second per-attempt timeout. Your endpoint must respond in under 10 seconds or Kirim treats it as a timeout failure.
Dedupe
Section titled “Dedupe”Kirim guarantees at-least-once delivery. Your endpoint will occasionally receive the same event twice — typically when a retry fires after your server processed the original but the response didn’t reach Kirim before the 10 s timeout.
Dedupe on X-Kirim-Event-Id. Same event id always represents the
same logical event, regardless of attempt number or whether the
delivery is a manual replay.
import { redis } from './lib/redis.js'
app.post('/kirim-webhook', async (req, res) => { const eventId = req.headers['x-kirim-event-id'] as string
// Atomic claim — SET NX with a 7-day TTL covers all retry windows. const fresh = await redis.set(`webhook:${eventId}`, '1', { EX: 604800, NX: true }) if (!fresh) { return res.status(200).send('duplicate-ack') }
// First time we've seen this event — process it. await handleEvent(req.body) res.status(200).send('ok')})Source dedup at the publisher
Section titled “Source dedup at the publisher”To avoid fanning out the same Meta retry to your subscription multiple times, Kirim itself dedupes meta-sourced events at the publisher layer. The first time Meta’s wamid hits Kirim, a 24-hour Redis claim is set. Subsequent deliveries of the same wamid from Meta are dropped before ever reaching your subscription.
This means the only dedupe scenario you need to worry about is the retry-vs-original race, not Meta’s own at-least-once retry pattern.
What’s next
Section titled “What’s next”- Verifying Signatures — code samples in Node, Python, Ruby, Go.
- Event catalog — every event type and what triggers it.
- Retries & Auto-Disable — the full retry schedule, dead-letter, and 20-failure auto-disable policy.
- Payload examples — copy-paste fixtures for each event type.