enВойти в Senler

Вебхуки

Вебхуки приложения

На странице «Вебхуки» developer-приложение настраивает отправку выбранных событий на внешний URL. Один webhook может слушать несколько типов событий, а у одного приложения может быть несколько webhooks с разными адресами и наборами событий.

Для рабочего endpoint используйте HTTPS. Одна попытка доставки ждёт ответ не более 120 секунд, но обработчику лучше быстро принять событие в собственную очередь и вернуть 2xx.

Вкладка «Общие» содержит публичные события приложения, «Для инструментов» — запросы HTTP-инструментов, а «Для шагов» — внутренние webhook шагов автоматизаций. У всех видов есть история запросов, однако режим ожидания результата агента применяется только к инструментам.

Статистика доставки

Блок «Статистика вебхуков» объединяет доставку всех webhook приложения. В переключателе периода доступны 7, 30 и 90 дней.

Показатели разделяют принятые к доставке, доставленные, не доставленные, находящиеся в обработке и отмененные операции. «Попытки доставки» — это фактические HTTP-запросы, включая автоматические повторы и тестовые отправки. Поэтому попыток может быть больше, чем принятых событий.

Какие события доступны

  • command_start — пользователь отправил /start; в data приходят command и args;
  • message_allow — пользователь разрешил отправку сообщений;
  • message_new — новое сообщение пользователя; текст находится в data.content;
  • button_clicked — пользователь нажал кнопку в сообщении;
  • lead_created — создан лид;
  • lead_unsubscribed — пользователь отписался от сообщений;
  • lead_blocked — пользователь заблокировал бота;
  • message_undelivered — сообщение не доставлено;
  • error — при обработке события произошла ошибка.

Для событий, связанных с подпиской или ошибкой, объект data может содержать key и error_message. Поля, для которых нет значения, приходят как null; обработчик не должен считать их обязательными для каждого типа события.

Как создать вебхук

На странице вебхуков откройте вкладку «Общие», затем:

  1. Нажмите «Добавить».
  2. В форме создания задайте понятное название webhook, например «Новые сообщения в CRM».
  3. Укажите полный URL обработчика.
  4. Галочками отметьте один или несколько типов событий. Сначала показывается понятное описание события, под ним — технический псевдоним для payload.
  5. Нажмите «Создать».
Как создать вебхук. Отмеченные элементы: 5. типов событий; 6. «Создать»
3 / 3
5. типов событий · 6. «Создать»

Новый webhook сразу получает статус «Активен».

Секрет и подпись

В блоке «Секрет вебхуков» хранится единый секрет подписи для всех вебхуков приложения, включая вызовы инструментов. Его можно показать или скрыть и скопировать в защищённое хранилище серверной части приложения. Ссылка «Подробнее» открывает окно с заголовками и формулой HMAC.

Если секрет потерян или скомпрометирован, нажмите «Заменить секрет». В окне подтверждения можно отменить действие или подтвердить замену. Старый секрет сразу перестанет работать для всех вебхуков и инструментов приложения, поэтому без паузы обновите его на бэкенде приложения.

Секрет и подпись. Отмеченные элементы: 1. блоке «Секрет вебхуков»; 2. показать или скрыть; 3. скопировать; 4. «Подробнее»
1 / 4
1. блоке «Секрет вебхуков» · 2. показать или скрыть · 3. скопировать · 4. «Подробнее»

Каждый запрос содержит заголовки:

  • Content-Type: application/json;
  • X-App-Id — Client ID приложения;
  • X-Webhook-Timestamp — время текущей попытки доставки; при повторе оно меняется;
  • X-Webhook-Event-Id — уникальный ID события, совпадающий с event_id в теле;
  • X-Webhook-Signature — hex-строка HMAC-SHA256.

Подпись рассчитывается по точному значению X-Webhook-Timestamp:

import { createHmac, timingSafeEqual } from "node:crypto";

const deliveryTimestamp = request.headers["x-webhook-timestamp"] ?? "";
const expectedHex = createHmac("sha256", webhookSecret)
  .update(deliveryTimestamp, "utf8")
  .digest("hex");
const expected = Buffer.from(expectedHex, "hex");
const received = Buffer.from(request.headers["x-webhook-signature"] ?? "", "hex");
const signatureIsValid =
  received.length === expected.length && timingSafeEqual(received, expected);

Перед обработкой проверьте свежесть X-Webhook-Timestamp, подпись, соответствие X-Webhook-Event-Id полю event_id, X-App-Id, разрешённый project_id и тип события. Не записывайте секрет в логи и не передавайте его в чат. Повторный event_id не обрабатывайте заново.

Формат запроса

