Payload Examples
These are real payload shapes produced by the production publisher. Use them as fixtures in your handler tests so a regression in your parser fails CI instead of silently swallowing live events.
message.received (text)
Section titled “message.received (text)”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": "<PHONE_NUMBER_ID>" }, "contacts": [ { "profile": { "name": "John Doe" }, "wa_id": "628111111111" } ], "messages": [ { "from": "628111111111", "id": "wamid.HBgN…", "timestamp": "1716480000", "type": "text", "text": { "body": "Halo, mau tanya..." } } ] }, "field": "messages" } ] } ]}message.received (image with caption)
Section titled “message.received (image with caption)”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>" }}To fetch the media bytes, use GET /v1/messages/{id}/media
which redirects to the cached CDN URL — no second Meta auth needed.
message.status
Section titled “message.status”Headers: X-Kirim-Event: message.status
Body:
{ "object": "whatsapp_business_account", "entry": [ { "id": "<WABA_ID>", "changes": [ { "value": { "messaging_product": "whatsapp", "metadata": { "display_phone_number": "...", "phone_number_id": "..." }, "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 via Kirim’s error catalogue — see
Errors).
conversation.assigned
Section titled “conversation.assigned”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" }, "assignee": { "user_id": "usr_…", "team_id": "…", "previous_user_id": null } }}conversation.closed
Section titled “conversation.closed”{ "id": "evt_…", "type": "conversation.closed", "created_at": "2026-05-23T10:00:00Z", "data": { "conversation": { "id": "cnv_…", "object": "conversation", "status": "resolved", "closed_by_user_id": "usr_…" } }}contact.created
Section titled “contact.created”{ "id": "evt_…", "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, "whatsapp_account": { "phone_number": "+62 851-1760-0829" }, "created_at": "2026-05-23T10:00:00Z", "updated_at": "2026-05-23T10:00:00Z" }, "acquisition_source": "organic" }}acquisition_source is one of:
| Value | Meaning |
|---|---|
ctwa_ad | Click-to-WhatsApp paid ad |
ctwa_post | Organic post with WhatsApp button |
organic | Direct chat without ad referral |
import | Synced from WhatsApp Business app history |
manual | Added via dashboard or POST /v1/contacts |
contact.updated
Section titled “contact.updated”{ "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", "...": "..." }, "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.