The Kanal webhook notifies you in real time when one of your contacts sends a WhatsApp message to your number. Ideal for plugging in a bot, feeding a CRM, or triggering server-side automations.
From the Kanal interface, add your server's public HTTPS URL. Kanal sends a POST request to this URL for every incoming message, with the JSON body described below.
Every webhook follows the same standardized envelope:
{
"id": "evt_01HRXYZ...",
"type": "message.received",
"api_version": "2026-05-13",
"created_at": "2026-05-13T10:00:00+00:00",
"phone_number_id": 123,
"data": {
"contact": { ... },
"message": { ... }
}
}
Root fields:
Field | Description |
|---|---|
| Unique identifier of the event. Use it to deduplicate on your server. |
| Event type. For now: |
| Version of the webhook contract. Lets you handle changes without breakage. |
| Date the event was emitted, in ISO 8601 format. |
| ID of the Kanal WhatsApp number that received the message. |
| Object containing the contact and the received message. |
The data object always contains two sub-objects: contact and message.
{
"id": 6789,
"phone_number": "33612345678",
"name": "John Smith",
"country_code": "FR"
}
{
"id": 98765,
"wamid": "wamid.HBgL...",
"type": "text",
"content": "Hello, I have a question",
"media": null
}
The content field always contains the usable text of the message (text body, media caption, label of a clicked button). The media field is filled only for media. For button clicks and list selections, a dedicated object (button / list_option) also provides the technical identifier of the chosen option.
{
"id": "evt_01HRXYZ...",
"type": "message.received",
"api_version": "2026-05-13",
"created_at": "2026-05-13T10:00:00+00:00",
"phone_number_id": 123,
"data": {
"contact": {
"id": 6789,
"phone_number": "33612345678",
"name": "John Smith",
"country_code": "FR"
},
"message": {
"id": 98765,
"wamid": "wamid.HBgL...",
"type": "text",
"content": "Hello, I have a question",
"media": null
}
}
}
{
"id": 98766,
"wamid": "wamid.HBgL...",
"type": "image",
"content": "Here is the photo of my product",
"media": {
"type": "image",
"url": "https://api.getkanal.com/api/media/messages/98766?signature=...",
"caption": "Here is the photo of my product"
}
}
The URL in the media.url field is a permanent signed link served by Kanal: you can store it and re-download the media at any time.
{
"id": 98767,
"wamid": "wamid.HBgL...",
"type": "video",
"content": null,
"media": {
"type": "video",
"url": "https://api.getkanal.com/api/media/messages/98767?signature=...",
"caption": null
}
}
{
"id": 98768,
"wamid": "wamid.HBgL...",
"type": "audio",
"content": null,
"media": {
"type": "audio",
"url": "https://api.getkanal.com/api/media/messages/98768?signature=...",
"caption": null
}
}
{
"id": 98770,
"wamid": "wamid.HBgL...",
"type": "button_reply",
"content": "Yes, I confirm",
"media": null,
"button": {
"id": "4233be9add_button_1",
"title": "Yes, I confirm"
}
}
content contains the label of the clicked button. The button object also provides its technical identifier (button.id), useful for routing the response on your server without depending on the displayed text. This format covers both the buttons of an interactive message and a template's quick reply buttons.
{
"id": 98771,
"wamid": "wamid.HBgL...",
"type": "list_reply",
"content": "Order tracking",
"media": null,
"list_option": {
"id": "cd593e96a1_row_2",
"title": "Order tracking"
}
}
Same principle: content contains the title of the chosen row, and list_option.id its technical identifier.
For now, the only event emitted is message.received (incoming message from a contact). Clicks on a template's quick reply buttons also arrive as message.received with the type button_reply. More events are planned soon (delivery status, read receipt).
type field. New types will be added without notice, but your integration will not be affected if you ignore the ones you do not handle.The id field (format evt_...) is unique for each event. Store it on your server to ignore possible duplicates in case of a retry.
The wamid field (the message's WhatsApp identifier) is also unique and stable, useful if you want to cross-reference several data sources.
The api_version field indicates the version of the webhook contract. The current version is 2026-05-13.
Non-breaking additions (new fields, new event types) are deployed without changing the version.
A new version will only be published in case of a breaking change, with a transition period.
To stay compatible, your integration must ignore fields it does not recognize.
Return 200 OK as fast as possible (within 5 seconds). Queue heavy processing on your server.
Filter events by type to process only what you care about.
Store the id of every received event to avoid duplicates.
To reply to the received message, use the POST /api/v1/messages/send endpoint with the contact's phone_number.
For compatibility with older clients (notably mobile notifications), the title, body and text fields are also present at the root of the payload. They contain a generic message like "<name> just sent you a new message". Ignore them for a modern integration: all the useful information is in data.