Skip to content
Wbiztool

Product guides

Incoming message webhooks (listeners)

A listener connects one of your WhatsApp numbers to Unibox and can send an incoming message webhook to your server. In the dashboard, listeners are managed on the Incoming Triggers page. Once a number is a listener, its chats sync into the Unibox inbox, and, if you add a webhook URL, Wbiztool sends each new message to your server. Use webhooks to log conversations in a CRM, alert your team or build an auto-reply.

Before you start#

  • The Unibox add-on. It costs $20/month or $200/year per WhatsApp number, and the number of listeners you can have equals the add-on quantity. Buy it under Available Add-ons on Billing & Plans inside the dashboard. Without it, the Incoming Triggers page still opens, but clicking Add New Listener shows Unibox Add-on Required with a Subscribe to Unibox Add-on button.
  • A connected WhatsApp number on WhatsApp settings. Only connected numbers that aren't listeners yet can be added.
  • You must be an owner or editor of the workspace.
  • For webhooks: a public URL (use https) of 100 characters or fewer that accepts POST requests with a JSON body.

Add a listener#

  1. Open Incoming Triggers

    In the sidebar, open Unibox and click Incoming Triggers, or go to Incoming Triggers.

  2. Start a new listener

    Click the Add New Listener card.

  3. Choose the number

    Pick it in Select WhatsApp Number. If the list says No available WhatsApp numbers, every connected number is already a listener, or none is connected.

  4. Add a webhook (optional)

    Enter your Webhook URL (Optional). Once you type a URL, Webhook Events appears: keep Incoming Messages, Outgoing Messages or both ticked. You can add or change the URL later.

  5. Save

    Click Add Listener. The listener appears as a card with the status Active. If you entered a URL, a webhook secret is created for it.

Manage listeners#

Each card shows the number, its status, the Webhook URL: (or Not configured), Last Activity: (when the number was last checked for messages, or Never) and the Webhook Secret:, hidden until you click the eye button.

Open the menu on a card to:

ActionWhat happens
EditChange the webhook URL, the webhook events or the secret. The number can't be changed.
Disable / EnableDisabling sets the listener to Inactive: the number stops syncing to the inbox and no webhooks are sent. Enabling makes it Active again.
DeleteRemoves the listener after you confirm. Conversations already in the inbox stay. Adding the same number again later restores the listener. If you enter a webhook URL when re-adding it, the listener keeps its previous webhook secret, if it had one, instead of getting a new one.

Listener statuses#

StatusMeaning
ActiveMessages sync and webhooks are sent while the number is connected.
PendingThe number wasn't connected when the listener was created, for example by Zapier. Click Enable once the number is connected.
InactiveDisabled. Nothing syncs and no webhooks are sent.

Stats#

CardWhat it shows
Active ListenersAll listeners on the page, including disabled ones.
Available NumbersConnected numbers that aren't listeners yet. Numbers whose listener you deleted still count as used here, so this can show fewer than you can actually add.
Total LimitHow many listeners your Unibox add-on allows.
Messages TodayNot tracked yet; always shows 0.

Change the webhook URL or events#

  1. Open the listener

    Click on the card, then Edit.

  2. Update the settings

    Change the Webhook URL (Optional) and the Webhook Events. Clear the URL to stop webhooks for this number: its secret is removed too, and a new one is created if you add a URL again.

  3. Save

    Click Update Listener.

Regenerate the secret#

In Edit Listener, click the refresh button next to Webhook Secret and confirm. The new secret is saved straight away, even if you then close the dialog without clicking Update Listener, and requests are signed with it from then on. Update your server with the new secret immediately.

How webhooks are delivered#

Wbiztool sends one POST request to your URL for each new message found when the number syncs, which happens every few minutes while the number is connected and isn't busy sending messages.

  • Events: message_received for messages people send to your number, and message_sent for messages sent from it (from the phone, campaigns or the API). Only the events ticked in Webhook Events are sent.
  • Replies from the Unibox inbox usually don't trigger message_sent, because the inbox already has them when the sync runs.
  • Response: reply with HTTP 200 within 8 seconds. Any other response or a timeout counts as a failed delivery.
  • No retries: each message is sent once. If your server is down, that webhook is lost.
  • Order: requests are sent independently and can arrive out of order. Sort by message.timestamp if order matters.

Headers#

HeaderValue
Content-Typeapplication/json
X-Wbiztool-Eventmessage_received or message_sent
X-Wbiztool-TimestampWhen the webhook was sent, in ISO 8601 UTC. Same as timestamp in the body.
X-Wbiztool-Webhook-IdThe listener's ID. Same as webhook_id in the body.
X-Wbiztool-Signaturesha256= followed by the signature. Sent whenever the listener has a secret, which is always the case when a URL is set.

Payload#

