Вебхуки: подписанные данные и повторы

Backend2026-10-17TryQuickToolBox

Вы только что интегрировали платёжный шлюз. В документации сказано: «Мы отправим вебхук на ваш эндпоинт, когда платёж пройдёт успешно». Вы создаёте простой маршрут, тестируете его локально и развёртываете. А затем в продакшене замечаете дублирующиеся события, пропущенные уведомления и загадочные ошибки 500. Вебхуки кажутся простыми — пока не перестанут.

Это руководство объясняет, как на самом деле работают вебхуки, почему важны подписанные данные и как построить логику повторных попыток, которая поддерживает синхронизацию системы, даже когда что-то идёт не так.

Что такое вебхук?

Вебхук — это HTTP-колбэк: одна система отправляет HTTP POST-запрос на указанный вами URL при наступлении определённого события. В отличие от опроса (polling), когда вы постоянно спрашиваете «Что-нибудь произошло?», вебхуки доставляют события почти в реальном времени.

Типичные сценарии использования:

Отправитель (например, 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, сериализованная строка может отличаться (пробелы, порядок ключей), и проверка подписи не пройдёт.

Типичные ошибки

Повторные попытки: обработка сбоев без потерь

Сети дают сбои, серверы перезапускаются, происходят развёртывания. Поставщики вебхуков предполагают, что ваш эндпоинт может быть временно недоступен, поэтому они повторяют неудачные доставки. Но повторные попытки создают новые проблемы: дублирующиеся события, нарушение порядка доставки и бесконечные циклы, если не быть осторожным.

Как поставщики выполняют повторные попытки

Большинство поставщиков используют экспоненциальную задержку: после первой неудачи ждут недолго, затем интервалы постепенно увеличиваются. Например, Stripe повторяет попытки до 3 дней с возрастающими задержками. GitHub — до 8 раз. Точный график различается, но закономерность одна.

Ваш эндпоинт должен быстро возвращать код состояния 2xx, чтобы подтвердить получение. Любой другой статус (4xx, 5xx) сигнализирует о сбое и запускает повторную попытку.

Создание идемпотентных обработчиков

Поскольку повторные попытки могут доставить одно и то же событие несколько раз, ваш обработчик должен быть идемпотентным: обработка одного и того же события дважды должна давать тот же результат, что и однократная обработка.

Распространённые стратегии:

Пример с простым множеством в памяти (для иллюстрации; в продакшене используйте постоянное хранилище):

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.

Лучшие практики для надёжных вебхуков

  1. Всегда проверяйте подписи, используя необработанное тело и сравнение с постоянным временем.
  2. Используйте HTTPS для вашего эндпоинта, чтобы предотвратить перехват и подмену данных.
  3. Реализуйте идемпотентность для обработки дублирующихся доставок.
  4. Логируйте всё — входящие данные, подписи и ваши ответы — для отладки.
  5. Следите за сбоями и настройте оповещения при повторяющихся ошибках.
  6. Тестируйте с реальными событиями через панели управления поставщиков или CLI-инструменты.
  7. Обрабатывайте события, пришедшие не по порядку, включая временные метки или порядковые номера в вашу логику.

Сравнение: опрос и вебхуки

АспектОпросВебхуки
ЗадержкаЗависит от интервалаПочти реальное время
Нагрузка на серверВысокая (постоянные запросы)Низкая (только при событиях)
СложностьПросто реализоватьТребуется эндпоинт, безопасность, повторы
НадёжностьВы контролируете повторыПоставщик контролирует повторы

FAQ

Что такое подпись вебхука?

Подпись вебхука — это криптографический хеш (обычно HMAC-SHA256) данных, сгенерированный с использованием общего секрета. Она позволяет получателю убедиться, что запрос пришёл от ожидаемого отправителя и не был изменён.

Как обрабатывать дублирующиеся события вебхуков?

Сделайте обработчик идемпотентным, отслеживая ID событий или используя операции с базой данных, которые естественно идемпотентны. Перед применением изменений проверьте, не было ли событие уже обработано.

Что должен возвращать мой эндпоинт вебхуков?

Возвращайте код состояния 2xx (например, 200 OK), как только получили и проверили событие. Если требуется тяжёлая обработка, подтвердите получение сразу и выполняйте работу асинхронно.

Готовы протестировать данные ваших вебхуков? Используйте наш JSON Formatter, чтобы просмотреть и проверить структуру JSON входящих событий.