Вебхуки: подписанные данные и повторы
Вы только что интегрировали платёжный шлюз или CI/CD-сервис, и теперь вам нужно получать события в реальном времени. Вебхуки — стандартное решение, но с ними связаны две критически важные задачи: убедиться, что запрос действительно исходит от отправителя, и гарантировать, что события не потеряются, если ваш сервер временно недоступен. В этой статье объясняется, как реализовать подписанные данные и механизмы повторов, чтобы построить надёжный приёмник вебхуков.
Что такое вебхуки?
Вебхук — это HTTP-колбэк: вместо того чтобы ваше приложение опрашивало API на предмет обновлений, сервис отправляет HTTP POST-запрос на указанный вами URL всякий раз, когда происходит событие. Такая модель push снижает задержку и нагрузку на сервер. Однако, поскольку запрос исходит от внешнего сервиса, вы должны рассматривать его как недоверенный ввод.
Зачем подписывать данные?
Без проверки любой, кто обнаружит вашу конечную точку вебхука, может отправлять поддельные события, потенциально вызывая несанкционированные действия. Подпись данных общим секретом позволяет подтвердить личность отправителя и убедиться, что данные не были изменены. Наиболее распространённый метод — HMAC (код аутентификации сообщения на основе хеша) с использованием SHA-256.
Как работает HMAC-подпись
Отправитель вычисляет HMAC-дайджест исходного тела запроса с помощью секретного ключа и включает его в заголовок (например, X-Signature). Ваш сервер пересчитывает HMAC с тем же секретом и сравнивает его с полученной подписью. Если они совпадают, запрос подлинный.
// Node.js example: verifying a webhook signature
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const hmac = crypto.createHmac('sha256', secret);
hmac.update(payload);
const digest = hmac.digest('hex');
// Use timingSafeEqual to prevent timing attacks
return crypto.timingSafeEqual(
Buffer.from(digest, 'hex'),
Buffer.from(signature, 'hex')
);
}
Важно: Всегда используйте функцию сравнения с постоянным временем (например, timingSafeEqual), чтобы избежать утечки информации через различия во времени выполнения. Кроме того, вычисляйте HMAC по исходному телу, а не по распарсенному JSON, поскольку повторная сериализация может изменить последовательность байтов.
Реализация повторов
Даже при наличии подписей сетевые проблемы или ошибки сервера могут привести к сбою доставки вебхука. Надёжный отправитель будет повторять неудачные доставки, как правило, с экспоненциальной задержкой. Как приёмник, вы должны спроектировать свою конечную точку идемпотентной и возвращать соответствующие коды состояния.
Стратегия повторов с точки зрения отправителя
- Повтор при ошибках 5xx и сетевых ошибках: они указывают на временные проблемы.
- Не повторять при ошибках 4xx: клиентские ошибки (например, 400 Bad Request) обычно постоянны; повтор не поможет.
- Используйте экспоненциальную задержку: увеличивайте интервал между повторами, чтобы не перегружать приёмник.
- Установите максимальное число повторов: после достижения лимита пометьте доставку как неудачную и отправьте оповещение.
Проектирование идемпотентного приёмника
Поскольку повторы могут приводить к дублирующимся доставкам, ваш приёмник должен обрабатывать одно и то же событие несколько раз без негативных последствий. Включите уникальный идентификатор события в данные (или заголовок) и сохраняйте обработанные ID. Если идентификатор события уже был обработан, немедленно верните 200 OK без повторной обработки.
// Pseudocode for idempotent webhook handling
function handleWebhook(event) {
if (processedEvents.has(event.id)) {
return { status: 200, body: 'Already processed' };
}
// Process event...
processedEvents.add(event.id);
return { status: 200, body: 'Success' };
}
Лучшие практики безопасности и надёжности вебхуков
- Используйте HTTPS: всегда обслуживайте конечную точку вебхука через TLS, чтобы предотвратить прослушивание.
- Проверяйте метку времени: если подпись содержит метку времени, отклоняйте запросы старше нескольких минут, чтобы предотвратить атаки повторного воспроизведения.
- Отвечайте быстро: подтверждайте получение статусом 2xx до выполнения тяжёлой обработки. Ставьте работу в асинхронную очередь.
- Логируйте всё: ведите логи полученных вебхуков, включая заголовки и данные (скрывая конфиденциальную информацию), для отладки проблем.
- Периодически меняйте секреты: поддерживайте несколько активных секретов, чтобы обеспечить бесшовную ротацию.
Сравнение: опрос vs. вебхуки
| Аспект | Опрос | Вебхуки |
|---|---|---|
| Задержка | Зависит от интервала | Почти в реальном времени |
| Нагрузка на сервер | Высокая (частые запросы) | Низкая (только при событиях) |
| Сложность | Просто реализовать | Требует проверки подписи и обработки повторов |
| Надёжность | Вы контролируете повторы | Зависит от политики повторов отправителя |
FAQ
Как проверить подпись вебхука?
Вычислите HMAC исходного тела запроса с помощью общего секрета, затем сравните его с подписью в заголовке с помощью функции сравнения с постоянным временем.
Что должна возвращать моя конечная точка вебхука?
Возвращайте код состояния 2xx (например, 200 OK), как только получили событие. Если вы вернёте 4xx или 5xx, отправитель может повторить попытку или пометить доставку как неудачную.
Можно ли использовать вебхуки без подписей?
Это не рекомендуется. Без подписей вы не можете проверить отправителя, что делает вашу конечную точку уязвимой для поддельных запросов. Всегда используйте подписи в продакшене.
При отладке данных вебхуков форматирование JSON имеет важное значение. Используйте наш JSON-форматтер, чтобы быстро отформатировать и проверить входящие JSON-данные.