Лучшие практики
Эти рекомендации помогут построить надёжную интеграцию: события не теряются, обработка не блокирует доставку, безопасность не страдает.
Идемпотентность
Одно и то же событие может быть доставлено несколько раз: при 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, чтобы автоматически получать ротации; - Хранить тела событий в открытом виде дольше необходимого — там может быть чувствительная информация.