Вебхуки приложения
Вебхуки приложения
На странице «Вебхуки» developer-приложение настраивает отправку выбранных событий на внешний URL. Один webhook может слушать несколько типов событий, а у одного приложения может быть несколько webhooks с разными адресами и наборами событий.
Для рабочего endpoint используйте HTTPS и отвечайте быстро: Senler ждёт ответ не более 10 секунд.
Какие события доступны
command_start— пользователь отправил/start; вdataприходятcommandиargs;message_allow— пользователь разрешил отправку сообщений;message_new— новое сообщение пользователя; текст находится вdata.content;lead_created— создан лид;lead_unsubscribed— пользователь отписался от сообщений;lead_blocked— пользователь заблокировал бота;message_undelivered— сообщение не доставлено;error— при обработке события произошла ошибка.
Для событий, связанных с подпиской или ошибкой, объект data может содержать key и error_message. Поля, для которых нет значения, приходят как null; обработчик не должен считать их обязательными для каждого типа события.
Как создать вебхук
- Нажмите «Создать вебхук».
- В форме создания укажите полный URL обработчика.
- Отметьте один или несколько типов событий.
- Нажмите «Создать».
Новый webhook сразу получает статус «Активен».
Секрет и подпись
После создания открывается окно с секретом. Скопируйте его через кнопку копирования, сохраните в защищённом хранилище и только затем нажмите «Готово». Полный секрет показывается один раз; в списке остаётся только его короткий префикс.
Каждый запрос содержит заголовки:
Content-Type: application/json;X-App-Id— Client ID приложения;X-Webhook-Timestamp— время события, совпадающее сtimestampв теле;X-Webhook-Event-Id— уникальный ID события, совпадающий сevent_idв теле;X-Webhook-Signature— hex-строка HMAC-SHA256.
Подпись защищает всё тело запроса. Сначала JSON канонизируется: ключи каждого объекта сортируются, порядок элементов массивов сохраняется, свойства со значением undefined пропускаются, а undefined внутри массива становится null. Затем соединяются версия подписи, время, ID события и канонический JSON:
import { createHmac, timingSafeEqual } from "node:crypto";
function canonicalize(value) {
if (value === null || ["boolean", "string"].includes(typeof value)) {
return value;
}
if (typeof value === "number" && Number.isFinite(value)) {
return value;
}
if (Array.isArray(value)) {
return value.map((item) =>
item === undefined ? null : canonicalize(item),
);
}
if (typeof value === "object") {
return Object.fromEntries(
Object.keys(value)
.sort()
.filter((key) => value[key] !== undefined)
.map((key) => [key, canonicalize(value[key])]),
);
}
throw new TypeError("Unsupported webhook payload value");
}
const canonicalPayload = JSON.stringify(canonicalize(payload));
const signedContent = [
"v1",
payload.timestamp,
payload.event_id,
canonicalPayload,
].join(".");
const expectedHex = createHmac("sha256", webhookSecret)
.update(signedContent, "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 точно совпадают с полями тела, проверьте подпись, X-App-Id, разрешённый project_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 в списке есть действие «Тест». Оно выполняет один запрос с собственным event_id, event_type: "test", project_id: "test", пустыми идентификаторами и сообщением в data. Заголовки и подпись формируются по тем же правилам, что и для боевого события. Обработчик должен принимать этот служебный тип отдельно от списка боевых событий.
Тест считается успешным только при HTTP-ответе 200-299 и показывает код и время ответа. Он не запускает серию повторов.
Для боевого события Senler делает до трёх попыток. На каждую отводится 10 секунд; после первой неудачи ожидание составляет около 1 секунды, после второй — около 5 секунд. Ответ 2xx завершает доставку, а сетевые ошибки и другие HTTP-статусы приводят к следующей попытке. Во всех попытках сохраняются те же event_id, timestamp, тело и подпись, поэтому повтор нужно распознавать по event_id и отвечать 2xx, если событие уже надёжно принято.
Управление и замена секрета
Карточка показывает выбранные события, состояние, время последнего вызова и последний HTTP-статус. Через переключатель состояния webhook можно отключить и снова включить; отключённый webhook не получает боевые события.
Изменить URL, набор событий или снова показать полный секрет существующего webhook на этой странице нельзя. Для замены создайте новый webhook, сохраните его секрет, переключите внешний обработчик, выполните тест и только затем удалите старый.
Кнопка удаления webhook открывает подтверждение. После окончательного удаления отправка на этот URL прекращается, а секрет восстановить нельзя.
Если события не приходят
- Убедитесь, что webhook имеет статус «Активен» и нужный тип события отмечен.
- Запустите тест и проверьте последний HTTP-код и время вызова.
- Проверьте, что внешний URL доступен по HTTPS и отвечает быстрее 10 секунд.
- Убедитесь, что обработчик принимает
Content-Type: application/jsonи служебныйevent_type: "test". - Сверьте
X-App-Id,X-Webhook-TimestampиX-Webhook-Event-Idс телом запроса. - Канонизируйте всё тело, соберите строку
v1.timestamp.event_id.canonicalPayloadи проверьте HMAC-SHA256. - Проверьте журналы внешнего сервера по
event_idи времени последнего вызова. - Возвращайте
2xxтолько после того, как событие принято или надёжно поставлено в вашу очередь; повторныйevent_idне обрабатывайте заново. - Если секрет потерян, создайте новый webhook и замените старый по безопасной последовательности выше.