Получать события Slen

Что несёт доставка вебхука, как проверить её подпись, когда её отправляют снова и о чём сообщает каждое семейство событий.

6 мин чтения

Вебхук отправляет события Slen на адрес вашего сервиса по мере того, как они происходят: взят холд, бюджет почти потрачен, вызов закончился, выполнение ждёт решения. Эта статья — о том, что приходит на этот адрес и как это читать. Вебхуки добавляют и меняют в консоли — статья Вебхуки показывает как.

Что несёт доставка

Доставка — это POST с телом JSON и заголовком Content-Type: application/json. Тело — это событие.

JSON
{
  "id": "74443412671954944",
  "type": "operation.uncertain",
  "created_at": "2026-10-02T09:13:04+00:00",
  "data": {
    "reservation_id": "74442097331195904",
    "project_id": "74438200973594624",
    "operation_code": "generate_email",
    "external_user_id": "user_8421",
    "model": "gpt-4o-mini",
    "reason": "operation_uncertain",
    "hold_expires_at": "2026-10-02T09:27:04+00:00"
  }
}
  • id называет событие. Каждый вебхук, который слышит событие, получает тот же id.

  • type — имя события, а data несёт то, о чём событие: id, суммы, статусы. Он никогда не несёт промпт или ответ модели.

  • created_at — момент, когда событие произошло, а не момент доставки.

Проверьте подпись

Каждая доставка подписана секретом вебхука, который показывают один раз при создании вебхука. Подпись идёт в заголовке доставки рядом с типом её тела:

HTTP
POST /webhooks/slen HTTP/1.1
Content-Type: application/json
Slen-Signature: t=1759396384,v1=5f0c8e2a…

t — время подписи в секундах с 1970 года, а v1 — подпись.

1. Соберите подписанный текст

Соедините значение t, точку и тело точно в том виде, в каком оно пришло, байт в байт. Не разбирайте тело и не записывайте его заново перед этим.

2. Вычислите подпись

Вычислите HMAC-SHA256 этого текста с секретом в роли ключа, записанный строчными шестнадцатеричными цифрами.

Terminal
printf '%s.%s' "$T" "$BODY" | openssl dgst -sha256 -hmac "$SECRET"

3. Сравните

Сравните результат со значением v1 за постоянное время. Отклоните доставку, подпись которой отличается, и доставку, чьё t далеко от ваших часов, — это старая доставка, которую кто-то отправил снова.

Когда доставку отправляют снова

  • Ваш сервис отвечает 2xx, чтобы принять доставку. Отвечайте сразу, а работу делайте потом: Slen ждёт ответа 10 секунд, если сервис не настроен иначе.

  • Ответ 400, 401, 403, 404, 410 или 422 завершает доставку: её не отправляют снова.

  • Любой другой ответ — перенаправление, 408, 429, 5xx — или отсутствие ответа вовремя считается сбоем, и доставку отправляют снова позже. Пауза начинается с 30 секунд и каждый раз удваивается, до часа. После 12 попыток от доставки отказываются. Это значения сервиса по умолчанию.

  • Доставки уходят раз в минуту.

Одно и то же событие может прийти больше одного раза, а два события могут прийти не в своём порядке. Храните id каждого обработанного события и пропускайте то, которое уже видели.

Какие вебхуки слышат событие

Вебхук слышит события, на которые он подписан.

  • Вебхук одного проекта слышит события этого проекта.

  • Вебхук всех проектов слышит события каждого проекта организации и события, которые ни к какому проекту не относятся: каждое connection.cooldown_started и события подключения, бюджета или оповещения всей организации.

  • Выключенный вебхук ничего не слышит. Доставка, которая уже его ждёт, всё равно отправляется.

События

Это события, которые отправляет Slen. Форма консоли предлагает часть из них; любое из них можно назвать через API.

ОбъектСобытия
reservation
  • reservation.created

    A hold was taken

  • reservation.captured

    A hold was captured

  • reservation.released

    A hold was released

  • reservation.expired

    A hold expired

  • reservation.overrun

    A capture came out above its hold

budget
  • budget.threshold_reached

    A budget crossed a threshold

  • budget.exceeded

    A budget reached its limit

