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 Kirimdev envelope
Section titled “The Kirimdev envelope”Every webhook POST body conforms to one of two shapes, distinguished
by the X-Kirim-Source header:
// X-Kirim-Source: kirim — Kirimdev-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", "data": { /* event-specific payload */ }}// X-Kirim-Source: meta — Meta passthrough (+ optional kirim enrichment on message.received){ "object": "whatsapp_business_account", "entry": [ /* Meta Cloud API shape */ ], "kirim": { /* optional — conversation ids, labels, media_url */ }}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" } ] } ], "kirim": { "conversation_id": "cnv_01HXYZABCDEFGHJKMNPQRSTVWX", "contact_id": "ctc_01HXYZABCDEFGHJKMNPQRSTVWX", "message_id": "msg_01HXYZABCDEFGHJKMNPQRSTVWX", "phone_number_id": "106540352242922", "labels": [ { "id": "lbl_01ABC…", "name": "VIP", "color": "#6366f1" } ], "conversation": { "status": "open", "assigned_to": null }, "contact": { "name": "John Doe", "phone_number": "+628111111111" }, "media_url": null, "media_status": null }}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.
For inbound images the webhook arrives ~1–3 seconds after the text
path because Kirimdev waits for storage upload. The optional kirim
block then includes a permanent hosted URL:
"kirim": { "message_id": "msg_01HXYZ…", "media_url": "https://media.kirimdev.com/whatsapp/<accountId>/<messageId>/bukti-transfer.jpg", "media_status": "ready"}You can fetch bytes directly from kirim.media_url, or via the Public
API — Kirimdev redirects (302) to the same asset so you don’t need Meta
credentials:
# The API returns 302 → signed CDN URL. Use -L to follow.curl -L \ https://api.kirimdev.com/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 Kirimdev code via the
error catalogue.
conversation.assigned — Kirimdev native
Section titled “conversation.assigned — Kirimdev 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", "data": { "conversation": { "id": "cnv_01HXYZABCDEFGHJKMNPQRSTVWX", "object": "conversation", "status": "open", "phone_number_id": "106540352242922" }, "assignee": { "user_id": "U7d2k9mq", "team_id": "tem_01HXYZ…", "previous_user_id": null } }}user_id / previous_user_id are raw internal user identifiers
(nanoid strings), not kirim-prefixed resource ids.
conversation.closed — Kirimdev native
Section titled “conversation.closed — Kirimdev native”{ "id": "evt_…", "type": "conversation.closed", "created_at": "2026-05-23T10:00:00Z", "data": { "conversation": { "id": "cnv_…", "object": "conversation", "status": "resolved", "closed_by_user_id": "U7d2k9mq", "phone_number_id": "106540352242922" } }}contact.created — Kirimdev native
Section titled “contact.created — Kirimdev native”{ "id": "evt_01HXYZABCDEFGHJKMNPQRSTVWX", "type": "contact.created", "created_at": "2026-05-23T10:00:00Z", "data": { "contact": { "id": "ctc_01HXYZABCDEFGHJKMNPQRSTVWX", "object": "contact", "phone_number": "+628111111111", "name": "John Doe", "email": null, "metadata": null, "phone_number_id": "106540352242922", "created_at": "2026-05-23T10:00:00Z", "updated_at": "2026-05-23T10:00:00Z" } }}contact.updated — Kirimdev native
Section titled “contact.updated — Kirimdev native”{ "id": "evt_…", "type": "contact.updated", "created_at": "2026-05-23T10:00:00Z", "data": { "contact": { "id": "ctc_…", "object": "contact", "phone_number": "+628111111111", "name": "John Doe (updated)", "email": "john@example.com", "phone_number_id": "106540352242922", "...": "..." }, "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.