Каждый входящий запрос содержит заголовок X-Webhook-Signature — это HMAC SHA-256 подпись тела запроса, созданная с помощью вашего секретного ключа. Зачем проверять: без этого любой может отправить запрос на ваш сервер и притвориться Sasha AI.

Заголовки запросов

ЗаголовокЧто содержит
Content-Typeapplication/json
X-Webhook-SignatureHMAC SHA-256 подпись тела запроса
X-Webhook-IDУникальный ID события
X-Webhook-TimestampВремя отправки, ISO 8601
X-Call-List-IDID колл-листа

Алгоритм проверки

1

Возьмите тело запроса как строку

Получите исходное тело запроса до парсинга JSON
2

Посчитайте HMAC SHA-256

Вычислите HMAC SHA-256 с вашим секретным ключом, результат — hex-строка
3

Сравните с заголовком

Сравните вычисленную подпись с полученной в заголовке X-Webhook-Signature, используя безопасное сравнение строк

Примеры кода

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const computed = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(computed, 'hex'),
    Buffer.from(signature, 'hex')
  );
}

// Пример использования в Express
app.post('/webhook', express.text({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const payload = req.body; // Сырое тело запроса в виде строки
  const secret = 'ваш_секретный_ключ_вебхука';
  
  if (!verifyWebhookSignature(payload, signature, secret)) {
    return res.status(401).send('Недействительная подпись');
  }
  
  const data = JSON.parse(payload);
  console.log('Получено событие:', data.type);
  res.status(200).send('OK');
});
Используйте сравнение строк, устойчивое к time-based атакам (например, crypto.timingSafeEqual() в Node.js, hmac.compare_digest() в Python или hash_equals() в PHP).