Перейти к содержанию

Лучшие практики

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

Идемпотентность

Одно и то же событие может быть доставлено несколько раз: при retry после временной ошибки или при ручном replay. Это нормальное поведение — проектируйте обработчик так, чтобы повторная доставка не приводила к двойной обработке.

Используйте event_id как ключ дедупликации

X-Webhook-Event-Id (и одноимённое поле в теле) уникально для события и не меняется при retry. Сохраняйте обработанные event_id в БД и игнорируйте дубли.

processed = db.execute(
    "INSERT INTO webhook_events (event_id) VALUES (%s) "
    "ON CONFLICT DO NOTHING RETURNING event_id",
    (event_id,),
)
if processed.rowcount == 0:
    return  # Уже обработано
# ... основная логика

Храните таблицу обработанных event_id минимум 7 дней — это совпадает с replay-окном и покрывает все повторные доставки (как автоматический retry, так и ручной replay).

Отвечайте быстро

Что делать Что не делать
Сохранить payload в очередь / БД Делать синхронные вызовы во внешние сервисы
Сразу вернуть 200 OK Запускать долгие транзакции в БД
Обрабатывать событие в фоне Считать длинные отчёты / отправлять email синхронно

Таймаут доставки — 10 секунд. Если ваш сервер отвечает медленнее — Pert считает доставку неуспешной и запускает retry, даже если на самом деле событие уже было получено и обрабатывается.

Безопасность

Всегда проверяйте подпись

Не доверяйте полям event_id, event_type, data до успешной верификации X-Webhook-Signature. Без подписи любой может постучаться на ваш URL и подделать «событие».

См. подробности на странице Верификация подписи.

Дополнительно:

  • Сравнивайте X-Webhook-Workspace-Id с ожидаемым на вашей стороне — если у вас несколько рабочих пространств, события не должны «перетекать» между ними.
  • Ограничивайте размер тела при чтении (например, 1 МБ). Это защита от случайных или злонамеренных огромных тел.
  • Логируйте отказы верификации — резкий рост количества может говорить об атаке или о расхождении публичного ключа.
  • Не пишите тело запроса в публичные логи — там может быть чувствительная информация о транзакциях.

Ограничивайте подписки

Подписывайтесь только на те категории, которые реально обрабатываете. Для каждой не нужной категории — это лишний трафик, лишние записи в истории доставок и потенциальные false-positive отказы.

Один webhook ≠ одна категория

Если вы обрабатываете transactions и vaults в одном сервисе — один webhook с обеими категориями работает проще. Если в разных сервисах — два webhook позволят независимо управлять статусом, retry и автоотключением.

Мониторинг

Настройте мониторинг по двум сторонам:

Что мониторить Где
HTTP-коды ответов на webhook-запросы На стороне вашего приложения
Латенция обработчика (p50, p95, p99) На стороне вашего приложения
Статус webhook (active / disabled) Через API /api/v1/webhooks
Количество failed за последние 24 часа Через API /api/v1/webhooks/{id}/deliveries

Алертинг:

  • Webhook перешёл в статус disabled — критичный алерт.
  • Доля 5xx ответов выросла выше N% — предупреждение.
  • Латенция обработчика > 5 секунд — предупреждение (близко к таймауту).

Тестирование

Перед подключением webhook в боевом окружении пройдите чек-лист:

  • Зарегистрируйте webhook на тестовом URL. Подойдёт собственный staging-эндпоинт или webhook.site.
  • Проверьте подпись. Это самое частое место для ошибок (raw body, кодировка, неправильный ключ).
  • Проверьте идемпотентность. Запустите replay и убедитесь, что дубли распознаются.
  • Сэмулируйте сбой. Намеренно верните 500 и проверьте, что приходит retry с тем же event_id.
  • Сэмулируйте автоотключение. Длительно возвращайте 500 и убедитесь, что мониторинг видит disabled.

Анти-паттерны

Чего делать не стоит

  • Парсить JSON до проверки подписи — фреймворки часто делают это автоматически и ломают подпись;
  • Отвечать 200 OK после долгой синхронной обработки — таймаут доставки приведёт к retry и дублям;
  • Игнорировать event_id — без него любая retry приведёт к двойной обработке;
  • Принимать webhook по HTTP — невозможно (HTTPS обязателен);
  • Раздавать публичный ключ из своих внутренних эндпоинтов — клиенты должны брать его напрямую с /api/v1/webhooks/signing-key, чтобы автоматически получать ротации;
  • Хранить тела событий в открытом виде дольше необходимого — там может быть чувствительная информация.