Example webhook bodies
{
  "event": "message_received",
  "timestamp": "2026-09-16T10:31:12.482913+00:00",
  "webhook_id": 42,
  "whatsapp_client_id": "678",
  "whatsapp_phone": "919812345678",
  "message": {
    "id": "[email protected]_3EB0C1A2B3D4E5F60718",
    "type": "chat",
    "content": "Hi, is my order #4821 out for delivery?",
    "from": "919876543210",
    "from_name": "Aman",
    "to": "919812345678",
    "timestamp": "2026-09-16T10:29:58+00:00",
    "whatsapp_timestamp": 1789554598,
    "direction": "incoming",
    "status": "pending",
    "is_forwarded": false,
    "forwarding_score": 0
  },
  "contact": {
    "whatsapp_id": "[email protected]",
    "phone": "919876543210",
    "name": "Aman",
    "is_group": false,
    "is_business": false
  },
  "organisation": {
    "id": "10314",
    "name": "Acme Stores"
  }
}

The numbers, IDs and names above are examples.

Top-level fields#

FieldTypeDescription
eventstringmessage_received or message_sent.
timestampstringWhen the webhook was sent (ISO 8601, UTC).
webhook_idintegerThe listener's ID.
whatsapp_client_idstringID of your WhatsApp number, as shown on WhatsApp settings.
whatsapp_phonestringYour WhatsApp number.
messageobjectThe message. See below.
contactobjectThe person or group the conversation is with. See below.
organisationobjectid (string) and name of your workspace.
groupobjectOnly for group chats: name, the group's name.

message fields#

FieldTypeDescription
idstringWhatsApp's ID for the message. Use it to ignore duplicates.
typestringchat for text. Otherwise WhatsApp's type, such as image, video, audio, ptt (voice note), document, sticker or location.
contentstringThe text for chat messages. For media, a label instead: 📸 Image, 🎥 Video, 🎵 Audio, 🎤 Voice Message, 😊 Sticker, Document: followed by the document's text (Document when there's no text), or the type name in title case for anything else, such as Location. Captions aren't included.
fromstringAlways the contact's number (or the group's ID), in both directions.
from_namestringThe contact's or group's name. Can be empty.
tostringAlways your WhatsApp number, in both directions.
timestampstringWhen the message was sent on WhatsApp (ISO 8601, UTC).
whatsapp_timestampintegerThe same time as a Unix timestamp in seconds.
directionstringincoming or outgoing. Use this, not from and to, to tell the direction.
statusstringCurrently always pending. Don't rely on it for delivery or read status.
is_forwardedbooleanWhether the message was forwarded.
forwarding_scoreintegerHow many times it was forwarded.
mediaobjectFor media messages when details are available: filename, mimetype and size in bytes. The file itself isn't included.
quoted_message_idstringOnly when the message replies to another message.

contact fields#

FieldTypeDescription
whatsapp_idstringThe WhatsApp ID, such as [email protected] for a person or …@g.us for a group.
phonestringThe number without +, or the group's ID for groups.
namestringThe name Wbiztool has for the contact, or the group name. Can be empty.
is_groupbooleantrue for group chats.
is_businessbooleantrue for WhatsApp Business accounts, when known.

Verify the signature#

Each request is signed with your listener's secret using HMAC-SHA256. The signature is calculated over the raw request body exactly as received, and sent as sha256= plus the lowercase hex digest in X-Wbiztool-Signature.

Always compute the signature from the raw bytes before parsing the JSON. Parsing and re-encoding the body changes it (for example, non-English characters and emoji arrive escaped as \uXXXX), and the signature won't match.

// Express: keep the raw body for this route
import crypto from "node:crypto";
import express from "express";

const app = express();
const SECRET = process.env.WBIZTOOL_WEBHOOK_SECRET;

app.post("/wbiztool/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const expected = "sha256=" + crypto.createHmac("sha256", SECRET).update(req.body).digest("hex");
  const received = req.get("X-Wbiztool-Signature") || "";

  const valid =
    received.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
  if (!valid) return res.status(401).send("Invalid signature");

  const data = JSON.parse(req.body.toString("utf8"));
  if (data.event === "message_received") {
    console.log(`New message from ${data.contact.phone}: ${data.message.content}`);
  }
  res.sendStatus(200); // reply quickly; do slow work in the background
});

app.listen(3000);

The signature doesn't cover the timestamp header, so it doesn't protect against a request being replayed. If that matters, store each message.id you've processed and ignore repeats.

Troubleshooting#

Message or problemWhat to do
Unibox Add-on Required when you click Add New ListenerYour workspace doesn't have the Unibox add-on. Buy it under Available Add-ons on Billing & Plans inside the dashboard.
You have reached your unibox numbers limitDelete a listener you no longer need, or increase the add-on quantity.
No available WhatsApp numbersConnect another number, or it's already a listener.
This WhatsApp number is already a listenerEdit the existing card instead.
Invalid WhatsApp clientThe number disconnected. Reconnect it on WhatsApp settings and reload the page.
Error mentioning value too long when savingThe webhook URL is longer than 100 characters. Use a shorter URL.
No webhooks arriveCheck the listener is Active, the number is connected, the event type is ticked, and your URL is public https with a valid certificate. Messages are only sent after the next sync, a few minutes later.
Some webhooks are missingYour server returned something other than 200, took longer than 8 seconds or was unreachable. Failed deliveries aren't retried.
Signature doesn't matchUse the raw body, not re-encoded JSON, and the current secret. Regenerating the secret, or connecting Zapier, replaces it.
Last Activity: says NeverThe number hasn't been checked yet. It must be connected and not busy sending messages.