Skip to content

Event reference

Every delivery has the same envelope:

{
"event": "message.delivered",
"timestamp": "2026-08-20T09:15:04.221Z",
"data": { }
}

And the same headers:

HeaderMeaning
X-KM-EventThe event name, so you can route before parsing
X-KM-Delivery-IdShared by every attempt at one event
X-KM-TimestampUnix seconds, part of the signed string
X-KM-Signaturev1=<hex>, see Verify webhook signatures
X-Webhook-SignatureThe older scheme, still sent

Reply 2xx as fast as you can and do the work afterwards. We retry three times with backoff on a 5xx or a timeout, and do not retry a 4xx, which cannot succeed by repetition.

A contact sent you a message.

{
"message_id": "6f1c2d3e-...",
"contact_id": "3f2a...",
"contact_phone": "+966500000000",
"contact_name": "Sara",
"message_type": "text",
"content": "Has it shipped yet?",
"whatsapp_account": "Main"
}

This also opens the 24-hour window for that contact, so it is the moment free text becomes possible again.

A message left K-Message, whether from an agent, the chatbot or the API. It means we handed it to WhatsApp, not that anyone received it.

WhatsApp confirmed the message reached the recipient’s device.

{
"message_id": "6f1c2d3e-...",
"contact_id": "3f2a...",
"contact_phone": "+966500000000",
"wamid": "wamid.HBgMOTY2NTAwMDAwMDAwFQIAERgS...",
"status": "delivered"
}

This is the event to record against your own order or ticket. wamid is Meta’s identifier, which is what you will be looking at if you ever compare our records with your Meta dashboard.

The recipient opened it. Not all recipients have read receipts enabled, so absence of this event means nothing.

WhatsApp refused the message.

{
"message_id": "6f1c2d3e-...",
"contact_id": "3f2a...",
"contact_phone": "+966500000000",
"status": "failed",
"error_message": "Message failed to send because more than 24 hours have passed since the customer last replied to this number"
}

error_message is Meta’s own text. The most common causes are being outside the 24-hour window, a template that is no longer approved, and a number that is not on WhatsApp.

A contact was added, either by messaging you for the first time or through the API.

{
"contact_id": "3f2a...",
"contact_phone": "+966500000000",
"contact_name": "Sara",
"whatsapp_account": "Main"
}

A contact’s details or tags changed.

{
"contact_id": "3f2a...",
"contact_phone": "+966500000000",
"contact_name": "Sara Al-Ahmed",
"tags": ["vip", "riyadh"]
}

Subscribe to both if you mirror contacts into your own system. A sync that heard only about creations would drift out of date on the first edit.

A conversation asked for a human agent, either because the customer requested one or because the chatbot escalated.

{
"transfer_id": "4e5f6a7b-...",
"contact_id": "3f2a...",
"contact_phone": "+966500000000",
"contact_name": "Sara",
"source": "chatbot",
"reason": "Customer asked to speak to someone",
"whatsapp_account": "Main"
}

An agent picked it up. Carries agent_id and agent_name.

The handover closed and the conversation went back to the chatbot.

A bulk send finished, with its settled counts.

{
"campaign_id": "5d4c3b2a-...",
"campaign_name": "Eid offer",
"status": "completed",
"total_recipients": 1200,
"sent": 1197,
"delivered": 1102,
"read": 640,
"failed": 3,
"skipped": 0
}

Fires once, when every recipient has been dealt with. A campaign parked against the daily allowance has not completed and does not fire this.

Meta approved a template and it can now be sent.

{
"template_id": "7c6b5a4d-...",
"template_name": "order_shipped",
"language": "ar",
"status": "APPROVED"
}

Meta rejected it, with the reason when one was given.

{
"template_id": "7c6b5a4d-...",
"template_name": "order_shipped",
"language": "ar",
"status": "REJECTED",
"reason": "INVALID_FORMAT"
}

Meta’s review is asynchronous and can take hours. These two events exist so a system that submits templates does not have to poll for the outcome.