Руководства
Вебхуки входящих сообщений (слушатели)
Слушатель связывает один из ваших номеров WhatsApp с Unibox и может отправлять вебхук о входящем сообщении на ваш сервер. На панели управления слушатели настраиваются на странице Входящие триггеры. Как только номер становится слушателем, его чаты синхронизируются во входящие Unibox, а если вы добавите URL вебхука, Wbiztool будет отправлять каждое новое сообщение на ваш сервер. Используйте вебхуки, чтобы записывать переписку в CRM, оповещать команду или создать автоответчик.
Перед началом работы#
- Дополнение Unibox. Оно стоит $20 в месяц или $200 в год за номер WhatsApp, а количество слушателей, которое вы можете создать, равно количеству единиц дополнения. Купите его в разделе Доступные дополнения на странице Биллинг и планы в панели управления. Без него страница Входящие триггеры всё равно открывается, но нажатие Добавить нового слушателя показывает Unibox Add-on Required (Требуется дополнение Unibox) с кнопкой Subscribe to Unibox Add-on (Подписаться на дополнение Unibox).
- Подключённый номер WhatsApp в настройках WhatsApp. Добавить можно только подключённые номера, которые ещё не являются слушателями.
- Вы должны быть владельцем или редактором рабочего пространства.
- Для вебхуков: публичный URL (используйте
https) длиной не более 100 символов, который принимает запросыPOSTс телом в формате JSON.
Добавление слушателя#
Откройте «Входящие триггеры»
В боковом меню откройте Unibox и нажмите Входящие триггеры или перейдите на страницу Входящие триггеры.
Создайте нового слушателя
Нажмите на карточку Добавить нового слушателя.
Выберите номер
Выберите номер в поле Выберите номер WhatsApp. Если в списке написано Нет доступных номеров WhatsApp, значит, все подключённые номера уже являются слушателями или ни один номер не подключён.
Добавьте вебхук (необязательно)
Введите URL вебхука (Опционально). Как только вы введёте URL, появится раздел Webhook Events (События вебхука): оставьте отмеченными Incoming Messages (Входящие сообщения), Outgoing Messages (Исходящие сообщения) или оба варианта. URL можно добавить или изменить позже.
Сохраните
Нажмите Добавить слушателя. Слушатель появится в виде карточки со статусом Active (Активен). Если вы указали URL, для него создаётся секрет вебхука.
Управление слушателями#
На каждой карточке указаны номер, его статус, URL вебхука: (или Не настроено), Последняя активность: (когда номер в последний раз проверялся на наличие сообщений, или Никогда) и Секрет вебхука:, который скрыт, пока вы не нажмёте кнопку с глазом.
Откройте меню ⋮ на карточке, чтобы выполнить действие:
| Действие | Что происходит |
|---|---|
| Редактировать | Изменение URL вебхука, событий вебхука или секрета. Номер изменить нельзя. |
| Отключить / Включить | Отключение переводит слушателя в статус Inactive (Неактивен): номер перестаёт синхронизироваться с почтовым ящиком, и вебхуки не отправляются. Включение снова делает его Active. |
| Удалить | Удаляет слушателя после подтверждения. Беседы, которые уже есть в почтовом ящике, остаются. Если позже снова добавить тот же номер, слушатель восстанавливается. Если при повторном добавлении указать URL вебхука, слушатель сохраняет прежний секрет вебхука (если он был), а не получает новый. |
Статусы слушателей#
| Статус | Значение |
|---|---|
| Active (Активен) | Сообщения синхронизируются, и вебхуки отправляются, пока номер подключён. |
| Pending (Ожидание) | Номер не был подключён, когда создавался слушатель, например через Zapier. Нажмите Включить, когда номер будет подключён. |
| Inactive (Неактивен) | Отключён. Ничего не синхронизируется, и вебхуки не отправляются. |
Статистика#
| Карточка | Что показывает |
|---|---|
| Активные слушатели | Все слушатели на странице, включая отключённых. |
| Доступные номера | Подключённые номера, которые ещё не являются слушателями. Номера, слушатель которых вы удалили, здесь по-прежнему считаются использованными, поэтому значение может быть меньше, чем вы на самом деле можете добавить. |
| Общий лимит | Сколько слушателей позволяет ваше дополнение Unibox. |
| Сообщений сегодня | Пока не отслеживается; всегда показывает 0. |
Изменение URL или событий вебхука#
Откройте слушателя
Нажмите ⋮ на карточке, затем Редактировать.
Обновите настройки
Измените Webhook URL (Optional) (URL вебхука, необязательно) и Webhook Events. Очистите URL, чтобы прекратить отправку вебхуков для этого номера: его секрет тоже удаляется, а если снова добавить URL, будет создан новый.
Сохраните
Нажмите Update Listener (Обновить слушателя).
Повторная генерация секрета#
В окне Edit Listener (Редактирование слушателя) нажмите кнопку обновления рядом с Webhook Secret (Секрет вебхука) и подтвердите. Новый секрет сохраняется сразу, даже если после этого закрыть окно, не нажимая Update Listener, и с этого момента запросы подписываются им. Немедленно обновите секрет на своём сервере.
Как доставляются вебхуки#
Wbiztool отправляет на ваш URL один запрос POST для каждого нового сообщения, найденного при синхронизации номера. Синхронизация происходит каждые несколько минут, пока номер подключён и не занят отправкой сообщений.
- События:
message_received— для сообщений, которые люди присылают на ваш номер, иmessage_sent— для сообщений, отправленных с него (с телефона, из кампаний или через API). Отправляются только события, отмеченные в Webhook Events. - Ответы из входящих Unibox обычно не вызывают
message_sent, потому что к моменту синхронизации они уже есть в почтовом ящике. - Ответ: ответьте HTTP
200в течение 8 секунд. Любой другой ответ или тайм-аут считается неудачной доставкой. - Без повторных попыток: каждое сообщение отправляется один раз. Если ваш сервер недоступен, этот вебхук теряется.
- Порядок: запросы отправляются независимо друг от друга и могут приходить не по порядку. Если порядок важен, сортируйте по
message.timestamp.
Заголовки#
| Заголовок | Значение |
|---|---|
Content-Type | application/json |
X-Wbiztool-Event | message_received или message_sent |
X-Wbiztool-Timestamp | Время отправки вебхука в формате ISO 8601 UTC. Совпадает с timestamp в теле. |
X-Wbiztool-Webhook-Id | ID слушателя. Совпадает с webhook_id в теле. |
X-Wbiztool-Signature | sha256=, за которым следует подпись. Отправляется всегда, когда у слушателя есть секрет, а он есть всегда, если задан URL. |
Полезная нагрузка#
{
"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"
}
}{
"event": "message_sent",
"timestamp": "2026-09-16T10:33:40.117205+00:00",
"webhook_id": 42,
"whatsapp_client_id": "678",
"whatsapp_phone": "919812345678",
"message": {
"id": "[email protected]_3EB0F9E8D7C6B5A40312",
"type": "chat",
"content": "Yes, it will reach you today by 6 PM.",
"from": "919876543210",
"from_name": "Aman",
"to": "919812345678",
"timestamp": "2026-09-16T10:31:04+00:00",
"whatsapp_timestamp": 1789554664,
"direction": "outgoing",
"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"
}
}{
"event": "message_received",
"timestamp": "2026-09-16T10:36:02.904311+00:00",
"webhook_id": 42,
"whatsapp_client_id": "678",
"whatsapp_phone": "919812345678",
"message": {
"id": "[email protected]_3EB0A7B6C5D4E3F20109",
"type": "image",
"content": "📸 Image",
"from": "919876543210",
"from_name": "Aman",
"to": "919812345678",
"timestamp": "2026-09-16T10:34:51+00:00",
"whatsapp_timestamp": 1789554891,
"direction": "incoming",
"status": "pending",
"is_forwarded": false,
"forwarding_score": 0,
"media": {
"filename": "",
"mimetype": "image/jpeg",
"size": 245760
}
},
"contact": {
"whatsapp_id": "[email protected]",
"phone": "919876543210",
"name": "Aman",
"is_group": false,
"is_business": false
},
"organisation": {
"id": "10314",
"name": "Acme Stores"
}
}{
"event": "message_received",
"timestamp": "2026-09-16T10:40:15.330187+00:00",
"webhook_id": 42,
"whatsapp_client_id": "678",
"whatsapp_phone": "919812345678",
"message": {
"id": "[email protected][email protected]",
"type": "chat",
"content": "Is the store open on Sunday?",
"from": "120363041234567890",
"from_name": "Acme Loyalty Club",
"to": "919812345678",
"timestamp": "2026-09-16T10:39:02+00:00",
"whatsapp_timestamp": 1789555142,
"direction": "incoming",
"status": "pending",
"is_forwarded": false,
"forwarding_score": 0
},
"contact": {
"whatsapp_id": "[email protected]",
"phone": "120363041234567890",
"name": "Acme Loyalty Club",
"is_group": true,
"is_business": false
},
"organisation": {
"id": "10314",
"name": "Acme Stores"
},
"group": {
"name": "Acme Loyalty Club"
}
}Номера, ID и имена выше приведены для примера.
Поля верхнего уровня#
| Поле | Тип | Описание |
|---|---|---|
event | string | message_received или message_sent. |
timestamp | string | Время отправки вебхука (ISO 8601, UTC). |
webhook_id | integer | ID слушателя. |
whatsapp_client_id | string | ID вашего номера WhatsApp, как в настройках WhatsApp. |
whatsapp_phone | string | Ваш номер WhatsApp. |
message | object | Сообщение. См. ниже. |
contact | object | Человек или группа, с которыми ведётся беседа. См. ниже. |
organisation | object | id (string) и name вашего рабочего пространства. |
group | object | Только для групповых чатов: name — название группы. |
Поля message#
| Поле | Тип | Описание |
|---|---|---|
id | string | ID сообщения в WhatsApp. Используйте его, чтобы отбрасывать дубликаты. |
type | string | chat для текста. В остальных случаях — тип WhatsApp, например image, video, audio, ptt (голосовое сообщение), document, sticker или location. |
content | string | Текст для сообщений chat. Для медиафайлов вместо него метка: 📸 Image, 🎥 Video, 🎵 Audio, 🎤 Voice Message, 😊 Sticker, Document: с текстом документа (Document, если текста нет) или название типа с заглавной буквы для всего остального, например Location. Подписи не включаются. |
from | string | Всегда номер контакта (или ID группы) — в обоих направлениях. |
from_name | string | Имя контакта или название группы. Может быть пустым. |
to | string | Всегда ваш номер WhatsApp — в обоих направлениях. |
timestamp | string | Когда сообщение было отправлено в WhatsApp (ISO 8601, UTC). |
whatsapp_timestamp | integer | То же время в виде Unix-метки в секундах. |
direction | string | incoming или outgoing. Определяйте направление по этому полю, а не по from и to. |
status | string | Сейчас всегда pending. Не полагайтесь на него для определения статуса доставки или прочтения. |
is_forwarded | boolean | Было ли сообщение переслано. |
forwarding_score | integer | Сколько раз сообщение пересылалось. |
media | object | Для медиасообщений, если данные доступны: filename, mimetype и size в байтах. Сам файл не включается. |
quoted_message_id | string | Только если сообщение является ответом на другое сообщение. |
Поля contact#
| Поле | Тип | Описание |
|---|---|---|
whatsapp_id | string | WhatsApp ID, например [email protected] для человека или …@g.us для группы. |
phone | string | Номер без + или, для групп, ID группы. |
name | string | Имя контакта, известное Wbiztool, или название группы. Может быть пустым. |
is_group | boolean | true для групповых чатов. |
is_business | boolean | true для аккаунтов WhatsApp Business, если это известно. |
Проверка подписи#
Каждый запрос подписывается секретом вашего слушателя с помощью HMAC-SHA256. Подпись вычисляется по необработанному телу запроса в точности в том виде, в каком оно получено, и передаётся в X-Wbiztool-Signature как sha256= плюс шестнадцатеричный дайджест в нижнем регистре.
Всегда вычисляйте подпись по необработанным байтам до разбора JSON. Разбор и повторное кодирование меняют тело (например, символы, отличные от английских, и эмодзи приходят экранированными как \uXXXX), и подпись не совпадёт.
// 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);# Flask
import hashlib
import hmac
import json
import os
from flask import Flask, abort, request
app = Flask(__name__)
SECRET = os.environ["WBIZTOOL_WEBHOOK_SECRET"].encode()
@app.post("/wbiztool/webhook")
def wbiztool_webhook():
raw_body = request.get_data() # raw bytes, before parsing
expected = "sha256=" + hmac.new(SECRET, raw_body, hashlib.sha256).hexdigest()
received = request.headers.get("X-Wbiztool-Signature", "")
if not hmac.compare_digest(expected, received):
abort(401)
data = json.loads(raw_body)
if data["event"] == "message_received":
print(f"New message from {data['contact']['phone']}: {data['message']['content']}")
return "", 200<?php
$secret = getenv('WBIZTOOL_WEBHOOK_SECRET');
$rawBody = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
$received = $_SERVER['HTTP_X_WBIZTOOL_SIGNATURE'] ?? '';
if (!hash_equals($expected, $received)) {
http_response_code(401);
exit('Invalid signature');
}
$data = json_decode($rawBody, true);
if ($data['event'] === 'message_received') {
error_log('New message from ' . $data['contact']['phone'] . ': ' . $data['message']['content']);
}
http_response_code(200);Подпись не охватывает заголовок с меткой времени, поэтому не защищает от повторной отправки перехваченного запроса. Если это важно, сохраняйте каждый обработанный message.id и игнорируйте повторы.
Устранение неполадок#
| Сообщение или проблема | Что делать |
|---|---|
| Unibox Add-on Required (Требуется дополнение Unibox) при нажатии Добавить нового слушателя | У вашего рабочего пространства нет дополнения Unibox. Купите его в разделе Доступные дополнения на странице Биллинг и планы в панели управления. |
You have reached your unibox numbers limit | Удалите слушателя, который вам больше не нужен, или увеличьте количество единиц дополнения. |
| Нет доступных номеров WhatsApp | Подключите ещё один номер — или нужный номер уже является слушателем. |
This WhatsApp number is already a listener | Вместо этого отредактируйте существующую карточку. |
Invalid WhatsApp client | Номер отключился. Подключите его заново в настройках WhatsApp и перезагрузите страницу. |
Ошибка с упоминанием value too long при сохранении | URL вебхука длиннее 100 символов. Используйте более короткий URL. |
| Вебхуки не приходят | Проверьте, что слушатель в статусе Active, номер подключён, тип события отмечен, а ваш URL — публичный https с действительным сертификатом. Сообщения отправляются только после следующей синхронизации, через несколько минут. |
| Некоторые вебхуки пропадают | Ваш сервер вернул ответ, отличный от 200, отвечал дольше 8 секунд или был недоступен. Неудачные доставки не повторяются. |
| Подпись не совпадает | Используйте необработанное тело, а не повторно закодированный JSON, и текущий секрет. Повторная генерация секрета или подключение Zapier заменяет его. |
| Последняя активность: показывает Никогда | Номер ещё не проверялся. Он должен быть подключён и не занят отправкой сообщений. |