Боевой запрос имеет общую форму:

{
  "event_id": "019c5a23-8b7c-7f10-a4dd-c4f4b650032a",
  "event_type": "message_new",
  "timestamp": "2026-07-10T12:00:00.000Z",
  "project_id": "project-id",
  "channel_id": "channel-id",
  "channel_type": "telegram",
  "lead_id": "lead-id",
  "dialog_id": "dialog-id",
  "platform_user_id": "platform-user-id",
  "data": {
    "content": "Текст сообщения"
  }
}

data зависит от event_type. Не привязывайте обработчик к наличию полей, которые не относятся к выбранному событию. Используйте event_id как ключ идемпотентности: сохраните уже принятые ID и не выполняйте одно событие повторно.

Тест и повторные попытки

У каждого webhook в списке есть действие «Тест». Оно открывает окно проверки с методом, URL, заголовками и JSON-телом будущего запроса. До нажатия «Запустить» запрос не отправляется. После запуска в том же окне отображаются результат, HTTP-статус, время, тело ответа и причина ошибки, если она возникла.

Тест выполняет один запрос с собственным event_id, event_type: "test", project_id: "test", пустыми идентификаторами и сообщением в data. Заголовки и подпись формируются по тем же правилам, что и для боевого события. Обработчик должен принимать этот служебный тип отдельно от списка боевых событий.

Тест считается успешным только при HTTP-ответе 200-299 и показывает код и время ответа. Это один немедленный запрос без повторов; он может ждать ответ до 120 секунд.

Боевое событие ставится в очередь с окном повторов в один день. При сетевой ошибке, тайм-ауте, HTTP 408, 425, 429 или ответе 5xx выполняется до 12 попыток: сразу, затем примерно через 1, 5, 15 и 30 минут, 1, 2, 4, 8, 12, 18 и 24 часа. Другой ответ 4xx, в том числе 401 или 403, завершает операцию без автоматических повторов: сначала нужно разобраться с причиной отказа. После исправления доступа запрос с ответом 401 или 403 можно повторить вручную. Во всех автоматических попытках сохраняются те же event_id, timestamp в теле и само тело запроса. Для каждой попытки создаются новые X-Webhook-Timestamp и подпись, поэтому повтор нужно распознавать по event_id и отвечать 2xx, если событие уже надёжно принято.

Управление и замена секрета

В списке webhook показываются заданное название, URL, выбранные события, состояние, время последнего вызова и последний HTTP-статус. Через переключатель состояния webhook можно отключить и снова включить; отключённый webhook не получает новые боевые события.

Откройте страницу webhook, чтобы изменить название, URL или отмеченный галочками набор публичных событий, затем нажмите «Сохранить». Секрет не относится к отдельному URL: он доступен в общем блоке на странице вебхуков приложения.

Управление и замена секрета. Отмеченные элементы: 1. страницу webhook; 2. URL; 3. набор публичных событий; 4. «Сохранить»
1. страницу webhook · 2. URL · 3. набор публичных событий · 4. «Сохранить»

Кнопка удаления webhook открывает подтверждение. После окончательного удаления отправка на этот URL прекращается; единый секрет приложения не меняется.

Вебхуки инструментов

Вкладка «Для инструментов» показывает HTTP-инструменты приложения и созданные для них webhook. URL, параметры и режим выполнения задаются в конструкторе инструмента; со страницы webhook можно перейти к редактированию инструмента.

Вкладка «Для шагов» показывает внутренние webhook шагов автоматизаций. Адрес и контракт задаются в конструкторе шага; со страницы webhook можно открыть историю его вызовов или вернуться к редактированию шага.

Вызовы HTTP-инструментов подписываются единым секретом вебхуков приложения из блока выше. Отдельного секрета у каждого инструмента нет.

Вебхуки инструментов. 1. редактированию инструмента
1. редактированию инструмента
  • в мгновенном режиме агент ждёт один ответ, а автоматических повторов нет;
  • в режиме ожидания результата запрос ставится в очередь, и агент продолжает шаг после успешной доставки;
  • в фоновом режиме запрос тоже ставится в очередь, но ответ не продолжает текущий шаг агента.

Тело вызова содержит event_type: "tool_call", идентификаторы события, приложения, установки, проекта, агента и диалога, необязательный lead_id, системное имя и tool_instance_id, а также arguments, обычную configuration и закрытые private_data экземпляра. Заголовки и подпись строятся по правилам раздела «Секрет и подпись» с единым секретом приложения. Обработчик должен проверять подпись и входные данные, не записывать приватные данные в открытые логи и возвращать результат, понятный агенту. Режимы, тайм-ауты, окна повторов и точный пример тела описаны в разделе «Инструменты агента».

