Message webhooks
The payload Whatsable POSTs to Make, Zapier, n8n or your own server for each incoming and outgoing WhatsApp message, including replies typed in the WhatsApp Business app and Click-to-WhatsApp ad referral data.
Whatsable can POST a JSON payload to your endpoint each time a WhatsApp message arrives on your number or is sent from it. That includes messages your team types by hand in the WhatsApp Business app on the phone. Use it to start a Make, Zapier or n8n automation, update a CRM, attribute ad leads, or keep a copy of your conversations.
This page is the reference for that payload. To add an endpoint, see outgoing webhooks.
Outgoing webhooks are part of the Pro and Agency plans and the free plan, not Bulk Message. See plans.
Set up an endpoint
Open Webhooks
In Whatsable, go to Settings → Webhooks and select Add endpoint.
Choose where it goes
Under Send to, choose Make, Zapier or n8n (incoming and outgoing messages, recommended) or Your own server (incoming messages only, one request per message). You can't change this after you add the webhook.
Paste the URL and pick the messages
Paste the Webhook URL, choose the Number, and under What to send tick Incoming messages, Outgoing messages, or both.
Sign requests, if you want to
Sign requests starts off. Turn it on only if your server will check the signature. Whatsable then shows the signing secret once. Copy it to verify each request. You cannot change this later.
Your endpoint must accept POST requests with a JSON body over public HTTPS, and answer with a 2xx status quickly. Whatsable retries a failed delivery a few times. When an endpoint keeps answering "not found", Whatsable stops sending to it and the webhook shows Disabled: endpoint not found. Fix the endpoint, then select Enable.
Webhook payload
Every delivery is one JSON object. Which fields it holds depends on the direction of the message.
Incoming message
A customer wrote to your number. This example comes from a Your own server endpoint.
{
"last_messages": "[{\"type\":\"user\",\"content\":\"Can you tell me more about the premium plan?\",\"timestamp\":\"2026-10-20T14:05:12.000Z\",\"content_type\":\"text\"},{\"type\":\"bot\",\"content\":\"Hi Sarah, how can we help?\",\"timestamp\":\"2026-10-20T14:01:40.000Z\",\"content_type\":\"text\"}]",
"conversation_paragraph": "User (2:05:12 PM): Can you tell me more about the premium plan? ; Bot (2:01:40 PM): Hi Sarah, how can we help?",
"phone_number": "14155552671",
"recipient_name": "Sarah Johnson",
"user_id": "9232fcef-a570-4a2c-b46b-6cab53aec304",
"last_message_of_user": "Can you tell me more about the premium plan?",
"last_message_of_bot": "Hi Sarah, how can we help?",
"message_type": "text",
"user_last_message_time": 1792505112,
"bot_last_message_time": 1792504900,
"attachment_url": "",
"note": "Asked about pricing last week",
"note_automation": "",
"labels": "premium-inquiry, sales",
"incoming_message": true,
"is_scheduled": false
}On a Make, Zapier or n8n endpoint, an incoming message has no incoming_message field at all. On a Your own server endpoint it is true. Treat a missing incoming_message as an incoming message.
Outgoing message
Your business sent a message. Only a Make, Zapier or n8n endpoint with Outgoing messages ticked receives these.
{
"last_messages": "[{\"type\":\"bot\",\"content\":\"Yes, it includes three numbers.\",\"timestamp\":\"2026-10-20T14:07:02.000Z\",\"content_type\":\"text\"},{\"type\":\"user\",\"content\":\"Can you tell me more about the premium plan?\",\"timestamp\":\"2026-10-20T14:05:12.000Z\",\"content_type\":\"text\"}]",
"conversation_paragraph": "Bot (2:07:02 PM): Yes, it includes three numbers. ; User (2:05:12 PM): Can you tell me more about the premium plan?",
"phone_number": 14155552671,
"recipient_name": "Sarah Johnson",
"user_id": "9232fcef-a570-4a2c-b46b-6cab53aec304",
"last_message_of_user": "Can you tell me more about the premium plan?",
"last_message_of_bot": "Yes, it includes three numbers.",
"message_type": "text",
"user_last_message_time": 1792505112,
"bot_last_message_time": 1792505222,
"attachment_url": "",
"note": "Asked about pricing last week",
"note_automation": "",
"labels": "premium-inquiry, sales",
"incoming_message": false,
"send_by": "WhatsApp App",
"was_message_by_human": true
}On an outgoing message, phone_number is a JSON number, not a string. When the last messages hold no message from the customer, last_message_of_user is left out.
Payload fields
The last messages of the conversation (up to 15), newest first, as a JSON string. Parse it before you loop over it. Each item has type (user for the customer, bot for your business), content, timestamp (ISO 8601) and content_type. Only messages with text are included.
The same messages as one line of text, such as User (2:05:12 PM): ... ; Bot (2:01:40 PM): .... Times are in UTC.
The customer's WhatsApp number, digits only. A string on incoming messages and a number on outgoing ones.
The customer's name in Whatsable.
The ID of your Whatsable account. It is the same on every delivery. It does not identify the customer: use phone_number for that.
The customer's latest message with text.
The latest message your business sent: from the inbox, the WhatsApp Business app, the AI agent, a broadcast, the API or an automation.
The type of the message that caused this delivery, such as text, image, audio, video, document, location, button or interactive. An incoming text message that came from a Click-to-WhatsApp ad has referral.
When the customer last wrote, in Unix seconds. null when the last messages hold none from the customer.
When your business last wrote, in Unix seconds. null when the last messages hold none from your business.
Always an empty string. The webhook does not carry media files.
The note your team wrote on the contact.
The note your automations wrote on the contact.
The contact's labels, sorted by name and joined with , .
false on an outgoing message. true on an incoming message to a Your own server endpoint. Missing on an incoming message to a Make, Zapier or n8n endpoint.
On incoming messages: false for a single message, true for combined messages. Missing on outgoing messages.
Outgoing messages only. Who sent the message:
send_by | Sent by |
|---|---|
WhatsApp App | Someone typing in the WhatsApp Business app on the phone |
AI | Your AI agent, including its follow-ups, or an automatic reply: the away message or the keep-the-window-open follow-up |
Notifyer | Anything else: a teammate in the inbox, a broadcast, the API or an automation |
Notifyer is the old name of Whatsable. The value stays the same so that existing automations keep working.
Outgoing messages only. false when your AI agent or an automatic reply (the away message or the keep-the-window-open follow-up) sent the message, true otherwise.
Incoming messages only, and only when the message came from a Click-to-WhatsApp ad or post. See Click-to-WhatsApp ads.
Incoming messages only, when the customer tapped a button. See button replies.
Only when the endpoint's Number is All numbers: the WhatsApp phone number ID of your number that sent or received the message.
Only when the endpoint's Number is All numbers: your number that sent or received the message.
Combined messages
Customers often send several short messages in a row. On a Make, Zapier or n8n endpoint, turn on Combine quick messages: Whatsable waits until the customer has sent nothing new for the time you choose, then sends their messages as one request. That request has is_scheduled: true and a scheduled_messages field that holds the combined messages as one string. In it, labels is an array of names rather than a joined string, and phone_number is a number.
Pause when your team replies
Turn on Pause when your team replies to stop sending a chat's incoming messages for a while after a teammate replies, so your automation and your team don't both answer. The timer restarts every time your team replies. Replies from bots, broadcasts and the API don't pause it.
Click-to-WhatsApp ads
When someone taps Send message on a Click-to-WhatsApp ad (Facebook, Instagram, or WhatsApp Status) and writes to your number, Meta attaches a referral object to that incoming message. Whatsable passes it on as a top-level referral field, to your own server and to your n8n, Make and Zapier automations.
Use it to see which ad, which creative and which platform produced the lead, then label the contact, route the chat, or send ctwa_clid to Meta's Conversions API.
referral is usually present only on the first message after the ad click. Store ctwa_clid and source_id (keyed on the contact's phone) as soon as they arrive.
| Goal | Fields |
|---|---|
| Group leads by campaign or creative | source_id, headline |
| See Facebook vs Instagram vs other | source_url (host is instagram.com, facebook.com, fb.me, …) |
| Show your team the ad the lead saw | headline, body, image_url / video_url, media_type |
| Send conversions back to Meta | ctwa_clid |
{
"message_type": "referral",
"last_message_of_user": "I'd like more information",
"referral": {
"source_type": "ad",
"source_id": "52540693396957",
"source_url": "https://www.instagram.com/p/DZiVBz7gAF9/",
"headline": "Spring sale",
"body": "Get 20% off this week only.",
"media_type": "image",
"image_url": "https://scontent.xx.fbcdn.net/v/t45.1600-4/example.jpg",
"ctwa_clid": "AfhN-JKvx8HeFTmfB-Tu11zktQRJ-MNdMV1mwvDNxHnpYrGl"
}
}Every referral field is described on Click-to-WhatsApp referral fields. WhatsApp Status ad placements may omit ctwa_clid. Organic posts use source_type: "post".
In n8n, Make or Zapier, add a filter or router: only treat the event as ad-attributed when referral exists. Map referral.headline, referral.source_url and referral.source_id into your CRM so your reports match Meta Ads Manager.
Verify the signature
When Sign requests was on, every request carries two headers. When it was off, these headers are absent.
| Header | Value |
|---|---|
X-Notifyer-Timestamp | The time of the request, in Unix seconds |
X-Notifyer-Signature-256 | sha256= followed by a hex HMAC-SHA256 |
To check a request, compute the HMAC-SHA256 of the timestamp, a dot and the raw request body (<timestamp>.<body>), with your signing secret as the key. Compare it with the hex value after sha256=. Reject a request whose signature does not match, or whose timestamp is too old.
const crypto = require('crypto');
function isFromWhatsable(rawBody, headers, secret) {
const timestamp = headers['x-notifyer-timestamp'];
const expected = crypto.createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');
const received = (headers['x-notifyer-signature-256'] || '').replace(/^sha256=/, '');
if (received.length !== expected.length) return false;
return crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
}Keep the signing secret on your server. Never put it in client-side code or a public repository.
Troubleshooting
- Check that the webhook is Active in Settings → Webhooks.
- Check that your endpoint is public, uses HTTPS and answers
2xx. - Check the Number of the webhook: a webhook for one number receives only that number's messages.
- If a teammate replied recently, Pause when your team replies may be holding that chat's messages.
Outgoing messages reach only a Make, Zapier or n8n endpoint with Outgoing messages ticked. A Your own server endpoint receives incoming messages only.
referralis usually only on the first message after the ad click.- Messages that did not come from an ad or post have no
referral. - A Facebook Lead Ad form is a different Meta product and does not produce a
referral.
FAQs
Yes. With Coexistence you keep the WhatsApp Business app on your phone. On a Make, Zapier or n8n endpoint with Outgoing messages ticked, those messages arrive with incoming_message: false, send_by: "WhatsApp App" and was_message_by_human: true. See phone replies.
No. A message from the inbox has send_by: "Notifyer", the same as a broadcast or an API send. send_by tells apart the WhatsApp Business app ("WhatsApp App") and the AI agent ("AI").
The webhook does not carry files: attachment_url is empty. Open the conversation in the inbox. With API v1, find the message with List messages, then download its file with Download a message's media file.
Not yet. Add and edit endpoints in Settings → Webhooks.
Need assistance?
Our support team can help with webhook setup, payload handling and integration questions.