Вебхуки: подписанные данные и повторы
Вы только что интегрировали платёжный шлюз. В документации сказано: «Мы отправим вебхук на ваш эндпоинт, когда платёж пройдёт успешно». Вы создаёте простой маршрут, тестируете его локально и развёртываете. А затем в продакшене замечаете дублирующиеся события, пропущенные уведомления и загадочные ошибки 500. Вебхуки кажутся простыми — пока не перестанут.
Это руководство объясняет, как на самом деле работают вебхуки, почему важны подписанные данные и как построить логику повторных попыток, которая поддерживает синхронизацию системы, даже когда что-то идёт не так.
Что такое вебхук?
Вебхук — это HTTP-колбэк: одна система отправляет HTTP POST-запрос на указанный вами URL при наступлении определённого события. В отличие от опроса (polling), когда вы постоянно спрашиваете «Что-нибудь произошло?», вебхуки доставляют события почти в реальном времени.
Типичные сценарии использования:
- Платёжные процессоры уведомляют об успешных списаниях.
- Сервисы хостинга Git запускают CI/CD-конвейеры при push.
- Коммуникационные платформы оповещают о новых сообщениях.
Отправитель (например, Stripe, GitHub) — это поставщик вебхуков; ваш сервер — потребитель вебхуков.
Почему подписанные данные необходимы
Ваш эндпоинт вебхуков доступен публично. Любой, кто знает URL, может отправлять поддельные события. Без проверки злоумышленник может подделать событие «платёж прошёл», и вы можете отправить товар бесплатно.
Подписанные данные решают эту проблему: в заголовках запроса передаётся криптографическая подпись, сгенерированная с использованием общего секрета, известного только вам и поставщику. Получив вебхук, вы заново вычисляете подпись и сравниваете. Если они совпадают, данные подлинные.
Как работают HMAC-подписи
Большинство поставщиков используют HMAC-SHA256. Поставщик вычисляет:
signature = HMAC-SHA256(secret, payload)и отправляет её в заголовке вроде X-Signature. Ваш сервер выполняет тот же расчёт и проверяет равенство. Поскольку секрет известен только сторонам, действительную подпись может сгенерировать только поставщик.
Проверка подписи на практике
Вот минимальный пример на Node.js с использованием Express:
const crypto = require('crypto');
const express = require('express');
const app = express();
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
const signature = req.headers['x-signature'];
const secret = process.env.WEBHOOK_SECRET;
const payload = req.body; // raw Buffer
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
if (signature !== expected) {
return res.status(401).send('Invalid signature');
}
// Process the event
const event = JSON.parse(payload);
console.log('Received event:', event.type);
res.status(200).send('OK');
});
app.listen(3000);Важно: всегда используйте необработанное тело запроса для проверки подписи. Если сначала распарсить JSON, сериализованная строка может отличаться (пробелы, порядок ключей), и проверка подписи не пройдёт.
Типичные ошибки
- Использование неверного секрета: у некоторых поставщиков отдельные секреты для тестового и рабочего режимов.
- Атаки по времени: используйте функцию сравнения с постоянным временем (например,
crypto.timingSafeEqual), чтобы не допустить утечки информации. - Игнорирование заголовков с временной меткой: многие поставщики включают временную метку для защиты от повторных атак. Отклоняйте запросы старше нескольких минут.
Повторные попытки: обработка сбоев без потерь
Сети дают сбои, серверы перезапускаются, происходят развёртывания. Поставщики вебхуков предполагают, что ваш эндпоинт может быть временно недоступен, поэтому они повторяют неудачные доставки. Но повторные попытки создают новые проблемы: дублирующиеся события, нарушение порядка доставки и бесконечные циклы, если не быть осторожным.
Как поставщики выполняют повторные попытки
Большинство поставщиков используют экспоненциальную задержку: после первой неудачи ждут недолго, затем интервалы постепенно увеличиваются. Например, Stripe повторяет попытки до 3 дней с возрастающими задержками. GitHub — до 8 раз. Точный график различается, но закономерность одна.
Ваш эндпоинт должен быстро возвращать код состояния 2xx, чтобы подтвердить получение. Любой другой статус (4xx, 5xx) сигнализирует о сбое и запускает повторную попытку.
Создание идемпотентных обработчиков
Поскольку повторные попытки могут доставить одно и то же событие несколько раз, ваш обработчик должен быть идемпотентным: обработка одного и того же события дважды должна давать тот же результат, что и однократная обработка.
Распространённые стратегии:
- Отслеживание ID события: сохраняйте уникальный ID события (указанный в данных или заголовках) в базе данных. Перед обработкой проверьте, не было ли оно уже обработано.
- Upsert-операции: используйте операции с базой данных, которые естественно идемпотентны (например,
INSERT ... ON CONFLICT DO NOTHING). - Конечные автоматы: проектируйте бизнес-логику так, чтобы она выдерживала повторные переходы (например, «отметить заказ как оплаченный» только если он ещё не оплачен).
Пример с простым множеством в памяти (для иллюстрации; в продакшене используйте постоянное хранилище):
const processedEvents = new Set();
app.post('/webhook', (req, res) => {
const event = req.body;
if (processedEvents.has(event.id)) {
return res.status(200).send('Already processed');
}
// Process event...
processedEvents.add(event.id);
res.status(200).send('OK');
});Быстрый ответ
У поставщиков часто есть тайм-аут (например, 5 секунд). Если ваш обработчик выполняет тяжёлую работу, подтвердите получение сразу и обрабатывайте асинхронно. Например, поместите событие в очередь и верните 200.
Лучшие практики для надёжных вебхуков
- Всегда проверяйте подписи, используя необработанное тело и сравнение с постоянным временем.
- Используйте HTTPS для вашего эндпоинта, чтобы предотвратить перехват и подмену данных.
- Реализуйте идемпотентность для обработки дублирующихся доставок.
- Логируйте всё — входящие данные, подписи и ваши ответы — для отладки.
- Следите за сбоями и настройте оповещения при повторяющихся ошибках.
- Тестируйте с реальными событиями через панели управления поставщиков или CLI-инструменты.
- Обрабатывайте события, пришедшие не по порядку, включая временные метки или порядковые номера в вашу логику.
Сравнение: опрос и вебхуки
| Аспект | Опрос | Вебхуки |
|---|---|---|
| Задержка | Зависит от интервала | Почти реальное время |
| Нагрузка на сервер | Высокая (постоянные запросы) | Низкая (только при событиях) |
| Сложность | Просто реализовать | Требуется эндпоинт, безопасность, повторы |
| Надёжность | Вы контролируете повторы | Поставщик контролирует повторы |
FAQ
Что такое подпись вебхука?
Подпись вебхука — это криптографический хеш (обычно HMAC-SHA256) данных, сгенерированный с использованием общего секрета. Она позволяет получателю убедиться, что запрос пришёл от ожидаемого отправителя и не был изменён.
Как обрабатывать дублирующиеся события вебхуков?
Сделайте обработчик идемпотентным, отслеживая ID событий или используя операции с базой данных, которые естественно идемпотентны. Перед применением изменений проверьте, не было ли событие уже обработано.
Что должен возвращать мой эндпоинт вебхуков?
Возвращайте код состояния 2xx (например, 200 OK), как только получили и проверили событие. Если требуется тяжёлая обработка, подтвердите получение сразу и выполняйте работу асинхронно.
Готовы протестировать данные ваших вебхуков? Используйте наш JSON Formatter, чтобы просмотреть и проверить структуру JSON входящих событий.