Вебхуки: подписанные payload и логика повторов
Вы создали API, и теперь нужно уведомлять другие сервисы о происходящих событиях. Опрос неэффективен, поэтому вы обращаетесь к вебхукам. Но вскоре обнаруживаете, что вебхуки могут давать сбои, быть подделаны или вызывать дублирующую обработку. В этой статье объясняется, как защитить вебхуки с помощью подписанных payload и реализовать надёжную логику повторов.
Что такое вебхуки?
Вебхуки — это определяемые пользователем HTTP-колбэки. Когда в вашей системе происходит событие (например, успешный платёж), вы отправляете HTTP POST-запрос на URL, предоставленный подписчиком. Payload обычно содержит данные события в формате JSON или form.
Вебхуки обеспечивают связь между сервисами в реальном времени без постоянного опроса. Они широко используются платёжными шлюзами, инструментами CI/CD и SaaS-платформами.
Зачем защищать вебхуки?
Без защиты любой, кто знает URL вебхука, может отправлять поддельные запросы, что приведёт к повреждению данных или мошенничеству. Кроме того, злоумышленники могут повторно отправлять старые запросы. Подписание payload с помощью общего секрета обеспечивает подлинность и целостность.
Подписание payload с помощью HMAC
HMAC (код аутентификации сообщения на основе хеша) — распространённый метод подписи вебхуков. Отправитель и получатель совместно используют секретный ключ. Отправитель вычисляет HMAC от payload с использованием секрета и включает его в заголовок (например, X-Signature). Получатель пересчитывает HMAC и сравнивает.
Пошагово: отправка подписанного вебхука
- Создайте payload (JSON).
- Вычислите HMAC-SHA256 от payload с использованием секрета.
- Включите hex-дайджест в заголовок, например,
X-Signature: sha256=.... - Отправьте POST-запрос.
// Пример на Node.js с использованием crypto
const crypto = require('crypto');
const secret = 'your_shared_secret';
const payload = JSON.stringify({ event: 'payment.success', amount: 100 });
const signature = crypto.createHmac('sha256', secret).update(payload).digest('hex');
// Отправьте POST с заголовком X-Signature: sha256=signature
Проверка подписи
На стороне получателя извлеките заголовок подписи, вычислите HMAC от исходного тела запроса и сравните, используя сравнение с постоянным временем для предотвращения атак по времени.
// Пример на Express.js
app.post('/webhook', (req, res) => {
const signature = req.headers['x-signature'];
const computed = crypto.createHmac('sha256', secret).update(req.rawBody).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(computed))) {
return res.status(401).send('Invalid signature');
}
// Обработайте payload
res.status(200).send('OK');
});
Примечание: убедитесь, что используете исходное тело, а не распарсенный JSON, так как парсинг может изменить payload.
Предотвращение атак повторного воспроизведения
Включите временную метку в подписанный payload и отклоняйте запросы старше нескольких минут. Также включите уникальный ID события и сохраняйте обработанные ID, чтобы избежать дублирования.
Логика повторов для надёжной доставки
Конечные точки вебхуков могут быть недоступны или работать медленно. Реализуйте повторные попытки с экспоненциальной задержкой и джиттером. Установите максимальное количество повторов (например, 5) и таймаут для каждого запроса.
Стратегия повторов
- Экспоненциальная задержка: ждите 1 с, 2 с, 4 с, 8 с и т.д.
- Джиттер: добавьте случайность, чтобы избежать эффекта «громового стада».
- Идемпотентность: используйте ключ идемпотентности, чтобы дублирующие доставки не вызывали повторных действий.
Пример расписания повторов: 1-й повтор через 5 с, 2-й через 30 с, 3-й через 2 мин, 4-й через 10 мин, 5-й через 1 ч.
Обработка сбоев
Если все повторы не удались, зарегистрируйте сбой и уведомите подписчика (например, по электронной почте или через панель управления). Предоставьте возможность ручной повторной отправки вебхуков.
Сравнение: подписанные payload vs. базовая аутентификация
| Метод | Безопасность | Сложность | Защита от повторов |
|---|---|---|---|
| HMAC подписанный payload | Высокая | Средняя | Да (с временной меткой) |
| Базовая аутентификация | Низкая | Низкая | Нет |
| API-ключ в заголовке | Средняя | Низкая | Нет |
Лучшие практики
- Всегда используйте HTTPS для конечных точек вебхуков.
- Периодически меняйте секреты.
- Проверяйте схему payload перед обработкой.
- Отвечайте быстро (в пределах 2xx) и обрабатывайте асинхронно.
- Отслеживайте метрики доставки вебхуков (процент успешных доставок, задержку).
Часто задаваемые вопросы
Как проверить подпись вебхука?
Вычислите HMAC от исходного тела запроса с использованием вашего секрета и сравните его с заголовком подписи, используя сравнение с постоянным временем.
Что делать, если вебхук не удался?
Реализуйте повторные попытки с экспоненциальной задержкой и джиттером. Если все повторы не удались, зарегистрируйте сбой и предоставьте возможность ручной повторной отправки.
Можно ли использовать вебхуки без подписи?
Это не рекомендуется. Без подписи вы не можете проверить отправителя, что делает вашу систему уязвимой для подделки и атак повторного воспроизведения.
При тестировании payload вебхуков вам может потребоваться проверить JSON-ответы. Используйте наш JSON Formatter, чтобы быстро отформатировать и проверить данные JSON.