События

Вебхуки

Как APIStend доставляет события и чем профили Bitrix24, Ozon, Wildberries и Apify отличаются друг от друга.

На этой странице · 8

Вебхук в APIStend — это подписка «событие сервиса → ваш обработчик». Стенд собирает тело события в родном для сервиса формате, отправляет его и записывает исход в журнал доставок.

Главное, что нужно понять до всего остального: политика доставки принадлежит сервису, а не транспорту. Боевой Bitrix24 не повторяет доставку вообще, Ozon повторяет по своей лестнице и ставит подписку на паузу, Wildberries повторяет по своей и в конце удаляет событие. Единая сетка повторов на всех трёх дала бы ложную картину ровно в том сценарии, ради которого стенд и нужен: «что будет, если мой обработчик упал».

Как проходит доставка

  1. 1

    Событие попадает в очередь

    Строка доставки пишется в базу в состоянии queued — до попытки отправки. Онлайн- и офлайн-путь совпадают: если получателя сейчас нет, строка просто останется в очереди.

  2. 2

    Стенд пытается отдать её немедленно

    Для локального получателя — кадром в WebSocket агента apistend listen, для публичного — обычным HTTP-запросом с сервера APIStend.

  3. 3

    Приходит ответ, стенд применяет правило успеха своего сервиса

    Успех — succeeded. Неуспех — либо queued с временем следующей попытки, либо терминальное состояние, если повторы у сервиса кончились или их нет.

  4. 4

    Планировщик подбирает просроченные повторы

    Он же переводит зависшие доставки в no_response. Проход — раз в две секунды, до 50 доставок за проход.

Чем отличаются три профиля

Это главная таблица раздела. Значения — из packages/shared/src/services.ts, профиль сервиса единственное место, где живут эти отличия.

Bitrix24Ozon SellerWildberriesApify
Content-Typeapplication/x-www-form-urlencodedapplication/jsonapplication/jsonapplication/json
Таймаут ответа30 с5 с10 с2 мин
Повторовнет10511
Лестница повторов—5 с, 15 с, 60 с, 5 мин, затем 6 раз по 10 мин10 с, 30 с, 2 мин, 7,5 мин, 15 минудвоение: 1 мин, 2, 4, … до ~32 ч
Всего попыток111612
Успехлюбой 2xx200 и тело {"result": true}строго 200любой 2xx
Подписьauth[application_token] в теленетHMAC-SHA256 в X-Hub-Signatureнет — секрет в адресе
Пауза после серии неудачнетдаданет

Про таймауты и повторы стоит помнить, откуда они взялись: у Ozon превышение пяти секунд само по себе одно из условий автоматической приостановки уведомлений, у Wildberries обработчик обязан ответить 200 в течение десяти секунд, а Bitrix24 про повторы говорит дословно: событие отправляется один раз, сбой фиксируется, повторной отправки нет.

Тело события

Тело собирается в родном формате сервиса. Если оно отличается от боевого, разработчик напишет разбор, который в проде не заработает, — поэтому это самое чувствительное место всей функции.

Форма, а не JSON, вложенность — PHP-скобками. В событии CRM лежит только идентификатор объекта: значения полей не передаются, обработчик обязан сходить за ними в crm.deal.get.

event=ONCRMDEALUPDATE
event_handler_id=975
data[FIELDS][ID]=7445
ts=1788948929
auth[domain]=demo.bitrix24.ru
auth[client_endpoint]=https://demo.bitrix24.ru/rest/
auth[server_endpoint]=https://oauth.bitrix24.tech/rest/
auth[member_id]=d897063e1ce7c5eb9f04b9751eef5915
auth[application_token]=stend_whsec_…

(в запросе это одна строка application/x-www-form-urlencoded; здесь она разбита по параметрам для чтения)

Конверт Wildberries всегда с одним событием

В профиле сервиса записано, что боевой WB кладёт в один запрос до 100 событий. Стенд эту пачку пока не собирает: каждая доставка — свой запрос с массивом events из одного элемента. Разбор писать всё равно нужно по массиву, иначе на бою он сломается на первой же пачке.

