Вебхуки: подписанные payload и повторные попытки
Зачем вебхукам подписи и повторные попытки
Вы создали эндпоинт для получения вебхуков от платёжного провайдера или SaaS-платформы. В тестах всё работает, но в продакшене возникают две проблемы: как убедиться, что запрос действительно пришёл от провайдера, и что произойдёт, если ваш сервер недоступен или медленно отвечает? Без подписанных payload любой может подделать запрос. Без повторных попыток один сетевой сбой может навсегда потерять событие.
Это руководство объясняет, как правильно реализовать и то, и другое. Мы рассмотрим HMAC-подписи, шаги проверки, стратегии повторных попыток и идемпотентность — три столпа надёжной обработки вебхуков.
Как работают подписи вебхуков
Подпись вебхука — это криптографический хеш, который доказывает, что payload был создан тем, кто знает общий секрет. Большинство провайдеров используют HMAC-SHA256. Отправитель вычисляет подпись по сырому телу запроса (иногда с меткой времени) и включает её в заголовок. Получатель пересчитывает её и сравнивает.
Типичный процесс:
- Провайдер и потребитель имеют общий секретный ключ (настраивается в панели провайдера).
- Провайдер создаёт payload, вычисляет
HMAC-SHA256(secret, payload)и отправляет его в заголовке, напримерX-Signature. - Потребитель получает запрос, читает сырое тело, пересчитывает HMAC и сравнивает с помощью функции с постоянным временем выполнения.
- Если они совпадают, событие обрабатывается; иначе запрос отклоняется с кодом 401.
Важно: Всегда используйте сырое тело запроса в том виде, в каком оно получено, а не распарсенную и повторно сериализованную версию. Сериализация JSON может изменить пробелы и порядок ключей, что нарушит подпись.
Добавление меток времени для предотвращения атак повторного воспроизведения
Подписанный payload сам по себе может быть воспроизведён злоумышленником, который перехватил действительный запрос. Чтобы смягчить это, включите метку времени в подписываемые данные и отклоняйте запросы старше нескольких минут. Например:
signed_payload = timestamp + "." + raw_body
signature = HMAC-SHA256(secret, signed_payload)
Потребитель проверяет, что метка времени находится в допустимом окне (например, 5 минут), и что подпись совпадает.
Реализация проверки подписи
Рассмотрим конкретный пример на Python с использованием Flask. Мы проверим подпись, переданную в заголовке X-Signature.
import hmac
import hashlib
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = b'your_shared_secret'
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-Signature')
if not signature:
abort(401)
raw_body = request.get_data()
expected = hmac.new(SECRET, raw_body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, signature):
abort(401)
# Обработка события
event = request.json
# ... обработка события ...
return '', 200
Ключевые моменты:
- Используйте
hmac.compare_digest(или эквивалент) для сравнения с постоянным временем выполнения, чтобы избежать атак по времени. - Получайте сырое тело через
request.get_data()до любого парсинга. - Возвращайте 200 быстро; тяжёлую обработку выполняйте асинхронно.
Проектирование стратегии повторных попыток
Даже с подписями вебхуки могут завершаться неудачей: ваш сервер может быть недоступен, вернуть 500 или превысить тайм-аут. Провайдеры обычно повторяют попытки с экспоненциальной задержкой. Как потребитель, вы также должны корректно обрабатывать повторные попытки.
Что следует учитывать:
- Идемпотентность: Каждое событие должно иметь уникальный ID. Сохраняйте обработанные ID, чтобы избежать повторной обработки при повторных попытках.
- Быстрый ответ: Подтверждайте получение статусом 2xx в течение нескольких секунд. Ставьте событие в очередь для фоновой обработки.
- Логика повторных попыток на вашей стороне: Если вы вызываете нижестоящие сервисы, реализуйте повторные попытки с задержкой и размыканием цепи.
Большинство провайдеров повторяют попытки при ответах, отличных от 2xx, или тайм-аутах, часто в течение до 24 часов с увеличивающимися интервалами. Уточните детали в документации вашего провайдера.
Пример: идемпотентный обработчик событий
Используйте таблицу в базе данных для отслеживания обработанных ID событий. Вот упрощённый шаблон:
def handle_event(event_id, payload):
if db.exists('processed_events', event_id):
return # уже обработано
# обработка payload
db.insert('processed_events', event_id)
# ... фактическая работа ...
Для высокой пропускной способности используйте уникальное ограничение на ID события и перехватывайте ошибки дублирования ключа.
Сравнение: подписанные и неподписанные вебхуки
| Аспект | Подписанные payload | Неподписанные payload |
|---|---|---|
| Безопасность | Проверяет подлинность отправителя | Любой может подделать запросы |
| Защита от повторного воспроизведения | Возможна с метками времени | Отсутствует |
| Трудозатраты на реализацию | Средние (HMAC + проверка метки времени) | Низкие |
| Рекомендуется для | Всех продакшен-вебхуков | Только внутреннего тестирования |
Распространённые ошибки и лучшие практики
- Не парсите до проверки: Всегда сначала проверяйте подпись на сыром теле.
- Используйте HTTPS: Подписи защищают целостность, но HTTPS защищает конфиденциальность.
- Ротируйте секреты: Поддерживайте несколько активных секретов во время ротации, чтобы избежать простоя.
- Логируйте сбои: Записывайте несовпадения подписей и повторные попытки для отладки.
- Тестируйте с реальными payload: Используйте инструменты вроде webhook.site или CLI вашего провайдера для симуляции событий.
Часто задаваемые вопросы
Что такое подписанный payload вебхука?
Подписанный payload включает криптографическую подпись (обычно HMAC), которая позволяет получателю убедиться, что запрос пришёл из доверенного источника и не был изменён.
Как обрабатывать повторные попытки вебхуков без дублирования действий?
Сделайте обработчик идемпотентным, отслеживая ID событий. Перед обработкой проверьте, не был ли ID уже обработан; если да, верните успех, не повторяя работу.
Можно ли использовать вебхуки без подписей?
Это не рекомендуется для продакшена. Без подписей вы не можете отличить легитимные запросы от поддельных. Всегда проверяйте подписи, когда они доступны.
При отладке payload вебхуков или анализе серверных логов JSON Formatter поможет быстро просмотреть и проверить структуры JSON.