Skip to content
Webhooks

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.

Headers:

X-Kirim-Source: meta
X-Kirim-Event: message.received
X-Kirim-Event-Id: wamid.HBgN…
X-Kirim-Delivery-Id: wbd_01HXYZ…
X-Kirim-Attempt: 1
X-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"
}
]
}
]
}

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.

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 sentdeliveredread. Failed sends emit a single failed status with an errors[] array containing Meta’s error code (mappable via Kirim’s error catalogue — see Errors).

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
}
}
}
{
"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_…"
}
}
}
{
"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:

ValueMeaning
ctwa_adClick-to-WhatsApp paid ad
ctwa_postOrganic post with WhatsApp button
organicDirect chat without ad referral
importSynced from WhatsApp Business app history
manualAdded via dashboard or POST /v1/contacts
{
"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.