Вебхуки: подписанные данные и повторные попытки
Вы только что интегрировали стороннего платёжного провайдера. Пользователь совершает покупку, но ваша база данных не обновляется. Почему? Потому что вебхук, который должен был уведомить ваше приложение, завершился с ошибкой. Без надлежащей проверки подписи и логики повторных попыток вебхуки могут стать источником загадочных ошибок и уязвимостей.
В этой статье мы объясним, как работают вебхуки, почему необходимо проверять подписанные данные и как спроектировать надёжный механизм повторных попыток. Мы также покажем, как эффективно тестировать и отлаживать вебхуки.
Что такое вебхуки?
Вебхуки — это определяемые пользователем HTTP-колбэки. Они позволяют одной системе отправлять данные в реальном времени другой системе при возникновении события. Например, когда платёж обработан, платёжный провайдер отправляет HTTP POST-запрос на указанный вами URL, содержащий подробности о событии.
В отличие от опроса (polling), при котором ваше приложение многократно запрашивает обновления, вебхуки передают данные вам, как только они становятся доступны. Это делает их эффективными и почти мгновенными.
Проблема безопасности: зачем проверять подписи?
Конечные точки вебхуков — это общедоступные URL. Любой может отправить на них POST-запрос. Без проверки злоумышленник мог бы подделать запрос, например, чтобы отметить заказ как оплаченный без фактической оплаты. Чтобы предотвратить это, провайдеры подписывают каждый payload секретным ключом.
Как работают HMAC-подписи
Большинство провайдеров используют HMAC (код аутентификации сообщения на основе хэша) с SHA-256. Они вычисляют хэш тела запроса с использованием общего секрета и включают его в заголовок, например X-Signature. Ваш сервер пересчитывает хэш и сравнивает его с заголовком. Если они совпадают, запрос подлинный.
Вот простой пример на Python:
import hmac
import hashlib
def verify_signature(payload, secret, signature):
expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
Всегда используйте функцию сравнения с постоянным временем, такую как hmac.compare_digest, чтобы избежать атак по времени.
Распространённые ошибки, которых следует избегать
- Использование неправильного секрета: Каждая среда (тестовая, рабочая) должна иметь свой собственный секрет.
- Отсутствие проверки метки времени: Некоторые провайдеры включают метку времени для предотвращения повторных атак. Отклоняйте запросы старше нескольких минут.
- Логирование секретов: Никогда не записывайте в логи секрет или полную подпись в производственной среде.
Проектирование надёжных повторных попыток
Вебхуки отправляются через интернет, поэтому сбои неизбежны. Сети обрываются, серверы перезагружаются, конечные точки не отвечают вовремя. Хорошая система вебхуков повторяет неудачные доставки с экспоненциальной задержкой.
Как обычно работают повторные попытки
Провайдеры обычно повторяют попытки с увеличивающимися задержками: через 1 минуту, 5 минут, 30 минут и т.д., до максимального числа попыток (например, 5-10). Если все повторные попытки не удались, событие помечается как неудачное и может быть доступно для ручного воспроизведения.
Ваша конечная точка должна быстро отвечать кодом состояния 2xx, чтобы подтвердить получение. Если вы возвращаете код, отличный от 2xx, или происходит тайм-аут, провайдер повторит попытку.
Реализация идемпотентности
Поскольку повторные попытки могут вызывать дублирующиеся доставки, ваша конечная точка должна быть идемпотентной. Используйте ID события (включённый в payload или заголовки), чтобы отслеживать обработанные события. Перед обработкой проверьте, не обрабатывали ли вы уже этот ID. Храните обработанные ID в базе данных с TTL.
Пример проверки идемпотентности:
def process_event(event_id, data):
if db.exists(f"processed:{event_id}"):
return # already processed
# process data
db.setex(f"processed:{event_id}", 86400, "1") # expire after 24h
Пошаговое создание получателя вебхуков
- Откройте HTTPS-эндпоинт: Используйте TLS для защиты данных при передаче.
- Проверьте подпись: Отклоняйте запросы с недействительными подписями с кодом 401.
- Проверьте метку времени: Если она предоставлена, убедитесь, что она находится в допустимом окне (например, 5 минут).
- Разберите payload: Проверьте структуру JSON и обязательные поля.
- Проверьте идемпотентность: Используйте ID события, чтобы избежать дублирования.
- Обрабатывайте асинхронно: Немедленно отвечайте 200, затем обрабатывайте в фоновой задаче, чтобы избежать тайм-аутов.
- Логируйте и отслеживайте: Записывайте сбои и настройте оповещения.
Тестирование вебхуков локально
Во время разработки вам нужен способ получать вебхуки на локальной машине. Инструменты вроде ngrok или localtunnel создают публичный URL, который перенаправляет на ваш localhost. Кроме того, многие провайдеры предлагают CLI для запуска тестовых событий.
Всегда тестируйте проверку подписи как с действительными, так и с недействительными подписями. Имитируйте повторные попытки, возвращая коды ошибок и наблюдая за поведением провайдера при повторных попытках.
Сравнение: вебхуки vs. опрос
| Аспект | Вебхуки | Опрос |
|---|---|---|
| Реальное время | Почти реальное время | Задержка на интервал |
| Эффективность | Отправляет данные только при событии | Частые запросы даже при отсутствии данных |
| Сложность | Требуется эндпоинт, проверка подписи, повторные попытки | Проще в реализации |
| Надёжность | Зависит от механизма повторных попыток | Вы контролируете расписание |
Часто задаваемые вопросы
Что такое подписанный payload вебхука?
Подписанный payload включает криптографическую подпись (обычно HMAC), которая позволяет получателю убедиться, что запрос пришёл от ожидаемого отправителя и не был изменён.
Сколько раз следует повторять вебхук?
Большинство провайдеров повторяют попытки 5-10 раз с экспоненциальной задержкой. Точное число зависит от политики провайдера. Ваша конечная точка должна быть идемпотентной для обработки дубликатов.
Какой код состояния HTTP должен возвращать мой вебхук?
Возвращайте код состояния 2xx (например, 200 OK), чтобы подтвердить успешное получение. Любой другой код (4xx или 5xx) сигнализирует о сбое и вызывает повторные попытки.
Готовы протестировать ваши payload'ы вебхуков? Используйте наш JSON Formatter для быстрой проверки и форматирования JSON-данных.