Skip to content
Webhooks

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.

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: 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": "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:

Terminal window
# 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.jpg

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 sentdeliveredread. 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.

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.

{
"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"
}
}
}
{
"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"
}
}
}
{
"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.