operation
  • operation.authorized

    A gateway call was authorized

  • operation.denied

    A gateway call was refused

  • operation.started

    A gateway call went out

  • operation.completed

    A gateway call completed

  • operation.failed

    A gateway call failed

  • operation.uncertain

    A gateway call has no known outcome

flow
  • flow.published

    A flow version was published

  • flow.rolled_back

    A flow was rolled back

connection
  • connection.cooldown_started

    A provider connection cooled down

  • connection.health_changed

    A provider connection changed health

approval
  • approval.requested

    An execution waits for an approval

  • approval.decided

    An approval got its verdict

execution
  • execution.finished

    A flow execution reached its final status

alert
  • alert.opened

    An alert rule fired

  • alert.resolved

    An alert cleared

overrun
  • overrun.decided

    An overrun was decided

customer
  • customer.balance_low

    An end customer's credits ran low

ledger
  • ledger.degraded

    The ledger went degraded

  • ledger.restored

    The ledger is back

СемействоО чём сообщают его события
reservation.Холд взят, списан, освобождён или истёк; reservation.overrun — списание оказалось больше холда
operation.Вызов модели во флоу: холд, который он взял (operation.authorized), отказ бюджета, политики или блокировки клиента до вызова (operation.denied), начало вызова у провайдера и его конец — operation.completed, operation.failed или operation.uncertain
budget.Холд довёл использование бюджета до 50 % или 80 % его лимита (budget.threshold_reached) или до всего лимита (budget.exceeded)
flow.Опубликована версия флоу, или флоу откатили к более ранней версии
execution.Выполнение закончилось, с его status и error_code (execution.finished)
approval.Выполнение ждёт решения (approval.requested), и само решение: одобрено, отклонено или истекло (approval.decided)
overrun.Узел модели обнаружил, что вызов обойдётся дороже своего холда, и его политика перерасхода решила, что делать (overrun.decided)
connection.Подключение провайдера на время выведено из работы (connection.cooldown_started), или изменился его status (connection.health_changed)
alert.Оповещение открылось или разрешилось
customer.Доступные клиенту кредиты опустились ниже уровня, заданного для его счёта (customer.balance_low)
ledger.Slen начал пропускать вызовы без своего реестра (ledger.degraded), и это время кончилось (ledger.restored)

Бюджет сообщает о каждом уровне не больше раза за окно — статья Бюджеты говорит когда. Оповещение сообщает о своём открытии и разрешении, а не о каждом обновлении — Оповещения.

События вызова модели

События семейств reservation. и operation., а также overrun.decided, приходят от вызовов модели во флоу. Конец вызова модели — одно из трёх событий, и каждое говорит о деньгах своё.

  • operation.completed — провайдер ответил.

  • operation.failed — вызов не состоялся, и его холд вернули. Одно исключение: вызов, который провайдер выполнял в фоне и чей холд закрыло другое списание, пока он ждал, заканчивается с reason, равным background_hold_expired. Ничего не возвращается, и провайдер всё ещё может выставить счёт за вызов.

  • operation.uncertain — никто не знает, чем закончился вызов: потерян ответ узла модели, провайдер не ответил на начало фонового вызова, или о фоновом вызове больше нельзя было спросить. Холд остаётся до hold_expires_at, потому что провайдеру могли заплатить. Не повторяйте такой вызов, будто он не удался, — статья Когда исход неизвестен говорит, что делать.

Вызов, который провайдер выполняет в фоне, сообщает о своём начале, как любой вызов, а о своём конце — когда он наступает, одним из трёх событий.

Что читать дальше

  • Вебхуки — добавьте вебхук, выберите его события и сохраните его секрет.

  • Когда исход неизвестен — что делать с вызовом, который закончился со статусом uncertain.

  • Согласования — решите судьбу выполнения, о котором сообщило approval.requested.

  • Операции — запись вызова модели, о котором сообщают события семейства operation..

  • Вебхуки — проверьте подпись доставки и прочитайте её событие в SDK.

Собрано из openapi b070e1f, sdk contract 5cc7055, php ae4a53b, node 84bc62d