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...) - каждый со своим форматом подписи. Как структурировать код чтобы избежать дублирования логики верификации?