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

Backend2026-10-18TryQuickToolBox

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

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

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

Вебхуки — это определяемые пользователем HTTP-обратные вызовы. Когда в исходной системе происходит событие (например, обрабатывается платеж), она отправляет HTTP POST-запрос на указанный вами URL с данными, содержащими детали события. Это позволяет осуществлять связь между сервисами в реальном времени без опроса.

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

Защита вебхуков с помощью подписанных данных

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

Как работает подпись HMAC

  1. Отправитель и получатель используют общий секретный ключ (желательно длинную случайную строку).
  2. Для каждого вебхука отправитель вычисляет HMAC тела запроса с использованием секрета и включает его в заголовок (например, X-Signature).
  3. Получатель вычисляет тот же HMAC для полученного тела и сравнивает его со значением заголовка. Если они совпадают, запрос подлинный.

Крайне важно подписывать исходное тело запроса, а не его разобранную версию, чтобы избежать расхождений из-за различий в сериализации JSON.

Пример: подпись данных в Node.js

const crypto = require('crypto');

function signPayload(payload, secret) {
  return crypto.createHmac('sha256', secret).update(payload).digest('hex');
}

// На стороне отправителя:
const payload = JSON.stringify({ event: 'payment.success', amount: 100 });
const signature = signPayload(payload, 'your-secret-key');
// Включите подпись в заголовок: X-Signature: signature

Проверка подписи на получателе

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

const crypto = require('crypto');

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

// В вашей конечной точке вебхука:
app.post('/webhook', (req, res) => {
  const signature = req.headers['x-signature'];
  const payload = req.rawBody; // исходное тело в виде строки
  if (!verifySignature(payload, signature, 'your-secret-key')) {
    return res.status(401).send('Invalid signature');
  }
  // Обработайте событие
  res.status(200).send('OK');
});

Примечание: Всегда используйте HTTPS для предотвращения атак типа «человек посередине». Также рассмотрите возможность включения временной метки в подписанные данные для предотвращения повторных атак.

Реализация повторных попыток с экспоненциальной задержкой

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

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

Пример: логика повторных попыток в Node.js

async function deliverWebhook(url, payload, secret, maxRetries = 5) {
  const signature = signPayload(payload, secret);
  let attempt = 0;
  while (attempt < maxRetries) {
    try {
      const response = await fetch(url, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'X-Signature': signature },
        body: payload,
      });
      if (response.ok) return;
      throw new Error(`HTTP ${response.status}`);
    } catch (error) {
      attempt++;
      if (attempt >= maxRetries) throw error;
      const delay = Math.min(1000 * 2 ** attempt + Math.random() * 1000, 30000);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

Эта функция повторяет попытки до 5 раз с экспоненциальной задержкой и джиттером, ограничивая задержку 30 секундами. Настройте параметры в соответствии с вашими потребностями.

Обеспечение идемпотентности

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

// На получателе:
const processedEvents = new Set();

app.post('/webhook', (req, res) => {
  const { id } = req.body;
  if (processedEvents.has(id)) {
    return res.status(200).send('Already processed');
  }
  // Обработайте событие
  processedEvents.add(id);
  res.status(200).send('OK');
});

В производственной среде используйте постоянное хранилище (например, Redis или базу данных) для отслеживания обработанных идентификаторов между перезапусками.

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

Аспект Неподписанные Подписанные (HMAC)
Безопасность Любой может подделать запросы Только владелец секрета может создать действительную подпись
Целостность Данные могут быть изменены Изменение делает подпись недействительной
Сложность Низкая Средняя (управление ключами, проверка)
Вариант использования Внутренние доверенные сети Любой публичный вебхук

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

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

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

Сколько раз следует повторять неудачный вебхук?

Нет фиксированного числа, но обычно практикуется 3-5 повторных попыток с экспоненциальной задержкой. После этого пометьте доставку как неудачную и уведомите вашу команду. Учитывайте критичность события: уведомления о платежах могут требовать большего числа повторных попыток, чем некритичные обновления.

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

Никогда не используйте вебхуки по простому HTTP в производственной среде. HTTPS шифрует данные при передаче, предотвращая подслушивание и атаки типа «человек посередине». Даже с подписями HTTP раскрывает конфиденциальные данные.

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