kirim.dev pushes events to your server via signed HTTP POSTs. You
subscribe a URL once at the organisation level, pick which events you
care about, and kirim.dev takes care of retries, dead-letter queueing,
and signing-secret rotation.
Polling GET /v1/{phone_number_id}/messages on a timer is the wrong
answer for almost every integration:
Latency. Polling at 1 Hz adds an average 500 ms before you
notice an inbound message — long enough for your bot to feel sluggish.
Webhooks land in under 200 ms from the customer hitting send.
Cost. A 1 Hz poll burns 86,400 requests per day per number
against your rate limit, almost all of them empty.
Race conditions. Status transitions (sent → delivered →
read) happen faster than you can poll; you’ll miss intermediate
states.
Webhooks invert the relationship: kirim.dev tells you when
something happened, you stay idle until it does.
Every webhook POST carries an X-Kirim-Source header. Its value tells
you whether the body is Meta passthrough or a Kirim-native
envelope:
X-Kirim-Source: meta
Events that originated inside Meta — a customer messaged you
(message.received), or Meta confirmed a delivery status
(message.status). The body is the exact JSON Meta sent, so
any existing WhatsApp Cloud API parser keeps working unchanged.
X-Kirim-Source: kirim
Events that originated inside kirim.dev — a contact was created,
a conversation got assigned, an outbound message was queued. These
use the Kirim envelope: { id, type, created_at, livemode, data }.
The two shapes coexist on the same subscription. Your handler
branches on X-Kirim-Source (or just on type after parsing) and
routes accordingly.
A webhook subscription is the binding between a URL and a list of
event types you care about. Subscriptions are organisation-scoped
— one subscription receives events from every WhatsApp account in
your org. You don’t need one subscription per phone number.
At-least-once. Every event reaches a healthy subscription at
least once. Dedupe on your side using X-Kirim-Event-Id.
2xx response = success. Any 2xx (200, 201, 204, …) marks the
delivery succeeded. 3xx is treated as failure — kirim.dev does
not follow redirects.
10-second per-attempt timeout. Your endpoint must respond in
under 10 seconds or the attempt is recorded as a timeout failure.
8 retries with exponential backoff before dead-letter. Total
window is roughly 24 hours. See
Retries & Auto-Disable for the full schedule.
Order is not guaranteed. Status callbacks may arrive out of
order (you can see delivered before sent). Treat status as a
state machine, not a sequence.
Because delivery is at-least-once, your endpoint will occasionally
receive the same event twice — typically when a retry fires after
your server processed the original but its 200 didn’t reach kirim.dev
before the 10 s timeout.
The dedupe key is X-Kirim-Event-Id. Same id = same logical event,
regardless of attempt number or whether the delivery is a manual
replay.
Meta itself retries inbound webhooks. To avoid fanning the same Meta
retry out to your subscription several times, kirim.dev dedupes
source=meta events at the publisher layer using a 24-hour claim on
the wamid. By the time an event reaches your endpoint, Meta-driven
duplicates are already filtered out.
The only dedupe scenario you need to worry about is the
retry-vs-original race described above, not Meta’s own
at-least-once retry pattern.