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

Backend2026-11-06TryQuickToolBox

Вы интегрировали платежный шлюз или CI/CD-сервис, и теперь вам нужно получать события в реальном времени. Вебхуки — стандартное решение, но с ними связаны две большие проблемы: проверка того, что запрос действительно пришел от ожидаемого отправителя, и обработка сбоев, когда ваш эндпоинт недоступен или медленно отвечает. В этой статье объясняется, как реализовать подписанные payload'ы вебхуков и надежную логику повторных попыток с практическими примерами кода.

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

Вебхук — это определяемый пользователем HTTP-колбэк. Вместо того чтобы ваше приложение опрашивало API для получения обновлений, провайдер отправляет HTTP POST-запрос на указанный вами URL всякий раз, когда происходит событие. Это делает интеграции эффективными и почти в реальном времени.

Однако вебхуки работают по push-модели, а значит, ваш эндпоинт должен быть общедоступным, выдерживать неожиданную нагрузку и справляться с дублирующимися или вредоносными запросами. Здесь на помощь приходят подписи и повторные попытки.

Зачем подписывать payload'ы вебхуков?

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

Наиболее распространенный подход — HMAC (Hash-based Message Authentication Code) с использованием SHA-256. Провайдер вычисляет подпись для исходного тела запроса с помощью секретного ключа и включает ее в заголовок (например, X-Signature). Ваш сервер пересчитывает подпись и сравнивает их.

Реализация проверки HMAC

Вот простой пример на Node.js с использованием Express:

const crypto = require('crypto');
const express = require('express');
const app = express();

// Use raw body for signature verification
app.use(express.raw({ type: 'application/json' }));

app.post('/webhook', (req, res) => {
  const signature = req.headers['x-signature'];
  const secret = process.env.WEBHOOK_SECRET;
  const payload = req.body; // Buffer

  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(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, () => console.log('Webhook server running'));

Ключевые моменты:

Обработка повторных попыток: делаем вебхуки надежными

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

Стратегии повторных попыток

Распространенные паттерны повторных попыток включают:

Большинство провайдеров реализуют что-то вроде: до 5 повторных попыток с экспоненциальной задержкой в течение 24 часов.

Идемпотентность: ключ к безопасным повторным попыткам

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

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

const processedEvents = new Set();

app.post('/webhook', (req, res) => {
  // ... signature verification ...
  const event = JSON.parse(payload);
  if (processedEvents.has(event.id)) {
    return res.status(200).send('Already processed');
  }
  // Process event
  processedEvents.add(event.id);
  res.status(200).send('OK');
});

Для продакшена используйте базу данных с уникальным ограничением на ID события.

Лучшие практики для потребителей вебхуков

Сравнение политик повторных попыток

Политика Плюсы Минусы
Фиксированный интервал Простота реализации Может перегрузить восстанавливающуюся систему
Экспоненциальная задержка Снижает нагрузку во время сбоев Задерживает доставку при временном сбое
Экспоненциальная задержка с джиттером Предотвращает синхронизированные повторные попытки Немного сложнее

Часто задаваемые вопросы

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

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

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

Провайдер должен повторять попытки с экспоненциальной задержкой. Убедитесь, что ваш эндпоинт быстро возвращает 2xx и обрабатывает события асинхронно, чтобы минимизировать влияние простоя.

Можно ли использовать вебхуки без подписи?

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

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