Real-Time Backend

Webhooks

Интернет-магазин подключился к Stripe. Платёж прошёл, но магазин не получил уведомление - endpoint был на техобслуживании. Пользователь ждёт письмо 'Заказ оплачен', а его нет. Через час он звонит в поддержку. Retry-политика Stripe могла бы это предотвратить - но только если endpoint реализован правильно.

  • **Stripe:** обрабатывает >1 млрд webhook-событий в месяц, повторяет доставку до 3 дней (5 попыток с экспоненциальным backoff). HMAC-SHA256 подпись включает timestamp для защиты от replay attacks.
  • **GitHub:** webhook timeout - 10 секунд. При неудаче повторяет до 3 раз. `X-Hub-Signature-256` - HMAC-SHA256 от raw body с секретом репозитория. Используется для CI/CD интеграций (Jenkins, CircleCI, Vercel).
  • **Shopify:** хранит историю webhook delivery attempts последние 5 дней в admin-панели. Merchant видит статус каждого события - доставлено, ошибка, retry. Это снижает нагрузку на поддержку на 40%.
  • **Twilio:** при отправке SMS получает webhook от операторов о статусе доставки (delivered/failed). Если endpoint Twilio-клиента недоступен - статусы буферизуются и доставляются при восстановлении, до 24 часов.

Что такое webhook

Webhook - это HTTP POST-запрос, который ваш сервер отправляет на URL клиента когда происходит событие. Это инверсия polling: вместо того чтобы клиент спрашивал 'есть ли новые данные?' каждые N секунд, сервер сам уведомляет клиента в момент события. Stripe уведомляет о платеже, GitHub - о пуше в репозиторий, Twilio - о входящем SMS.

Stripe обрабатывает >1 млрд webhook-событий в месяц. При каждой транзакции система генерирует множество событий: payment_intent.created, payment_intent.processing, payment_intent.succeeded, charge.succeeded. Каждое событие доставляется на все зарегистрированные endpoint-ы merchant-а.

Ключевое ограничение webhook: клиент должен иметь публично доступный HTTPS-endpoint. Это делает webhooks неудобными для локальной разработки (localhost недоступен из интернета). Решение - туннели типа ngrok или специализированные инструменты (Stripe CLI, webhook.site).

Интернет-магазин хочет мгновенно обновлять статус заказа при изменении статуса доставки в курьерской службе. Какой подход выбрать?

Retry Policies для webhook

Клиентский endpoint может быть недоступен: деплой, кратковременный сбой, таймаут. Надёжная webhook-система не теряет события при таких сбоях - она повторяет доставку по определённой политике. Главная проблема: клиент должен быть идемпотентным, иначе повторная доставка создаст дубли.

Stripe повторяет доставку webhook до 3 дней при неудаче - итого 5 попыток с экспоненциальным backoff. GitHub Actions webhook timeout - 10 секунд: если receiver не ответил за 10 сек, считается отказ. Shopify хранит историю всех webhook delivery attempts в admin-панели последние 5 дней - merchant может видеть статус каждой доставки.

Webhook-endpoint вернул 500 Internal Server Error. Что должна сделать отправляющая система?

Webhook Signatures (HMAC)

Когда сервер получает webhook-запрос, как убедиться что он пришёл от доверенного отправителя, а не от злоумышленника? HMAC-подпись решает эту задачу: отправитель подписывает payload секретным ключом, получатель проверяет подпись тем же ключом. Без этого любой может прислать фейковое событие 'платёж прошёл'.

Stripe использует HMAC-SHA256 с временной меткой в подписи: `t=timestamp,v1=signature`. Временная метка позволяет отклонять запросы старше 5 минут - защита от replay attacks (злоумышленник перехватил легитимный webhook и отправил его повторно). GitHub использует HMAC-SHA256 в заголовке `X-Hub-Signature-256`.

Злоумышленник перехватил легитимный webhook 'payment.succeeded' и отправил его снова через 10 минут. Как система должна его отклонить?

Webhook Security Best Practices

Webhook endpoint - это публичный URL, принимающий данные из интернета. Без должных мер он становится вектором атаки: SSRF, injection через payload, DDoS через массовые запросы. Несколько уровней защиты превращают endpoint из дыры в надёжный интерфейс.

  • **Всегда верифицировать HMAC-подпись** до любой обработки payload - первая и главная проверка
  • **Отвечать 200 OK быстро** (<5 сек), обработку делать асинхронно - иначе отправитель решит что доставка не удалась и начнёт retry
  • **Идемпотентность по event_id** - хранить обработанные ID и пропускать дубли, так как retry гарантированы
  • **Rate limiting на endpoint** - ограничить число запросов с одного IP/источника, защита от DoS
  • **Проверять Content-Type** - принимать только application/json, отклонять другие форматы
  • **Не доверять данным из payload для действий с высоким риском** - перепроверять через API провайдера (например, Stripe SDK verify)

Критическая ошибка при верификации Stripe webhook: вычислять HMAC от JSON.parse + JSON.stringify вместо raw body. Сериализация JSON не гарантирует идентичную строку (порядок ключей, пробелы). HMAC верифицируется от exact bytes raw body - именно поэтому Stripe требует передавать raw Buffer, не распарсенный объект.

Webhook endpoint достаточно защитить HTTPS - шифрование трафика обеспечивает безопасность.

HTTPS шифрует трафик, но не аутентифицирует отправителя. Без HMAC-верификации любой злоумышленник может отправить POST на endpoint с фейковым payload. HTTPS + HMAC - оба уровня обязательны.

HTTPS защищает от перехвата в transit. HMAC защищает от подделки отправителя. Это два разных threat model: man-in-the-middle vs impersonation. Обе угрозы реальны и закрываются разными механизмами.

Webhook endpoint Stripe начал получать тысячи запросов в секунду. Обработка каждого занимает 2-3 секунды (запись в БД, отправка email). Что произойдёт?

Итоги

  • **Webhook = push вместо poll**: сервер уведомляет клиента при событии (<100 мс) вместо того чтобы клиент опрашивал каждые N секунд - снижает нагрузку и задержку одновременно.
  • **Retry + идемпотентность неразделимы**: любая webhook-система делает retry при ошибке, поэтому получатель обязан обрабатывать повторные доставки без побочных эффектов (проверка event_id в Redis).
  • **HMAC-подпись от raw body**: верификация от JSON.parse объекта ломает подпись из-за недетерминированной сериализации - проверять нужно точные байты body, как это делает Stripe SDK.

Связанные темы

Webhooks применяют несколько фундаментальных паттернов надёжных систем:

  • Webhook Delivery Guarantees — At-least-once delivery, ordering, dead letter queue - следующий урок развивает тему надёжности webhook-доставки
  • Idempotency — Идемпотентность получателя - обязательное требование, так как retry гарантированы при любой ошибке транзиента
  • Message Queues — Правильная реализация webhook-получателя: быстрый ответ 200, затем обработка через queue - классический паттерн async processing

Вопросы для размышления

  • Stripe прислал webhook о платеже. Нужно ли перепроверять данные через Stripe API, если HMAC-подпись валидна? В каких сценариях это критично?
  • Как тестировать webhook-интеграцию локально, если localhost недоступен из интернета? Какие инструменты и подходы существуют?
  • Webhook-endpoint получает события от 10 разных провайдеров (Stripe, GitHub, Twilio...) - каждый со своим форматом подписи. Как структурировать код чтобы избежать дублирования логики верификации?

Связанные уроки

  • net-36-websocket
  • sd-09-message-queue
Webhooks

0

1

Войти