Webhooks

    7 мин чтения
    ·
    403 просмотра
    ·
    Обновлено 23.10.2026

    Webhooks: уведомления о событиях

    Webhooks позволяют получать HTTP-уведомления на ваш сервер при наступлении событий: входящие сообщения, исходящие сообщения, изменения статусов и другие.

    Как работает

    1. Вы регистрируете URL вашего сервера через PATCH /v1/webhooks
    2. WazzaBee отправляет тестовый запрос на ваш URL: если он не вернёт 200, регистрация будет отклонена
    3. При наступлении событий WazzaBee отправляет POST-запрос на ваш URL с данными события
    4. Каждый запрос подписан HMAC-SHA256, и вы можете проверить подлинность

    Два способа подключить webhook

    1. Через личный кабинет (просто, без программиста). Откройте «Коннекторы» → «API» → блок «Webhook для входящих». Вставьте адрес вашего сервера и нажмите «Подключить», и кабинет сам проверит адрес и покажет секретный ключ для подписи.

    2. Через REST API (для автоматизации). Отправьте запрос PATCH /v1/webhooks, как описано ниже.

    Вебхук настраивается один раз, поэтому запрос удобнее отправить со своего сервера. Если отправляете его из браузера и видите Failed to fetch, проверьте, что метод написан заглавными буквами (PATCH), а ваш адрес отвечает 200 на тестовый запрос не дольше 10 секунд.


    PATCH /v1/webhooks: регистрация/обновление

    URL

    PATCH https://api.wazzabee.com/api-gateway/v1/webhooks

    Заголовки

    Authorization: Bearer ВАШ_API_КЛЮЧ
    
    Content-Type: application/json

    Параметры тела запроса

    ПараметрТипОбязательныйОписание
    webhooksUristring (URL)✅ ДаURL вашего сервера для получения уведомлений. Только https, должен быть доступен из интернета и возвращать 200 на POST-запросы. Не может указывать на локальные или внутренние адреса (localhost, 127.0.0.1, 10.x, 172.16–31.x, 192.168.x, 169.254.x)
    subscriptionsarray of stringsНетСписок событий для подписки. По умолчанию: ["messages_incoming"]
    headersobjectНетСвои заголовки к каждому уведомлению, до 10 штук. Например {"Authorization": "Bearer ..."} для авторизации на вашем сервере или идентификатор вашей системы. Служебные заголовки (Content-Type, X-Webhook-*) задать нельзя. Не передали: остаются прежние; null или {}: убираются. Статические параметры можно указать прямо в webhooksUri после ?

    Доступные подписки (subscriptions)

    СобытиеОписание
    messages_incomingВходящие сообщения от клиентов
    messages_outgoingИсходящие сообщения (отправленные через API или вручную)
    messages_statusСтатусы исходящих сообщений: отправлено, доставлено, прочитано, ошибка. Формат ниже, в разделе «Статусы доставки»
    messages_buttonНажатия кнопок в шаблонах отдельным событием: с кампанией и рассылкой, на которую ответил клиент. Формат ниже, в разделе «Нажатия кнопок»
    contactsAndDealsCreationСоздание новых контактов и сделок
    channelsUpdatesИзменение статуса каналов (подключение, отключение, ошибка)

    Пример запроса

    curl -X PATCH "https://api.wazzabee.com/api-gateway/v1/webhooks" \
    
      -H "Authorization: Bearer ВАШ_API_КЛЮЧ" \
    
      -H "Content-Type: application/json" \
    
      -d '{
    
        "webhooksUri": "https://ваш-сервер.com/webhook/wazzabee",
    
        "subscriptions": [
    
          "messages_incoming",
    
          "messages_outgoing",
    
          "messages_status"
    
        ]
    
      }'

    Успешный ответ (200 OK)

    {
    
      "success": true,
    
      "webhooksUri": "https://ваш-сервер.com/webhook/wazzabee",
    
      "subscriptions": [
    
        "messages_incoming",
    
        "messages_outgoing",
    
        "messages_status"
    
      ],
    
      "secret": "a1b2c3d4e5f6...64-символьный-hex-ключ",
    
      "message": "Вебхук установлен. Сохраните secret, он используется для подписи уведомлений (HMAC-SHA256)."
    
    }
    🔴 Важно! Поле secret генерируется заново при каждом вызове PATCH. Обязательно сохраните его: он нужен для проверки подписи входящих уведомлений.

    Тестовый запрос

    При регистрации вебхука WazzaBee сразу отправляет тестовый POST-запрос на ваш URL:

    {
    
      "test": true,
    
      "event": "webhook_test",
    
      "challenge": "случайная_строка_для_подтверждения",
    
      "timestamp": "2026-03-12T10:00:00.000Z",
    
      "message": "WazzaBee webhook test: если вы видите это, ваш webhook работает корректно."
    
    }

    Ваш сервер должен ответить статусом 200. Если ответ не 200 или URL недоступен, регистрация вебхука будет отклонена с ошибкой.

    Поле challenge (рекомендуется). В тестовом запросе приходит поле challenge со случайной строкой. Если ваш сервер вернёт это же значение (в теле ответа или JSON-полем challenge, например {"challenge": "то же значение"}), webhook получит отметку «подтверждён» (более высокий уровень доверия). Это не обязательно: если ваш приёмник (например готовый no-code сервис) не умеет возвращать значение, webhook всё равно подключится и будет работать под защитой HMAC-подписи.

    Возможные ошибки при регистрации

    КодОшибкаПричина
    400Поле webhooksUri обязательноНе передан URL
    400webhooksUri должен быть валидным URLФормат URL некорректен
    400webhooksUri должен использовать https://Адрес начинается не с https://
    400webhooksUri не может указывать на локальный или внутренний адресURL ведёт на localhost, приватную сеть или metadata-адрес
    400Тестовый запрос вернул ошибку NВаш сервер вернул не-200 ответ
    400Не удалось отправить тестовый запросURL недоступен или таймаут (10 секунд)

    GET /v1/webhooks: текущие настройки

    URL

    GET https://api.wazzabee.com/api-gateway/v1/webhooks

    Пример запроса

    curl -X GET "https://api.wazzabee.com/api-gateway/v1/webhooks" \
    
      -H "Authorization: Bearer ВАШ_API_КЛЮЧ"

    Ответ: вебхук настроен (200 OK)

    {
    
      "success": true,
    
      "webhook": {
    
        "webhooksUri": "https://ваш-сервер.com/webhook/wazzabee",
    
        "subscriptions": ["messages_incoming", "messages_outgoing"],
    
        "status": "active",
    
        "failureCount": 0,
    
        "createdAt": "2026-03-12T08:00:00.000Z",
    
        "updatedAt": "2026-03-12T08:00:00.000Z"
    
      }
    
    }

    Ответ: вебхук не настроен (200 OK)

    {
    
      "success": true,
    
      "webhook": null,
    
      "message": "Вебхук не настроен"
    
    }

    Описание полей

    ПолеТипОписание
    webhooksUristringЗарегистрированный URL
    subscriptionsarrayАктивные подписки
    statusstringactive или disabled
    failureCountintegerСколько уведомлений подряд не доставлено после всех повторов
    createdAtstring (ISO 8601)Дата создания
    updatedAtstring (ISO 8601)Дата последнего обновления

    Повторная отправка

    Если ваш сервер не ответил кодом 2xx за 10 секунд (вернул ошибку, недоступен или не успел ответить), WazzaBee отправит то же уведомление повторно. Паузы между попытками растут: 1 минута, 5 минут, 15 минут, 1 час, 3 часа, 6 часов, 12 часов. Всего 8 попыток примерно за сутки.

    Заголовки каждой отправки

    ЗаголовокОписание
    X-Webhook-SignatureПодпись HMAC-SHA256 тела запроса, проверка описана ниже
    X-Webhook-Delivery-IdИдентификатор уведомления. Одинаковый у первой отправки и всех повторов
    X-Webhook-AttemptНомер попытки: 1 у первой отправки, 2 и дальше у повторов
    Как не обработать уведомление дважды. Сохраняйте X-Webhook-Delivery-Id обработанных уведомлений и пропускайте запрос с уже знакомым идентификатором. Повтор может прийти, даже если первая отправка дошла, но ваш сервер ответил позже 10 секунд. Поэтому отвечайте 200 сразу, а долгую обработку делайте после ответа.

    Повтор может прийти позже следующих уведомлений. Порядок сообщений восстанавливайте по полю timestamp.

    Когда вебхук отключается

    Уведомление считается недоставленным, если не прошла ни одна из 8 попыток. После 10 недоставленных уведомлений подряд вебхук переходит в статус disabled. Любая успешная доставка обнуляет счётчик. Все попытки видны в журнале доставки в кабинете: «Коннекторы» → «API».


    Статусы доставки (messages_status)

    По каждому исходящему сообщению с линии официального WhatsApp (WABA) приходит уведомление о смене статуса. queueId совпадает с queueId из ответа POST /v1/messages/send, по нему сопоставляйте статус со своей отправкой. Если при отправке был передан campaignId, он тоже приходит.

    {
      "statuses": [{
        "messageId": "uuid",
        "queueId": "uuid",
        "campaignId": "promo_oct",
        "channelId": "uuid",
        "chatId": "77071234567",
        "conversationId": "uuid",
        "contactId": "uuid",
        "status": "delivered",
        "error": null,
        "timestamp": "2026-09-30T12:00:00.000Z"
      }]
    }
    statusЧто значит
    sentСообщение принято WhatsApp
    deliveredДоставлено на телефон абонента
    readПрочитано
    failedНе доставлено. В error код и понятное описание причины, например {"code": 131026, "description": "..."}

    Те же данные можно запросить в GET /v1/messages: поля status, sentAt, deliveredAt, readAt, error.


    Нажатия кнопок (messages_button)

    Когда абонент нажимает кнопку шаблона, кроме обычного входящего приходит отдельное событие. В replyTo уже указано, на какое исходящее ответили: queueId, campaignId (ваша метка из отправки через API) и broadcastId (если шаблон ушёл рассылкой из кабинета). Искать исходное сообщение по context самому не нужно.

    {
      "buttons": [{
        "messageId": "uuid",
        "channelId": "uuid",
        "chatId": "77071234567",
        "contactId": "uuid",
        "conversationId": "uuid",
        "button": { "type": "button", "id": null, "title": "Да, интересно", "payload": "YES_INTERESTED" },
        "replyTo": {
          "messageId": "uuid",
          "queueId": "uuid",
          "campaignId": "promo_oct",
          "broadcastId": null
        },
        "timestamp": "2026-09-30T12:00:00.000Z"
      }]
    }

    replyTo равен null, если исходное сообщение не найдено (например, отправлено не через WazzaBee).


    Свои заголовки и параметры в адресе

    Если ваш сервер принимает уведомления только с авторизацией, передайте заголовки в поле headers при регистрации вебхука. Они уходят с каждым уведомлением, с повторами и с тестовым запросом. Статические данные (например, идентификатор вашей системы) можно добавить прямо в адрес: https://ваш-сервер.com/hook?source=wazzabee&system=crm. Заголовки можно задать и в кабинете: «Коннекторы» → «API» → блок вебхука.

    {
      "webhooksUri": "https://ваш-сервер.com/hook?source=wazzabee",
      "subscriptions": ["messages_incoming", "messages_status", "messages_button"],
      "headers": { "Authorization": "Bearer ваш-токен" }
    }

    Проверка подписи (HMAC-SHA256)

    Каждое уведомление подписано с помощью secret, полученного при регистрации вебхука. Рекомендуем всегда проверять подпись для защиты от поддельных запросов.

    Пример проверки на Node.js

    const crypto = require("crypto");
    
    
    
    function verifyWebhookSignature(body, signature, secret) {
    
      const expected = crypto
    
        .createHmac("sha256", secret)
    
        .update(JSON.stringify(body))
    
        .digest("hex");
    
      return signature === expected;
    
    }
    
    
    
    // В обработчике webhook:
    
    app.post("/webhook/wazzabee", (req, res) => {
    
      const signature = req.headers["x-webhook-signature"];
    
      if (!verifyWebhookSignature(req.body, signature, YOUR_WEBHOOK_SECRET)) {
    
        return res.status(401).send("Invalid signature");
    
      }
    
      // Обработка события...
    
      res.status(200).send("OK");
    
    });

    Пример проверки на Python

    import hmac
    
    import hashlib
    
    import json
    
    
    
    def verify_signature(body: dict, signature: str, secret: str) -> bool:
    
        expected = hmac.new(
    
            secret.encode(),
    
            json.dumps(body).encode(),
    
            hashlib.sha256
    
        ).hexdigest()
    
        return hmac.compare_digest(signature, expected)
    
    
    
    # В обработчике Flask:
    
    @app.route("/webhook/wazzabee", methods=["POST"])
    
    def handle_webhook():
    
        signature = request.headers.get("X-Webhook-Signature", "")
    
        if not verify_signature(request.json, signature, YOUR_WEBHOOK_SECRET):
    
            return "Invalid signature", 401
    
        # Обработка события...
    
        return "OK", 200

    Формат входящего webhook-уведомления

    При поступлении входящего сообщения WazzaBee отправляет POST-запрос на ваш URL со следующей структурой:

    {
      "channel_id": "uuid",
      "channel_type": "whatsapp_waba",
      "channel_name": "WhatsApp Business",
      "conversation_id": "uuid",
      "message": {
        "id": "msg_id",
        "from": "77771234567",
        "sender_name": "Клиент",
        "type": "text",
        "text": "Здравствуйте!",
        "timestamp": "1712345678"
      },
      "operator": {
        "crm_user_id": "manager-42",
        "crm_user_name": "Алексей Менеджер"
      },
      "ad": {
        "ad_id": "120255518599590051",
        "source_type": "ad",
        "headline": "Visa4U",
        "text": "Отказали в визе? Не все потеряно!",
        "media_type": "video",
        "image_url": "https://scontent.xx.fbcdn.net/...jpg",
        "video_url": "https://video.xx.fbcdn.net/...mp4",
        "url": "https://fb.me/2abcXYZ",
        "click_id": "AfgHqhgPqgcChoXjlCqLkHfWKeS8"
      },
      "raw": { ... }
    }

    Описание полей

    ПолеТипОписание
    channel_idstring (UUID)Идентификатор линии связи WazzaBee
    channel_typestringТип линии: whatsapp_waba, whatsapp_qr, telegram_bot, telegram_personal
    channel_namestringНазвание линии связи
    conversation_idstring (UUID) | nullИдентификатор диалога. Используйте для группировки сообщений одного клиента
    messageobjectДанные сообщения
    operatorobject | nullПоследний оператор, ответивший в этом чате. null если ещё никто не отвечал
    operator.crm_user_idstringID пользователя из вашей CRM (переданный при создании iframe-сессии)
    operator.crm_user_namestringИмя оператора. Берём его из user.name вашей iframe-сессии, а при отправке через API из поля senderName
    adobject | отсутствуетИсточник обращения: с какого объявления пришёл клиент. Блок приходит только у обращений с рекламы Click-to-WhatsApp на линиях официального WhatsApp (WABA). Описание полей ниже
    rawobjectПолный оригинальный payload от провайдера (для отладки)
    Массив messages в том же запросе. Вместе с полями выше приходит массив messages в формате, описанном ниже в разделе про messages_outgoing, только с "direction": "incoming". Это одно уведомление об одном сообщении: читайте тот формат, который вам удобнее, второй можно не разбирать. Уникальный идентификатор уведомления приходит в заголовке X-Webhook-Delivery-Id.

    Блок ad: источник обращения

    Если клиент нажал «Написать в WhatsApp» в рекламе Instagram или Facebook, вместе с уведомлением приходит блок ad. У обычных обращений его нет: проверяйте наличие самого поля, а не значения внутри. Блок приходит только на линиях официального WhatsApp: такие данные Meta передаёт лишь по WABA.

    ПолеТипОписание
    ad_idstring | nullИдентификатор объявления. По нему объявление находится в рекламном кабинете Meta
    source_typestring | nullad: реклама, post: обычная публикация
    headlinestring | nullЗаголовок объявления. Может повторяться у разных объявлений: для точной привязки используйте ad_id
    textstring | nullТекст объявления
    media_typestring | nullТип креатива: image или video
    image_urlstring | nullКартинка объявления или кадр из видео
    video_urlstring | nullВидео объявления
    urlstring | nullСсылка на объявление
    click_idstring | nullИдентификатор клика по рекламе (ctwa_clid). Сохраните его у себя: по нему в Meta возвращают события о продаже, чтобы реклама оптимизировалась на покупателей
    Что с этим делать. Запишите ad_id и click_id в карточку клиента у себя, тогда в отчётах будет видно, какое объявление приносит сделки, а не просто обращения.
    Поле operator позволяет вашей CRM точнее направлять уведомления. Если оператор уже отвечал клиенту, вы знаете кому адресовать новое входящее сообщение.

    Ответы клиентов на кнопки WABA-шаблонов

    Когда вы отправляете клиенту одобренный WABA-шаблон с кнопками Quick Reply (или Interactive buttons / list), и клиент нажимает кнопку, WazzaBee присылает в webhook дополнительные поля message.interactive и message.context. Поле message.text содержит реальный текст нажатой кнопки, его можно показывать в чате CRM как обычное сообщение клиента.

    Значения message.type

    ЗначениеКогда приходит
    textОбычное текстовое сообщение
    image, video, audio, document, stickerМедиа
    location, contacts, reactionСлужебные типы WhatsApp
    buttonКлик по Quick Reply кнопке WABA-шаблона (старый Meta формат)
    interactiveКлик по Interactive button / list (Meta Cloud API v3)

    Структура поля message.interactive

    {
    
      "type": "button" | "button_reply" | "list_reply",
    
      "id":    "идентификатор кнопки" | null,
    
      "title": "текст кнопки, который видел клиент" | null,
    
      "description": "описание (только для list_reply)" | null,
    
      "payload": "значение payload (только для type=button)" | null
    
    }

    Структура поля message.context

    Ссылка на исходное HSM-сообщение, на кнопку которого нажал клиент:

    {
    
      "id":          "uuid исходящего сообщения в WazzaBee",
    
      "from":        "номер отправителя (ваш WABA-номер)",
    
      "gs_id":       "внутренний ID сообщения у провайдера",
    
      "meta_msg_id": "wamid.HBgL... (ID в Meta)"
    
    }

    Пример webhook: клиент нажал кнопку «Да, буду вовремя»

    {
    
      "channel_id": "f0e8...",
    
      "channel_type": "whatsapp_waba",
    
      "channel_name": "WhatsApp Business",
    
      "conversation_id": "b1c2...",
    
      "message": {
    
        "id": "wamid.HBgLNzcwMTEwMDEwNTkVAgASGBQzQTY5NkJCNjE3M0JGQjE1MjVEMQA=",
    
        "from": "77011001059",
    
        "sender_name": "Фархат",
    
        "type": "button",
    
        "text": "Да, буду вовремя",
    
        "interactive": {
    
          "type": "button",
    
          "title": "Да, буду вовремя",
    
          "payload": "Да, буду вовремя"
    
        },
    
        "context": {
    
          "id": "584abab5-911a-49ca-a750-95e8116be1e1",
    
          "from": "77079119977",
    
          "gs_id": "378ba4ed-5a5d-4c58-a4e8-b6ca08b2ad7a",
    
          "meta_msg_id": "wamid.HBgL..."
    
        },
    
        "timestamp": "1776870877"
    
      },
    
      "operator": null,
    
      "raw": { ... }
    
    }
    ✅ Совместимость со старыми интеграциями. Поле message.text теперь содержит реальный текст нажатой кнопки, поэтому старые клиенты, которые читают только text, будут видеть в чате осмысленное сообщение «Да, буду вовремя» вместо технической заглушки.

    Пример: клик по Interactive list (Meta Cloud API v3)

    {
    
      "message": {
    
        "type": "interactive",
    
        "text": "Доставка на дом",
    
        "interactive": {
    
          "type": "list_reply",
    
          "id": "delivery_home",
    
          "title": "Доставка на дом",
    
          "description": "По адресу клиента"
    
        },
    
        "context": { "meta_msg_id": "wamid.HBgL..." }
    
      }
    
    }

    Исходящие WABA-шаблоны (messages_outgoing)

    Когда вы отправляете HSM-шаблон через API или из UI WazzaBee, при подписке на messages_outgoing в webhook приходит структура с полем template (если шаблон содержит кнопки):

    {
    
      "messages": [{
    
        "messageId": "uuid",
    
        "direction": "outgoing",
    
        "type": "template",
    
        "text": "Здравствуйте! Подтвердите встречу завтра в 15:00.",
    
        "template": {
    
          "name": "appointment_reminder_v2",
    
          "language": "ru",
    
          "buttons": [
    
            { "type": "quick_reply", "text": "Да, буду вовремя" },
    
            { "type": "quick_reply", "text": "Перенести" }
    
          ]
    
        },
    
        "timestamp": "2026-04-22T15:14:42Z"
    
      }]
    
    }

    Это позволяет вашей CRM отрисовать кнопки в ленте чата точно так же, как они выглядят у клиента в WhatsApp.


    Кто отправил исходящее сообщение (author)

    Событие messages_outgoing приходит на любой ответ клиенту: написанный оператором в кабинете WazzaBee, отправленный из чата, встроенного в вашу CRM, посланный шаблоном или через POST /v1/messages/send. Вместе с сообщением приходит блок author: кто именно ответил.

    {
      "messages": [{
        "messageId": "uuid",
        "channelId": "uuid",
        "chatId": "77001234567",
        "contactId": "uuid",
        "conversationId": "uuid",
        "direction": "outgoing",
        "type": "text",
        "text": "Здравствуйте! Ваши документы готовы.",
        "author": {
          "kind": "operator",
          "name": "Назерке А.",
          "userId": "manager-42"
        },
        "timestamp": "2026-07-30T09:20:12.000Z"
      }]
    }

    Поля блока author

    ПолеТипОписание
    author.kindstringВид отправки: operator (ответил человек), api (сообщение отправила ваша система), automation (робот или автоответ), broadcast (рассылка)
    author.namestring | nullИмя сотрудника. Для ответов из встроенного чата это то самое имя, которое вы передали в user.name при создании iframe-сессии; при отправке по API это senderName. null, если имя не передавали
    author.userIdstring | nullИдентификатор сотрудника в вашей CRM (user.id из iframe-сессии или userId при отправке по API). Возвращаем как есть, сопоставлять его с пользователями WazzaBee не нужно
    ✅ Как получить имя автора. Ничего дополнительно настраивать не нужно: передавайте user.id и user.name при создании iframe-сессии, и имя сотрудника будет и в подписи под сообщением в чате, и в этом вебхуке. Для сообщений, которые ваша система отправляет сама, передавайте senderName и userId в запросе на отправку.