Вебхуки: подписанные данные и стратегии повторных попыток
Вы создали API, который отправляет вебхуки на конечные точки ваших пользователей. Но затем сталкиваетесь с классическими проблемами: как убедиться, что данные не были подделаны? Что происходит, когда конечная точка недоступна? Как предотвратить дублирующую обработку? В этой статье мы рассмотрим эти проблемы с помощью подписанных данных и надежных стратегий повторных попыток.
Почему вебхукам нужны подписи
Вебхуки — это HTTP-обратные вызовы, которые уведомляют другие системы о событиях. Поскольку они отправляются через публичный интернет, они уязвимы для перехвата и подделки. Без подписи злоумышленник может отправлять поддельные вебхуки вашим пользователям, вызывая нежелательные действия. Подписание данных общим секретом обеспечивает подлинность и целостность.
Как работают подписанные данные
Наиболее распространенный подход — HMAC (код аутентификации сообщений на основе хэша) с SHA-256. Отправитель и получатель совместно используют секретный ключ. Отправитель вычисляет подпись для данных (и иногда временной метки) и включает ее в заголовок. Получатель пересчитывает подпись и сравнивает ее с полученной.
Пошагово: подписание данных
- Создайте данные: Сериализуйте данные события в JSON. Убедитесь в единообразии форматирования (например, без лишних пробелов), поскольку подпись вычисляется по точным байтам.
- Добавьте временную метку: Включите временную метку Unix в данные или заголовок, чтобы предотвратить атаки повторного воспроизведения. Получатель может отклонять запросы старше нескольких минут.
- Вычислите HMAC: Используйте свой секретный ключ для генерации HMAC-SHA256 от строки данных (или временной метки + данных).
- Прикрепите подпись: Отправьте ее в заголовке, например
X-Signature.
Пример на Python:
import hmac
import hashlib
import json
import time
secret = b'your_shared_secret'
payload = {"event": "user.created", "data": {"id": 123}}
body = json.dumps(payload, separators=(',', ':'))
timestamp = str(int(time.time()))
signed_content = timestamp + '.' + body
signature = hmac.new(secret, signed_content.encode(), hashlib.sha256).hexdigest()
# Отправьте заголовки: X-Signature: signature, X-Timestamp: timestamp
Проверка подписи
На принимающей стороне вы должны:
- Извлечь временную метку и подпись из заголовков.
- Отклонить, если временная метка слишком старая (например, >5 минут), чтобы предотвратить атаки повторного воспроизведения.
- Пересчитать подпись, используя тот же метод.
- Сравнить с помощью функции с постоянным временем выполнения, чтобы избежать атак по времени.
Пример проверки на Node.js:
const crypto = require('crypto');
function verifySignature(req, secret) {
const signature = req.headers['x-signature'];
const timestamp = req.headers['x-timestamp'];
if (!signature || !timestamp) return false;
if (Date.now() / 1000 - timestamp > 300) return false; // 5 мин
const body = JSON.stringify(req.body);
const signed = timestamp + '.' + body;
const expected = crypto.createHmac('sha256', secret).update(signed).digest('hex');
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
Разработка стратегий повторных попыток
Даже с подписями вебхуки могут не доставляться из-за проблем с сетью, ошибок сервера или тайм-аутов. Надежная стратегия повторных попыток обеспечивает доставку, не перегружая получателя.
Ключевые соображения
- Идемпотентность: Получатели должны корректно обрабатывать дублирующиеся события. Используйте ключ идемпотентности (например, ID события) для дедупликации.
- Экспоненциальная задержка: Повторяйте попытки с увеличивающимися интервалами (например, 1 с, 5 с, 30 с, 5 мин, 30 мин), чтобы дать получателю время на восстановление.
- Максимальное количество попыток: Установите лимит (например, 5 попыток), после чего помечайте вебхук как неудавшийся, возможно, уведомляя пользователя.
- Тайм-ауты: Установите разумный тайм-аут (например, 10 секунд) для каждой попытки.
Реализация повторных попыток
Большинство отправителей вебхуков используют систему очередей (например, Redis, RabbitMQ или базу данных) для планирования повторных попыток. Вот упрощенный поток:
- При срабатывании события поставьте в очередь задание вебхука с данными и целевым URL.
- Рабочий процесс берет задание и пытается доставить.
- Если статус ответа 2xx, пометьте как успешное.
- Если нет, запланируйте повторную попытку с экспоненциальной задержкой, до максимального количества попыток.
- После максимального количества попыток пометьте как неудачное и уведомите.
Пример расписания повторных попыток:
| Попытка | Задержка |
|---|---|
| 1 | Немедленно |
| 2 | 1 минута |
| 3 | 5 минут |
| 4 | 30 минут |
| 5 | 2 часа |
Лучшие практики безопасности вебхуков
- Используйте HTTPS: Всегда доставляйте вебхуки через TLS, чтобы предотвратить подслушивание.
- Ротация секретов: Периодически меняйте общие секреты и поддерживайте несколько активных секретов во время ротации.
- Проверка URL: Убедитесь, что URL вебхука использует HTTPS и не указывает на внутренние IP-адреса (для предотвращения SSRF).
- Логирование и мониторинг: Ведите журналы попыток доставки и сбоев для отладки и аудита.
Часто задаваемые вопросы
Что такое подписанные данные вебхука?
Подписанные данные включают криптографическую подпись (обычно HMAC), которая позволяет получателю проверить подлинность отправителя и то, что данные не были изменены.
Сколько раз следует повторять попытку при неудачном вебхуке?
Нет универсального ответа, но распространенный подход — 3–5 повторных попыток с экспоненциальной задержкой. Учитывайте критичность события и возможности получателя.
Можно ли использовать JWT для подписи вебхуков?
Да, JWT можно использовать, но это сложнее. HMAC проще и достаточно для большинства случаев. JWT полезен, если вам нужно включить утверждения или срок действия в саму подпись.
Готовы протестировать данные вашего вебхука? Используйте наш JSON Formatter для проверки и форматирования ваших JSON-данных перед отправкой.