Локалізовано за допомогою ШІ

Підпис запиту

Кожен webhook-запит від Emailit містить заголовок X-Emailit-Signature з підписом HMAC-SHA256 та заголовок X-Emailit-Timestamp з Unix-часовою міткою моменту підписання запиту. Вам слід перевіряти цей підпис, щоб переконатися в автентичності запиту та відсутності втручання в його зміст.

Як це працює

  1. При створенні webhook'а Emailit генерує секретний ключ підпису для цієї кінцевої точки
  2. Для кожної доставки Emailit об'єднує часову мітку та необроблене тіло запиту у форматі {timestamp}.{rawBody}
  3. Обчислюється HMAC-SHA256 хеш цього рядка з використанням вашого секретного ключа підпису
  4. Підпис надсилається в заголовку X-Emailit-Signature, а часова мітка — в X-Emailit-Timestamp
  5. Ваш сервер повторно обчислює підпис і порівнює його зі значенням у заголовку

Перевірка підпису

Для перевірки підпису webhook'а:

  1. Витягніть заголовки X-Emailit-Signature та X-Emailit-Timestamp із запиту
  2. Об'єднайте часову мітку та необроблене тіло запиту у форматі {timestamp}.{rawBody}
  3. Обчисліть HMAC-SHA256 хеш цього рядка, використовуючи ваш секретний ключ підпису webhook'а
  4. Порівняйте обчислений хеш з підписом, використовуючи безпечне порівняння за часом
  5. За бажанням відхиляйте запити, де часова мітка старша за кілька хвилин, щоб захиститися від атак повторного відтворення

Перевірка підпису

import crypto from 'crypto';

function verifyWebhookSignature(rawBody, signature, timestamp, secret) {
  const signedPayload = `${timestamp}.${rawBody}`;
  const computed = crypto
    .createHmac('sha256', secret)
    .update(signedPayload, 'utf8')
    .digest('hex');

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

// У вашому обробнику webhook'а:
const rawBody = req.body; // необроблене тіло запиту як рядок
const signature = req.headers['x-emailit-signature'];
const timestamp = req.headers['x-emailit-timestamp'];
const secret = process.env.WEBHOOK_SIGNING_SECRET;

// Захист від атак повторного відтворення (толерантність 5 хвилин)
const age = Math.floor(Date.now() / 1000) - parseInt(timestamp, 10);
if (age > 300) {
  return res.status(401).send('Запит занадто старий');
}

if (!verifyWebhookSignature(rawBody, signature, timestamp, secret)) {
  return res.status(401).send('Недійсний підпис');
}

Важливі зауваження

  • Підпис обчислюється для {timestamp}.{rawBody} — завжди використовуйте необроблений рядок тіла запиту, а не повторно серіалізовану версію (наприклад, JSON.stringify(req.body)). Відмінності в пробілах або порядку ключів порушать підпис.
  • Завжди використовуйте функцію безпечного порівняння за часом (наприклад, crypto.timingSafeEqual, hmac.compare_digest, hash_equals) для запобігання атакам за часом.
  • Розгляньте можливість відхилення запитів, де X-Emailit-Timestamp старший за кілька хвилин, щоб захиститися від атак повторного відтворення.
  • Тримайте ваш секретний ключ підпису в безпеці — зберігайте його в змінних середовища, а не в вихідному коді.
  • Ви можете знайти ваш секретний ключ підпису webhook'а в панелі керування Emailit у налаштуваннях webhook'а або через API Webhooks.