Вебхуки Exode доставляют подтверждённые события в ваши внешние сервисы почти в реальном времени и используют повторные попытки с экспоненциальной задержкой. Каждая следующая попытка происходит через увеличивающуюся задержку после предыдущей неудачной попытки:
11 → 22 → 44 → 88 → 176 минут (от момента предыдущей попытки, а не от старта). Поэтому приёмщик должен быть идемпотентным и быстро отвечать кодами 200-202 (timeout на response = 15 сек.).Как Exode доставляет вебхуки
Фиксация события
События
UserSignedUp, PaymentCompleted и другие генерируются ядром Exode и попадают в очередь Bull с таймстэмпом и параметрами инициатора.Поиск активных эндпоинтов
Затем ядро Exode ищет до пяти активных эндпоинтов в рамках школы/продавца, фильтруя их по выбранному событию.
Сбор полезной нагрузки
Для каждого события собираются связанные сущности (пользователь, платеж, доступ и т.д.), формируется
data, общий idempotencyKey и timestamp.Ограничения и требования
- Для одного продавца/школы можно создать не более пяти эндпоинтов, каждый со своим набором событий.
- Url должен поддерживать HTTPS, принимать
Content-Type: application/jsonи возвращать ответ не позднее 15 секунд. - Секретный ключ генерируется автоматически при создании и хранится в зашифрованном виде.
- Всегда проверяйте
idempotencyKey, чтобы отфильтровать дубликаты при повторной доставке. - Используйте разные эндпоинты для независимых систем, чтобы изолировать сбои и управлять разными наборами событий.
Формат webhook-сообщения
Имя события. Используйте значения из раздела «Поддерживаемые события».
Момент возникновения события в ISO 8601 UTC.
Общий ключ для всех попыток доставки конкретного события. Сохраняйте его, чтобы выполнять идемпотентную обработку.
Объект с доменными сущностями. Состав зависит от события и описан ниже.
Поддерживаемые события
UserSignedUp
UserSignedUp
Триггер: новый пользователь самостоятельно завершил регистрацию.
data.user содержит полный профиль, data.profile — карточку профиля, data.states.utmSignupParams — исходные UTM-метки.UserAcquainted
UserAcquainted
Триггер: пользователь прошёл онбординг в приложении. Полезная нагрузка идентична
UserSignedUp.UserTgConnected
UserTgConnected
Триггер: пользователь связал Telegram.
data.user и data.profile содержат текущие данные, data.prevTgId помогает отследить перепривязку.CourseProgressChanged
CourseProgressChanged
Триггер: изменился статус урока.
data.user дополнен профилем, data.course включает продукт и продавца. Событие содержит идентификаторы урока и статус (CourseProgressLessonStatus).CourseCompleted
CourseCompleted
Триггер: пользователь завершил курс. Набор данных тот же, что и для
CourseProgressChanged.PaymentCompleted
PaymentCompleted
Триггер: оплата успешно списана.
data.payment содержит платеж, инвойс, пользователя, продукты, скидки и способ оплаты.ProductEnrolledToFree
ProductEnrolledToFree
Триггер: пользователю выдан бесплатный доступ.
data.access описывает доступ, data.product и data.course — продукт и курс, data.user и data.profile — пользователя.Проверка подписи
Подпись передаётся в заголовкеsignature и формируется как HMAC-SHA256 от полного JSON тела вебхука и секретного ключа эндпоинта.
Диагностика и повторные попытки
Повторные попытки
Повторные попытки
Каждая попытка использует ту же нагрузку и
idempotencyKey. Задержки между попытками считаются от момента предыдущей неудачной попытки: вторая попытка через 11 минут после первой, третья через 22 минуты после второй, четвёртая через 44 минуты после третьей, пятая через 88 минут после четвёртой. Если все пять попыток завершаются ошибкой, задача помечается как failed и отображается в очереди Bull (раздел Monitoring). Отправьте успешный ответ, чтобы остановить дальнейшие попытки.Временное отключение
Временное отключение
Чтобы сохранить конфигурацию, установите
active = false. События будут игнорироваться до повторного включения, но секретный ключ и статистика останутся.Трассировка
Трассировка
Добавляйте логирование прихода события с
event, timestamp, idempotencyKey и статусом проверки подписи. Это поможет быстро сопоставить ваши логи с внутренними логами Exode.