Webhooks: подписанные payload и повторные попытки

Backend2026-10-09TryQuickToolBox

Вы только что интегрировали платежный шлюз, и теперь вам нужно обновлять базу данных при успешной оплате. Опрос API каждые несколько секунд — это расточительно и медленно. Вебхуки решают эту проблему, отправляя события на ваш сервер по мере их возникновения. Но без должной безопасности и надежности вебхуки могут стать источником ошибок и уязвимостей. Эта статья объясняет, как правильно реализовать вебхуки, уделяя особое внимание подписанным payload и повторным попыткам.

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

Вебхук — это HTTP-колбэк: в сервисе происходит событие, и этот сервис отправляет HTTP POST-запрос на указанный вами URL. Payload обычно содержит JSON-данные о событии. Например, когда клиент завершает оплату, Stripe отправляет событие charge.succeeded на ваш эндпоинт.

Вебхуки работают по принципу push, поэтому они снижают задержку и нагрузку на сервер по сравнению с опросом. Однако они создают проблемы: как узнать, что запрос подлинный? Что, если ваш сервер недоступен, когда срабатывает событие?

Почему важны подписанные payload

Любой может отправить HTTP POST на ваш вебхук-эндпоинт. Без проверки злоумышленник может подделать события, что приведет к несанкционированным действиям, например, отметке заказа как оплаченного. Подписанные payload решают эту проблему, включая криптографическую подпись, которую можете сгенерировать только отправитель и вы.

Самый распространенный метод — HMAC (Hash-based Message Authentication Code) с общим секретом. Отправитель вычисляет хеш payload с использованием секрета и включает его в заголовок. Вы пересчитываете хеш и сравниваете. Если они совпадают, запрос подлинный.

Как проверить подписанный payload

Вот пошаговый подход с использованием Node.js и Express:

  1. Получите необработанное тело запроса в виде строки. Не парсите его как JSON до проверки, так как парсинг может изменить последовательность байтов.
  2. Извлеките подпись из заголовка (например, X-Signature).
  3. Вычислите HMAC, используя ваш секрет и необработанное тело.
  4. Сравните вычисленную подпись с полученной, используя сравнение с постоянным временем, чтобы предотвратить атаки по времени.
const crypto = require('crypto');

function verifySignature(rawBody, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
  const signature = req.headers['x-signature'];
  if (!verifySignature(req.body, signature, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }
  const event = JSON.parse(req.body);
  // Process event
  res.status(200).send('OK');
});

Всегда используйте надежный секрет и периодически меняйте его. Никогда не раскрывайте его в клиентском коде.

Реализация повторных попыток с backoff

Вебхуки доставляются через интернет, поэтому сбои случаются. Ваш сервер может быть недоступен, или сеть может дать сбой. Надежная система вебхуков повторяет неудачные доставки с экспоненциальной задержкой.

Обычно отправитель повторяет попытку, если не получает ответ 2xx в течение таймаута (например, 5 секунд). График повторных попыток может быть: немедленно, затем через 1 минуту, 5 минут, 30 минут, 2 часа и т.д., до максимального количества попыток (например, 5).

Как получатель, вы должны быстро отвечать, чтобы избежать таймаутов. Подтвердите получение с помощью 200 OK и обрабатывайте событие асинхронно.

Обработка идемпотентности

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

async function processEvent(event) {
  const { id, type, data } = event;
  if (await db.processedEvents.findOne({ id })) {
    return; // Already processed
  }
  await db.processedEvents.insertOne({ id });
  // Handle event based on type
}

Используйте транзакцию базы данных, чтобы гарантировать, что событие помечается как обработанное только после успешной обработки.

Лучшие практики безопасности вебхуков

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

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

FAQ

Как тестировать вебхуки локально?

Используйте инструмент вроде ngrok, чтобы открыть ваш локальный сервер для интернета. Настройте провайдера вебхуков на отправку событий на ваш URL ngrok.

Что делать, если мой сервер недоступен, когда отправляется вебхук?

Отправитель должен повторить попытку с backoff. Убедитесь, что ваш сервер высокодоступен и быстро отвечает, чтобы избежать таймаутов.

Могу ли я использовать асимметричные подписи вместо HMAC?

Да, некоторые провайдеры используют подписи RSA или ECDSA. Вы проверяете их с помощью их открытого ключа. HMAC проще, но требует общего секрета.

При отладке payload вебхуков полезно форматировать JSON для удобочитаемости. Попробуйте наш JSON Formatter, чтобы красиво отформатировать и проверить payload вебхуков мгновенно.