История и восстановление доставки

На странице webhook область «Запросы» показывает операции доставки в очереди, на повторе, завершенные успешно или завершенные с ошибкой. Для внутренних webhook также отображается понятный тип: вызов инструмента агентом или запуск шага автоматизации. Выберите операцию в списке, чтобы открыть её подробности.

История и восстановление доставки. Отмеченные элементы: 1. «Запросы»; 2. операцию
1. «Запросы» · 2. операцию

В фильтрах истории введите полный task_id или event_id в поле идентификатора и нажмите кнопку поиска. Поиск по ID точный; содержимое JSON не сканируется. Дополнительно можно выбрать статус доставки, состояние проблемы, тип события и период от последних 24 часов до всего срока хранения. Сбросьте фильтры, чтобы вернуться к исходной выборке, или обновите список, чтобы получить текущие результаты. Список догружается кнопкой «Показать еще».

Что требует внимания разработчика

В списке приложений у каждого приложения отдельно показываются два числа: сколько текущих инцидентов нужно проверить разработчику и сколько доставок не завершилось успешно за выбранный период. Это разные показатели. Историческая ошибка остаётся в статистике, даже если её уже рассмотрели, а счётчик «Проверить разработчику» содержит только нерешённые текущие инциденты. В API этот текущий счётчик возвращается как developer_attention вместе с соответствующим app_id.

Категория отвечает на вопрос, кто должен разбираться с причиной:

КатегорияЧто произошлоКто действует
Ошибка приложения (application)Обработчик приложения вернул ошибку или сообщил о сбое своей логикиРазработчик приложения проверяет обработчик
Настройка проекта (configuration)В установленном приложении конкретного проекта нет нужного аккаунта, доступа или другой настройкиВладелец или участник проекта исправляет настройку; это не ошибка кода приложения
Ошибка платформы (platform)Запрос не дошёл до приложения из-за сбоя доставки внутри SenlerПричину устраняет Senler; разработчику приложения ничего перенастраивать не нужно
Причина не определена (unknown)Ответа недостаточно для надёжной классификацииРазработчик проверяет запрос и ответ вручную

application и unknown получают developer_action_required: true и входят в счётчик проверки разработчиком. configuration и platform получают developer_action_required: false и в этот счётчик не входят.

Как определяется категория

Senler классифицирует не по одному HTTP-коду и не по тексту ошибки. Используется следующий порядок:

  1. Если доставка завершилась до вызова внешнего обработчика, инцидент относится к platform.
  2. Если обработчик вернул корректный объект incident, Senler использует объявленную приложением категорию application или configuration.
  3. Если объекта incident нет, ответ 5xx считается application.
  4. Остальные необъяснённые ошибки считаются unknown.

Поле code не выбирает категорию автоматически. Это стабильный технический идентификатор причины внутри уже определённой категории. Поле retryable отдельно указывает, имеет ли смысл повторять запрос без исправления его данных. Для ответа без объекта incident значение выводится из результата доставки — сетевой ошибки, тайм-аута или HTTP-статуса. В истории запросов API возвращает итоговый incident с полями code, category, developer_action_required и retryable. У ручного повтора есть дополнительное исключение для ответов внешнего обработчика 401 и 403 — оно описано ниже.

Как приложению вернуть точную причину

Чтобы Senler не пытался угадать причину по HTTP-ответу, обработчик приложения может вернуть безопасный JSON с объектом incident. Например, инструмент оплаты не нашёл выбранный пользователем аккаунт в настройках проекта:

{
  "message": "Configured Prodamus account is unavailable",
  "incident": {
    "code": "prodamus_account_unavailable",
    "category": "configuration",
    "retryable": false
  }
}

Контракт объекта:

  • code — необязательный постоянный машинный код причины: от 1 до 128 символов, начинается с латинской буквы или цифры и далее содержит только строчные латинские буквы, цифры, ., _ или -; отсутствующий или некорректный код сохраняется как null, но не отменяет категорию;
  • category — только application или configuration; категории platform и unknown назначает сам Senler;
  • retryabletrue, только если повтор того же payload без изменения данных действительно безопасен и может помочь;
  • developer_action_required приложение не передаёт — Senler вычисляет его по категории.

category и логическое значение retryable обязательны. Если одно из них отсутствует или недопустимо, Senler игнорирует объявленный объект и применяет обычную классификацию по источнику сбоя и HTTP-статусу.

Текст message нужен человеку, а incident.code — агенту, логам и автоматической диагностике. Не помещайте в них секреты, токены, персональные данные или внутренний stack trace. Если проблема вызвана выбранным аккаунтом или настройкой конкретной установки, используйте configuration; если сломана логика обработчика самого приложения — application.

