Payload Examples
These are real payload shapes produced by the production publisher. Use them as fixtures in your handler tests — a regression in your parser will fail CI instead of silently swallowing live events.
The Kirim envelope
Section titled “The Kirim envelope”Every webhook POST body conforms to one of two shapes, distinguished
by the X-Kirim-Source header:
// X-Kirim-Source: kirim — Kirim-native events{ "id": "evt_01HXYZABCDEFGHJKMNPQRSTVWX", // ULID, also in X-Kirim-Event-Id "type": "contact.created", // see Event Catalogue "created_at": "2026-05-23T10:00:00Z", "livemode": true, // false when fired from a sandbox key "data": { /* event-specific payload */ }}// X-Kirim-Source: meta — Meta passthrough{ "object": "whatsapp_business_account", "entry": [ /* exactly what Meta sent */ ]}message.received (text) — Meta passthrough
Section titled “message.received (text) — Meta passthrough”Headers
X-Kirim-Source: metaX-Kirim-Event: message.receivedX-Kirim-Event-Id: wamid.HBgN…X-Kirim-Delivery-Id: wbd_01HXYZ…X-Kirim-Attempt: 1X-Kirim-Signature: t=1716480000,v1=<hex>Body (raw Meta payload, passthrough)
{ "object": "whatsapp_business_account", "entry": [ { "id": "<WABA_ID>", "changes": [ { "value": { "messaging_product": "whatsapp", "metadata": { "display_phone_number": "62851176008029", "phone_number_id": "106540352242922" }, "contacts": [ { "profile": { "name": "John Doe" }, "wa_id": "628111111111" } ], "messages": [ { "from": "628111111111", "id": "wamid.HBgN…", "timestamp": "1716480000", "type": "text", "text": { "body": "Halo, mau tanya soal pesanan" } } ] }, "field": "messages" } ] } ]}from here is the customer’s WhatsApp number that messaged you.
The phone_number_id at entry[].changes[].value.metadata.phone_number_id
(here 106540352242922) is the connected number that received the
message — use this in any follow-up POST /v1/{phone_number_id}/messages
call.
message.received (image with caption) — Meta passthrough
Section titled “message.received (image with caption) — Meta passthrough”Same envelope, different messages[0]:
{ "from": "628111111111", "id": "wamid.HBgN…", "timestamp": "1716480000", "type": "image", "image": { "caption": "Bukti transfer", "mime_type": "image/jpeg", "sha256": "<sha256>", "id": "<MEDIA_ID>" }}The image.id is a Meta media id scoped to this phone number.
Fetch the bytes via the per-account media endpoint — kirim.dev
redirects (302) to a short-lived signed CDN URL so you don’t need
Meta credentials:
# The API returns 302 → signed CDN URL. Use -L to follow.curl -L \ https://api.kirim.chat/v1/106540352242922/messages/wamid.HBgN…/media \ -H "Authorization: Bearer $KIRIM_KEY" \ -o transfer-proof.jpgimport { Kirim } from '@kirimdev/sdk'
const kirim = new Kirim({ apiKey: process.env.KIRIM_KEY! })const phone = kirim.phoneNumbers('106540352242922')
// SDK returns the redirect target without following it,// so you decide whether to stream or download.const media = await phone.messages.media('wamid.HBgN…')
const res = await fetch(media.url)const buf = Buffer.from(await res.arrayBuffer())await Bun.write('transfer-proof.jpg', buf)message.status — Meta passthrough
Section titled “message.status — Meta passthrough”Headers: X-Kirim-Event: message.status
Body
{ "object": "whatsapp_business_account", "entry": [ { "id": "<WABA_ID>", "changes": [ { "value": { "messaging_product": "whatsapp", "metadata": { "display_phone_number": "62851176008029", "phone_number_id": "106540352242922" }, "statuses": [ { "id": "wamid.HBgN…", "status": "delivered", "timestamp": "1716480010", "recipient_id": "628111111111", "conversation": { "id": "<META_CONVERSATION_ID>", "origin": { "type": "user_initiated" } }, "pricing": { "billable": true, "pricing_model": "CBP", "category": "user_initiated" } } ] }, "field": "messages" } ] } ]}status cycles through sent → delivered → read. Failed sends
emit a single failed status with an errors[] array containing
Meta’s error code — mappable to a stable kirim.dev code via the
error catalogue.
message.created — Kirim native
Section titled “message.created — Kirim native”Fired the moment kirim.dev queues an outbound send. Lands in milliseconds — no need to wait for Meta.
Headers
X-Kirim-Source: kirimX-Kirim-Event: message.createdX-Kirim-Event-Id: evt_01HXYZABCDEFGHJKMNPQRSTVWXBody
{ "id": "evt_01HXYZABCDEFGHJKMNPQRSTVWX", "type": "message.created", "created_at": "2026-05-23T10:00:00Z", "livemode": true, "data": { "message": { "id": "msg_01HXYZABCDEFGHJKMNPQRSTVWX", "object": "message", "whatsapp_account_id": "wba_01HXYZ…", "phone_number_id": "106540352242922", "to": "628111111111", "type": "text", "status": "queued", "conversation_id": "cnv_01HXYZ…", "created_at": "2026-05-23T10:00:00Z" } }}conversation.assigned — Kirim native
Section titled “conversation.assigned — Kirim native”Headers: X-Kirim-Source: kirim, X-Kirim-Event: conversation.assigned
Body
{ "id": "evt_01HXYZABCDEFGHJKMNPQRSTVWX", "type": "conversation.assigned", "created_at": "2026-05-23T10:00:00Z", "livemode": true, "data": { "conversation": { "id": "cnv_01HXYZABCDEFGHJKMNPQRSTVWX", "object": "conversation", "status": "open", "whatsapp_account_id": "wba_01HXYZ…", "phone_number_id": "106540352242922" }, "assignee": { "user_id": "usr_01HXYZ…", "team_id": "tem_01HXYZ…", "previous_user_id": null } }}conversation.closed — Kirim native
Section titled “conversation.closed — Kirim native”{ "id": "evt_…", "type": "conversation.closed", "created_at": "2026-05-23T10:00:00Z", "livemode": true, "data": { "conversation": { "id": "cnv_…", "object": "conversation", "status": "resolved", "closed_by_user_id": "usr_…", "phone_number_id": "106540352242922" } }}contact.created — Kirim native
Section titled “contact.created — Kirim native”{ "id": "evt_01HXYZABCDEFGHJKMNPQRSTVWX", "type": "contact.created", "created_at": "2026-05-23T10:00:00Z", "livemode": true, "data": { "contact": { "id": "ctc_01HXYZABCDEFGHJKMNPQRSTVWX", "object": "contact", "phone_number": "+628111111111", "name": "John Doe", "email": null, "metadata": null, "whatsapp_account_id": "wba_01HXYZ…", "phone_number_id": "106540352242922", "created_at": "2026-05-23T10:00:00Z", "updated_at": "2026-05-23T10:00:00Z" }, "acquisition_source": "ctwa_ad" }}acquisition_source indicates how the contact reached you:
| Value | Meaning |
|---|---|
ctwa_ad | Click-to-WhatsApp paid ad |
ctwa_post | Organic post with WhatsApp button |
organic | Direct chat, no ad/post referral |
broadcast | Reached via outbound broadcast list |
api | Created via POST /v1/{phone_number_id}/contacts |
manual | Added by a teammate in the dashboard |
contact.updated — Kirim native
Section titled “contact.updated — Kirim native”{ "id": "evt_…", "type": "contact.updated", "created_at": "2026-05-23T10:00:00Z", "livemode": true, "data": { "contact": { "id": "ctc_…", "object": "contact", "phone_number": "+628111111111", "name": "John Doe (updated)", "phone_number_id": "106540352242922", "whatsapp_account_id": "wba_01HXYZ…", "...": "..." }, "changed_fields": ["name", "email"] }}changed_fields lists only the field names that actually differ from
the prior version. An empty array would be elided rather than sent —
no event fires when nothing changed.