События, отправленные кнопкой «Тест» и командой apistend trigger без серии, помечаются: у Wildberries в объект события добавляется "test": true.

Заголовки доставки

ЗаголовокЗначение
Content-Typeиз профиля сервиса
User-AgentAPIStend-Webhooks/1.0
X-APIStend-Delivery-Idидентификатор доставки, вида evt_162c81ddfd68
X-Hub-Signatureтолько Wildberries: HMAC-SHA256

Подпись Wildberries считается от сырого тела запроса ключом самого вебхука (secret подписки, значение вида stend_whsec_…), результат — hex в нижнем регистре. Проверять её нужно до разбора JSON: пересобранное тело даст другую подпись.

import { createHmac, timingSafeEqual } from 'node:crypto'

const expected = createHmac('sha256', secret).update(rawBody).digest('hex')
const got = req.headers['x-hub-signature']
const ok = expected.length === got.length &&
  timingSafeEqual(Buffer.from(expected), Buffer.from(got))

У Bitrix24 подписи в заголовке нет — вместо неё в теле приходит auth[application_token], его и сравнивают. У Ozon подписи нет вовсе, это свойство боевого сервиса, а не упрощение стенда.

Что считается успехом

Проверку делает сервер: только у него есть тело ответа.

  • Bitrix24 — любой код 2xx.
  • Wildberries — ровно 200. 201 или 204 не подойдут.
  • Ozon — 200 и тело {"result": true}. Пустое тело, невалидный JSON или {"result": false} считаются неуспехом; в журнале это видно как EMPTY_BODY, INVALID_JSON или WRONG_RESULT_FIELD.
  • Ozon, событие TYPE_PING — ответ должен содержать version, name и time.

Повторы, приостановка и отключение

Повтор планируется по лестнице сервиса: к времени ответа прибавляется задержка для текущего номера попытки, доставка возвращается в queued, планировщик поднимает её, когда срок наступит, и увеличивает счётчик попыток.

Когда повторы кончились, а сервис приостанавливает подписки (Ozon и Wildberries), вебхук переходит в состояние failing. Успешная доставка его оттуда не выводит — снятие приостановки только ручное, переключателем в строке вебхука. Так же ведёт себя боевой Ozon: приостановленная подписка возобновляется только вручную.

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

Кнопка «Повторить ошибочные» ставит в очередь до 50 доставок в состояниях failed и no_response — кроме доставок Bitrix24: у боевого сервиса повторов нет, и мок не должен быть добрее.

Публичный получатель

Кроме доставки на localhost вебхук можно направить на обычный публичный адрес — тогда запрос делает сам сервер APIStend. Из-за этого адрес проверяется, причём дважды: при создании подписки и ещё раз перед каждой отправкой, включая повторы и события серии.

Отклоняются приватные и служебные диапазоны: 10/8, 127/8, 172.16/12, 192.168/16, 169.254/16 (метаданные облаков), 100.64/10, IPv6 ::1, fc00::/7, fe80::/10 и IPv4, завёрнутый в IPv6. Имя резолвится: домен, указывающий на приватный адрес, тоже не пройдёт. Такая доставка сразу получает терминальное состояние failed с причиной blocked — ждать нечего, адрес не станет публичным.

В разработке приватные адреса разрешены

Переключатель WEBHOOK_ALLOW_PRIVATE_TARGETS снимает проверку целиком: на локальной машине API и приложение живут рядом. Проверка от перепривязки DNS между проверкой и запросом не спасает — это известное ограничение, а не недосмотр.

Подводные камни

  • Тело Bitrix24 — не JSON. JSON.parse на нём упадёт. Разбирайте application/x-www-form-urlencoded и помните про PHP-скобки в именах параметров.
  • Ozon смотрит в тело. Обработчик, который отвечает 200 OK пустым телом, на Ozon считается упавшим и через одиннадцать попыток отправит подписку на паузу.
  • У Bitrix24 попытка одна. Ошибка в обработчике — событие потеряно навсегда, повторить его может только человек, и то не через «Повторить ошибочные».
  • Пауза не снимается сама. После серии неудач вебхук останется в failing, пока его не включат руками.