Повтор и закрытие инцидента

В выбранной операции проверьте детали запроса: исходный JSON, HTTP-ответ или сетевую ошибку. Ниже находится список попыток с временем и результатом каждой доставки. Сначала устраните причину ошибки, затем выберите нужное действие:

  • «Отправить повторно» ещё раз отправляет сохранённый запрос на URL webhook. Ответ остаётся в журнале и сам по себе не продолжает работу ожидающего агента.
  • «Отправить повторно и передать ответ агенту» доступно для инструмента в режиме ожидания результата, если операция связана с агентом и её можно повторить. После успешного ответа результат передаётся ожидающему агенту для продолжения работы.
  • Если повтор не нужен, отметьте проблему решённой. Она исчезнет из числа нерешённых, но запрос не будет отправлен заново, а история сохранится.

На мобильном скриншоте метка 3 выделяет всю карточку запроса. Отдельные попытки перечислены внизу карточки.

История и восстановление доставки. Отмеченные элементы: 3. детали запроса; 4. «Отправить повторно»; 5. «Отправить повторно и передать ответ агенту»; 6. отметьте проблему решённой
3. детали запроса · 4. «Отправить повторно» · 5. «Отправить повторно и передать ответ агенту» · 6. отметьте проблему решённой

Повтор доступен только для ещё не решённой операции со статусом повтора или ошибки. Обычно требуется incident.retryable: true. Отдельное исключение — внешний обработчик ответил HTTP 401 или 403: после исправления авторизации или прав доступа запрос можно отправить вручную даже при retryable: false. Надпись «Автоматический повтор недоступен» не запрещает этот ручной повтор. Для ошибки внутри платформы исключение не действует.

Если кнопки повтора нет, исправьте данные или конфигурацию и заново выполните исходное действие. Это создаст запрос с актуальными данными. Ручная отправка из журнала не собирает параметры заново: она использует сохранённое тело запроса.

Перед подтверждением учтите защиту от дублей. Ручной повтор создаёт новую операцию доставки, но сохраняет исходный event_id; timestamp в теле обновляется, остальные данные остаются прежними. Автоматические попытки сохраняют и исходный event_id, и timestamp тела. Поэтому на стороне обработчика определяйте дубли по event_id, а не по времени или полному совпадению JSON. Если действие уже выполнено, не создавайте заказ или сообщение повторно; для инструмента верните ранее полученный результат в ожидаемом формате.

Для нескольких нерешенных операций отметьте все загруженные или выберите строки вручную. Повторно отправить можно до 25 выбранных запросов за одно действие; каждый должен допускать ручной повтор. Для каждого создаётся отдельная операция с его исходным event_id. Отметить выбранные решенными можно для выборки до 100 запросов. Действие «Отметить решенными все по фильтру» обрабатывает до 1000 совпадений; если их больше, сузьте фильтр.

При закрытии выберите причину и оставьте комментарий от 10 до 1000 символов. Для обычной проверки используйте reviewed, если исправление не требовалось, или fixed, если причина устранена. Для специальных случаев доступны obsolete, superseded, invalid_payload, accepted_loss, task_completed, diagnostic_completed, replay_cancelled и webhook_deleted. Код причины сохраняется как resolution_code, комментарий — как resolution_comment; агент может использовать оба поля, чтобы отличить рассмотренный инцидент от действительно исправленного.

Операции хранятся 90 дней, нерешенные ошибки — до решения, а отдельные попытки доставки — 365 дней. Отметка «Решено» не удаляет историю и не отправляет запрос повторно; она только исключает проблему из числа нерешенных.

Если события не приходят

  1. Убедитесь, что webhook имеет статус «Активен» и нужный тип события отмечен.
  2. Запустите тест и проверьте последний HTTP-код и время вызова.
  3. Проверьте, что внешний URL доступен по HTTPS и отвечает не позднее 120 секунд.
  4. Убедитесь, что обработчик принимает Content-Type: application/json и служебный event_type: "test".
  5. Сверьте X-App-Id с Client ID приложения и X-Webhook-Event-Id с event_id в теле.
  6. Проверьте свежесть X-Webhook-Timestamp и HMAC-SHA256 этого точного значения с единым секретом приложения.
  7. Откройте историю запросов и сопоставьте сохранённый payload, ответ или ошибку с журналом внешнего сервера по event_id.
  8. Возвращайте 2xx только после того, как событие принято или надёжно поставлено в вашу очередь; повторный event_id не обрабатывайте заново.
  9. Если секрет потерян, замените единый секрет и сразу обновите его во всех обработчиках приложения.