WhatsAble

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

last_messagesstring

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.

conversation_paragraphstring

The same messages as one line of text, such as User (2:05:12 PM): ... ; Bot (2:01:40 PM): .... Times are in UTC.

phone_numberstring | number

The customer's WhatsApp number, digits only. A string on incoming messages and a number on outgoing ones.

recipient_namestring

The customer's name in Whatsable.

user_idstring

The ID of your Whatsable account. It is the same on every delivery. It does not identify the customer: use phone_number for that.

last_message_of_userstring

The customer's latest message with text.

last_message_of_botstring

The latest message your business sent: from the inbox, the WhatsApp Business app, the AI agent, a broadcast, the API or an automation.

message_typestring

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.

user_last_message_timeinteger | null

When the customer last wrote, in Unix seconds. null when the last messages hold none from the customer.

bot_last_message_timeinteger | null

When your business last wrote, in Unix seconds. null when the last messages hold none from your business.

attachment_urlstring

Always an empty string. The webhook does not carry media files.

notestring

The note your team wrote on the contact.

note_automationstring

The note your automations wrote on the contact.

labelsstring

The contact's labels, sorted by name and joined with , .

incoming_messageboolean

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.

is_scheduledboolean

On incoming messages: false for a single message, true for combined messages. Missing on outgoing messages.

send_bystring

Outgoing messages only. Who sent the message:

send_bySent by
WhatsApp AppSomeone typing in the WhatsApp Business app on the phone
AIYour AI agent, including its follow-ups, or an automatic reply: the away message or the keep-the-window-open follow-up
NotifyerAnything 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.

was_message_by_humanboolean

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.

referralobject

Incoming messages only, and only when the message came from a Click-to-WhatsApp ad or post. See Click-to-WhatsApp ads.

button_replyobject

Incoming messages only, when the customer tapped a button. See button replies.

business_phone_number_idstring

Only when the endpoint's Number is All numbers: the WhatsApp phone number ID of your number that sent or received the message.

business_phone_numberstring

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.

GoalFields
Group leads by campaign or creativesource_id, headline
See Facebook vs Instagram vs othersource_url (host is instagram.com, facebook.com, fb.me, …)
Show your team the ad the lead sawheadline, body, image_url / video_url, media_type
Send conversions back to Metactwa_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.

HeaderValue
X-Notifyer-TimestampThe time of the request, in Unix seconds
X-Notifyer-Signature-256sha256= 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.

FAQs

Need assistance?

Our support team can help with webhook setup, payload handling and integration questions.