Обзор
Вебхук — это push поверх API v1: как только в организации что-то произошло, Smengo сам отправляет POST на ваш адрес. Опрашивать /api/v1/* по расписанию не нужно.
Вебхуки против опроса API
| Опрос API | Вебхуки | |
|---|---|---|
| Кто инициирует | вы, по расписанию | Smengo, по факту события |
| Задержка | равна периоду опроса | одиночное изменение — в пределах минуты |
| Стоимость простоя | запросы уходят даже когда ничего не менялось | запросов нет, пока нет событий |
| Что приходит | текущее состояние ресурса | факт изменения: тип, идентификаторы, дата |
Тело события тонкое: идентификаторы и даты, без деталей. Детали клиент дочитывает своим API-ключом — у большинства событий для этого есть готовая ссылка related.url. Так вебхук не отдаёт больше, чем отдал бы API тому же ключу, а данные в теле не устаревают при ретрае.
Обычная схема интеграции: вебхук как триггер, API как источник правды.
Как подключить
- Откройте «Настройки» → «Интеграции» → блок «Вебхуки». Раздел доступен участнику с правом «Настройки организации» (
manage_org). - Нажмите «Добавить вебхук», укажите адрес приёмника, отметьте события и (по желанию) описание.
- Скопируйте секрет
whsec_…— он показывается ровно один раз. Им подписан каждый запрос, см. Проверку подписи. - Нажмите «Отправить тестовое» — придёт служебное событие
ping, код ответа вашего сервера покажется сразу в карточке.
Подписаться можно только на те события, данные которых вы сами видите в Smengo, — таблица «событие → право» в Событиях. Недоступные чекбоксы задизейблены с пояснением.
Пошаговая инструкция для нетехнического пользователя — в справке.
Формат доставки
- Метод —
POST, тело — компактный JSON (без отступов и переносов). Content-Type: application/json; charset=utf-8,User-Agent: Smengo-Webhooks/1.0.- Только
https://и порт 443. Схема и порт проверяются при сохранении адреса и заново перед каждой доставкой. - Редиректы не переходим. Ответ
3xx— это сбой доставки, а не переход поLocation. - Таймаут — 15 секунд на всю доставку (соединение, TLS, ответ).
- Успех — строго
2xx. Тело ответа на решение о доставке не влияет: из него мы читаем не больше 512 байт — они сохраняются в журнал для отладки, а остальное не вычитывается вовсе (соединение обрывается на этом месте). - Одно событие — один запрос. Батчинга нет.
Заголовки запроса
| Заголовок | Значение |
|---|---|
Smengo-Event-Id |
UUID события. Один на событие: если на одно событие подписаны два эндпоинта, обоим уйдёт один и тот же Smengo-Event-Id. При ретрае не меняется — это ключ дедупликации. |
Smengo-Delivery-Id |
<uuid доставки>:<номер попытки>, например …:3. Строка доставки одна на эндпоинт, ретрай переиспользует её и меняет только номер. |
Smengo-Event-Sequence |
Целое число, монотонно растёт. Сортируйте события по нему, а не по времени получения. |
Smengo-Event-Type |
schedule.published, schedule_entry.changed, checkin.recorded, swap.decided или служебный ping — маршрутизация без парсинга тела. |
Smengo-Timestamp |
Unix-секунды момента подписи. То же значение, что в t= подписи. |
Smengo-Signature |
t=<unix>,v1=<hex>. В окне ротации секрета — две подписи через пробел: t=…,v1=<hex> v1=<hex>. См. Проверку подписи. |
Дополнительно проставляются Content-Length и Accept-Encoding: identity (сжатый ответ нам не нужен — мы читаем из него только первые 512 байт в журнал).
Пример запроса
POST /hooks/smengo HTTP/1.1
Host: example.com
Content-Type: application/json; charset=utf-8
User-Agent: Smengo-Webhooks/1.0
Smengo-Event-Id: 3f1c8b52-9a4d-4f7e-b2c1-8e5d0a6b4c39
Smengo-Delivery-Id: 7b2e4d16-0c58-4a93-9f41-2d6b8c3e5a70:1
Smengo-Event-Sequence: 10427
Smengo-Event-Type: schedule_entry.changed
Smengo-Timestamp: 1785921323
Smengo-Signature: t=1785921323,v1=5a0f…c81b
{"id":"3f1c8b52-9a4d-4f7e-b2c1-8e5d0a6b4c39","type":"schedule_entry.changed","created":"2026-08-01T09:15:23.481Z","sequence":10427,"org_id":"9c1a2e34-5678-4abc-9def-0123456789ab","api_version":"v1","data":{"action":"updated","employee_id":"5e6d9f2a-3b7c-4e2f-9a1d-7c8e2f6b4a10","entry_date":"2026-08-14","entry_id":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"},"related":{"url":"https://smengo.com/api/v1/schedule-entries?from=2026-08-14&to=2026-08-14"}}
Конверт события
{
"id": "3f1c8b52-9a4d-4f7e-b2c1-8e5d0a6b4c39",
"type": "schedule_entry.changed",
"created": "2026-08-01T09:15:23.481Z",
"sequence": 10427,
"org_id": "9c1a2e34-5678-4abc-9def-0123456789ab",
"api_version": "v1",
"data": { "action": "updated", "employee_id": "5e6d9f2a-3b7c-4e2f-9a1d-7c8e2f6b4a10", "entry_date": "2026-08-14", "entry_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" },
"related": { "url": "https://smengo.com/api/v1/schedule-entries?from=2026-08-14&to=2026-08-14" }
}
| Поле | Тип | Описание |
|---|---|---|
id |
uuid | То же, что Smengo-Event-Id. |
type |
string | Тип события. |
created |
string | Момент рождения события, ISO 8601 UTC с суффиксом Z и миллисекундами. |
sequence |
number | То же, что Smengo-Event-Sequence. |
org_id |
uuid | Организация-источник. Полезно, если один приёмник обслуживает несколько организаций. |
api_version |
string | Версия API, к которой относятся идентификаторы и ссылка — сейчас всегда v1. |
data |
object | Состав зависит от типа события — см. События. Ключи внутри data отсортированы по алфавиту. |
related.url |
string | Готовый GET-запрос к API v1 за деталями. Поле опционально: у swap.decided, schedule.published и у удаления смены его нет — см. События. |
Порядок ключей конверта фиксирован (id, type, created, sequence, org_id, api_version, data, related) и одинаков во всех попытках доставки: подпись считается по тем же байтам, что уходят в тело.
Инстанты внутри data приходят так, как их отдаёт база, — ISO 8601 со смещением (2026-08-01T09:15:23.481+00:00), тогда как created в конверте всегда нормализован до Z. Разбирайте оба любым ISO-8601-парсером, а не сравнением строк.
At-least-once и дедупликация
Доставка гарантирована не менее одного раза: событие может прийти повторно после таймаута, обрыва соединения или восстановления зависшей доставки. Это нормальный режим работы, а не сбой.
Дедуплицируйте по Smengo-Event-Id. Практическая схема: уникальный индекс по этому идентификатору у себя; повторная вставка — не ошибка, а сигнал «уже обработано, отвечаем 200».
Порядок не гарантируется
События могут прийти в любом порядке: одна транзакция порождает пачку событий, они уходят параллельно, а ретрай отодвигает конкретное событие на часы.
Для восстановления порядка используйте Smengo-Event-Sequence — целое, монотонно растущее в пределах организации. Поле created для этого не годится: у всех событий одной транзакции оно совпадает с точностью до микросекунд.
Требования к приёмнику
- Публичный
https-адрес на порту 443. Адреса в приватных диапазонах (10/8,127/8,169.254/16,192.168/16, ULA IPv6 и т. д.) отклоняются — доставка на них не уйдёт даже при валидном DNS-имени. - Не фильтруйте по IP. Фиксированного списка адресов, с которых уходят доставки, мы не публикуем и не обещаем: подлинность запроса подтверждает подпись, а не адрес источника.
- Без
user:pass@в URL и без фрагмента#…. Длина адреса — до 2000 символов. - Отвечайте
2xxбыстро, обрабатывайте асинхронно. Проверьте подпись, положите событие в очередь и сразу ответьте200. Если бизнес-логика упирается в 15-секундный таймаут, доставка считается провалившейся и придёт снова. - Будьте идемпотентны — см. дедупликацию выше.
- Не полагайтесь на редиректы. Адрес должен обслуживаться конечным сервером,
301/302на новый путь мы не пройдём. - Отвечайте
410 Gone, только если адрес мёртв окончательно: это единственный код, который сразу отключает вебхук.
Тестовое событие ping
Кнопка «Отправить тестовое» шлёт служебное событие:
{
"id": "…",
"type": "ping",
"created": "2026-08-01T09:15:23.481Z",
"sequence": 10428,
"org_id": "9c1a2e34-5678-4abc-9def-0123456789ab",
"api_version": "v1",
"data": { "test": true }
}
Оно подписано теми же секретами и несёт те же заголовки, что боевая доставка, — проверять его нужно тем же кодом. На ping нельзя подписаться: он шлётся адресно на конкретный эндпоинт, в том числе поставленный на паузу, и не ретраится (ровно одна попытка).
Ограничения v1
- Управление эндпоинтами — только через интерфейс: REST-эндпоинтов
/api/v1/webhook-*нет, API v1 остаётся read-only. - Подписка действует на всю организацию: фильтра по отделу нет.
- Ручного повтора доставки из интерфейса нет — пропущенное дочитывается через API.
Дальше
- События — каталог, состав
data, права. - Проверка подписи — готовый код на Node и Python.
- Ретраи и сбои — расписание попыток, авто-отключение, журнал.