Вебхуки: подписанные данные и повторные попытки
Зачем вебхукам нужны подписанные данные и повторные попытки
Вы только что интегрировали сторонний сервис, который отправляет вебхуки на ваш эндпоинт. В тестах всё работает, но в продакшене вы замечаете пропущенные события, дублирующиеся доставки и, что хуже, — кто-то подделывает запросы на ваш URL вебхука. Без надлежащих мер безопасности и надёжности вебхуки могут стать источником багов и уязвимостей.
Вебхуки — это HTTP-колбэки, которые уведомляют ваше приложение о наступлении события. Концептуально они просты, но на практике сложны, поскольку пересекают границы доверия и сталкиваются с проблемами надёжности сети. В этом руководстве мы рассмотрим два критически важных аспекта: подписанные данные для проверки подлинности и повторные попытки для гарантии доставки. Мы разберём практические реализации, которые вы можете применить сразу.
Понимание подписанных данных
Подписанные данные — это способ отправителя доказать, что вебхук пришёл именно от него и не был изменён. Отправитель вычисляет криптографическую подпись над телом запроса (а иногда и над заголовками) с использованием общего секрета, затем включает эту подпись в заголовок. Ваш эндпоинт пересчитывает подпись и сравнивает её.
Наиболее распространённый алгоритм — HMAC-SHA256. Он быстрый, безопасный и широко поддерживается. Вот как это работает:
- Отправитель и получатель используют общий секретный ключ (настраивается в параметрах вебхука).
- Для каждого вебхука отправитель создаёт подпись, хешируя данные с секретом с помощью HMAC-SHA256.
- Подпись отправляется в заголовке, например
X-Signature. - Получатель вычисляет тот же HMAC и сравнивает его, используя сравнение с постоянным временем для предотвращения атак по времени.
Реализация проверки HMAC в Node.js
Рассмотрим конкретный пример. Предположим, отправитель включает подпись в X-Signature в виде hex-строки. Вот как проверить её в маршруте Express:
const crypto = require('crypto');
const express = require('express');
const app = express();
// Используйте сырое тело для проверки подписи
app.use('/webhook', express.raw({ type: 'application/json' }));
app.post('/webhook', (req, res) => {
const signature = req.headers['x-signature'];
const secret = process.env.WEBHOOK_SECRET;
const expected = crypto
.createHmac('sha256', secret)
.update(req.body)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).send('Invalid signature');
}
// Обработка события
const event = JSON.parse(req.body);
console.log('Received event:', event.type);
res.status(200).send('OK');
});
app.listen(3000);
Важно: Всегда используйте сырое тело запроса для вычисления подписи, а не распарсенный JSON. Парсинг и повторная сериализация могут изменить пробелы и порядок, нарушив подпись.
Защита от повторов с помощью временных меток
Одни подписи не защищают от атак повторного воспроизведения. Злоумышленник может перехватить валидный подписанный запрос и отправить его позже. Чтобы смягчить это, включите временную метку в подписанные данные или в заголовок. Например, добавьте заголовок X-Timestamp с Unix-временем. Получатель проверяет, что временная метка находится в пределах допустимого окна (например, 5 минут), и отклоняет более старые запросы.
Также включайте временную метку в вычисление подписи, чтобы предотвратить подмену. Отправитель вычисляет HMAC над timestamp + '.' + body. Получатель делает то же самое и сравнивает.
Реализация повторных попыток для надёжной доставки
Вебхуки передаются через публичный интернет, поэтому сбои неизбежны: сетевые таймауты, серверные ошибки (5xx) или временная недоступность вашего эндпоинта. Надёжная система вебхуков должна повторять неудачные доставки.
Стратегии повторных попыток
Распространённые стратегии повторных попыток включают:
- Фиксированная задержка: Повтор через фиксированный интервал (например, 1 минуту). Просто, но может перегрузить при большом числе сбоев.
- Экспоненциальная задержка: Ждать дольше после каждой попытки (например, 1с, 2с, 4с, 8с...). Это наиболее распространённый и рекомендуемый подход.
- Джиттер: Добавление случайности к задержке, чтобы избежать проблемы «стада» при одновременном сбое множества вебхуков.
Типичная политика может быть такой: до 5 повторных попыток с экспоненциальной задержкой и джиттером, начиная с 10 секунд. После этого доставка помечается как неудачная, и, возможно, отправляется оповещение.
Идемпотентность: обработка дубликатов
Повторные попытки могут привести к дублирующимся доставкам. Ваш эндпоинт должен быть идемпотентным — обработка одного и того же события дважды не должна вызывать побочных эффектов, например двойного списания средств с клиента. Включите уникальный ID события в данные (например, event.id) и сохраняйте обработанные ID в течение разумного периода. Перед обработкой проверьте, не встречался ли уже этот ID.
В качестве альтернативы проектируйте операции так, чтобы они были естественно идемпотентными (например, используя upsert вместо insert).
Быстрый ответ
Ваш эндпоинт вебхука должен быстро подтверждать получение (в течение нескольких секунд) и обрабатывать событие асинхронно. Если обработка занимает слишком много времени, отправитель может получить таймаут и повторить попытку, вызвав дубликаты. Используйте очередь: отвечайте 200 OK сразу после проверки подписи, затем ставьте событие в очередь для фоновой обработки.
Сравнение мер безопасности вебхуков
| Мера | Назначение | Сложность реализации |
|---|---|---|
| Подпись HMAC | Проверка подлинности и целостности | Низкая |
| Проверка временной метки | Защита от атак повторного воспроизведения | Низкая |
| Ключи идемпотентности | Предотвращение дублирующей обработки | Средняя |
| Белый список IP | Ограничение известными IP отправителя | Низкая (но ненадёжная) |
| HTTPS | Шифрование данных при передаче | Низкая |
Хотя белый список IP может добавить уровень защиты, он ненадёжен, поскольку IP отправителя могут меняться. Полагайтесь в первую очередь на подписи.
Тестирование эндпоинта вебхука
Используйте инструменты вроде ngrok, чтобы открыть локальный сервер и запускать тестовые вебхуки из панели провайдера. Убедитесь, что подписи проверяются корректно, и что повторные попытки работают, симулируя сбои (например, возвращайте 500 для первых нескольких попыток).
При отладке вам может потребоваться просмотреть JSON-данные. JSON Formatter поможет быстро отформатировать и проверить данные вебхука.
Часто задаваемые вопросы
Что такое подписанные данные вебхука?
Подписанные данные включают криптографическую подпись (обычно HMAC), которая позволяет получателю убедиться, что запрос пришёл от ожидаемого отправителя и не был изменён. Она вычисляется с использованием общего секрета.
Сколько раз повторять неудачный вебхук?
Универсального числа нет, но обычно делают 3–5 повторных попыток с экспоненциальной задержкой. После этого логируйте сбой и рассмотрите оповещение. Цель — обработать временные проблемы без бесконечных повторов.
Можно ли использовать JWT для подписи вебхуков?
Да, некоторые провайдеры используют JWT (JSON Web Tokens) для подписи вебхуков. Токен содержит утверждения и подписывается секретом или закрытым ключом. Проверка аналогична: проверьте подпись и утверждения, такие как срок действия.
Готовы внедрить безопасные вебхуки? Начните с генерации надёжного секрета, добавления проверки HMAC и настройки повторных попыток с идемпотентностью. Для быстрой проверки данных во время разработки попробуйте наш JSON Formatter, чтобы отформатировать и проверить JSON вебхука.