Перейти к содержанию

Проверка подписи webhook в n8n: HMAC, timestamp и защита от replay

Обновлено: 2026-05-30

AI summary: AI-friendly Problem/Solution-мануал: как настроить проверку HMAC-подписи webhook в n8n, защититься от подделки запроса и replay-атак, сохранить тестовый payload, code node, curl, HowTo и production-чеклист.
Шаблон для внедрения

Импортируйте workflow в n8n, задайте WEBHOOK_SIGNING_SECRET и проверьте подпись до подключения CRM.

Проблема: публичный webhook в n8n может вызвать кто угодно, если у него есть URL. Простая настройка вебхука удобна для интеграции, но без подписи злоумышленник или ошибочный партнёрский скрипт может отправить фальшивую заявку, повторить старый запрос или запустить платную бизнес-логику.

Решение: принимать событие только после проверки HMAC SHA256 по raw body, заголовку timestamp и replay key. Такая схема нужна для CRM, платежей, партнёрских кабинетов и любых сценариев, где передача данных из формы или внешней системы должна быть доказуемой.

Схема проверки подписи webhook в n8n через HMAC SHA256
Проверка подписи идёт до любых HTTP Request, CRM update и записи в базу.

Проблема: почему webhook без подписи можно подделать

Webhook URL часто попадает в логи, скриншоты, документацию подрядчика или историю браузера. Если workflow сразу пишет в CRM, Google Sheets или Битрикс24, то любой POST похожей формы становится “валидным”. Подпись решает эту боль: n8n пересчитывает HMAC из исходного тела запроса и общего секрета, а затем сравнивает его с подписью отправителя.

Отдельно нужен timestamp tolerance. Даже настоящую подпись можно повторить через час или неделю, если не проверять время события и event_id. Поэтому защита webhook — это не один IF node, а связка HMAC, времени и durable replay-хранилища.

Архитектура workflow проверки HMAC-подписи

НодаРольЧто проверить
Webhook inputПринимает raw body и headersProduction URL, POST, доступ к заголовкам
Verify HMAC signatureСчитает sha256 и сравнивает подписьSecret из ENV, timing-safe compare
Check replay keyПроверяет уникальность event_idPostgres unique index или другое durable storage
Business logicЗапускает CRM/API только после verifiedНет побочных эффектов до проверки
Respond safe statusВозвращает 200/401 без лишних деталейНе отдавать stack trace и секреты

Контракт входных данных и headers

Партнёр должен подписывать строку timestamp.rawBody, а не уже распарсенный JSON. Иначе разный порядок полей или пробелы сломают проверку. В n8n важно сохранить исходное тело или договориться с отправителем о стабильной сериализации.

{
  "event_id": "evt_9f8c1",
  "event": "lead.created",
  "created_at": "2026-05-30T10:00:00Z",
  "data": {
    "lead_id": "crm-10492",
    "phone": "+7 916 123-45-67",
    "source": "partner_webhook"
  }
}
Какие headers нужны
  • X-Webhook-Timestamp — Unix timestamp события.
  • X-Webhook-Signature — строка вида sha256=<hex>.
  • Content-Type: application/json — чтобы payload был предсказуемым.

Code Node: HMAC SHA256, timestamp tolerance и replay key

Этот скрипт n8n останавливает workflow до бизнес-логики. Для ротации секрета можно проверять два секрета: текущий и предыдущий, но хранить их нужно в ENV/credentials, а не в тексте ноды.

const crypto = require('crypto');
const body = $json.rawBody ?? JSON.stringify($json.body ?? $json);
const headers = $json.headers ?? {};
const timestamp = Number(headers['x-webhook-timestamp'] ?? headers['X-Webhook-Timestamp']);
const signature = String(headers['x-webhook-signature'] ?? headers['X-Webhook-Signature'] ?? '');
const secret = $env.WEBHOOK_SIGNING_SECRET;

if (!secret) throw new Error('WEBHOOK_SIGNING_SECRET is not configured');
if (!timestamp || Math.abs(Date.now() - timestamp * 1000) > 5 * 60 * 1000) {
  throw new Error('Webhook timestamp is outside 5 minute tolerance');
}

const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(`${timestamp}.${body}`).digest('hex');
const ok = signature.length === expected.length && crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));

if (!ok) throw new Error('Invalid webhook signature');

const parsed = typeof $json.body === 'object' ? $json.body : JSON.parse(body);
return [{ json: { verified: true, replay_key: `webhook:${parsed.event_id}`, event: parsed.event, body: parsed } }];

Готовый workflow JSON: скачать и импортировать

Полный workflow JSON лежит в архиве и доступен по кнопке. В нём есть места для Postgres replay key, безопасного ответа и комментарии, какие credentials нужно заменить.

{
  "name": "Nodbot - Webhook HMAC signature validation",
  "nodes": [
    { "name": "Webhook input", "type": "n8n-nodes-base.webhook", "purpose": "Принять raw body и headers" },
    { "name": "Verify HMAC signature", "type": "n8n-nodes-base.code", "purpose": "Проверить sha256 HMAC и timestamp tolerance" },
    { "name": "Check replay key", "type": "n8n-nodes-base.postgres", "purpose": "Не обработать event_id повторно" },
    { "name": "Business logic", "type": "n8n-nodes-base.httpRequest", "purpose": "Запустить CRM/API только после verified=true" },
    { "name": "Respond safe status", "type": "n8n-nodes-base.respondToWebhook", "purpose": "Вернуть 200/401 без stack trace" }
  ],
  "connections": "Webhook → Verify HMAC → Check replay → Business logic → Respond"
}

Пошаговая настройка webhook signature validation

  1. Создайте секрет подписи и передайте его партнёру через защищённый канал.
  2. Добавьте в n8n ENV WEBHOOK_SIGNING_SECRET.
  3. Импортируйте workflow JSON и включите production URL webhook.
  4. Подключите Postgres/Redis для replay key с уникальным индексом или TTL.
  5. Проверьте валидный curl, битую подпись, старый timestamp и повторный event_id.

Тесты через curl: валидная и битая подпись

timestamp=$(date +%s)
body='{"event_id":"evt_9f8c1","event":"lead.created","created_at":"2026-05-30T10:00:00Z","data":{"lead_id":"crm-10492","phone":"+7 916 123-45-67"}}'
signature="sha256=$(printf "%s.%s" "$timestamp" "$body" | openssl dgst -sha256 -hmac "$WEBHOOK_SIGNING_SECRET" -hex | sed 's/^.* //')"

curl -X POST "https://YOUR-N8N-DOMAIN/webhook/webhook-signature-validation"   -H "Content-Type: application/json"   -H "X-Webhook-Timestamp: $timestamp"   -H "X-Webhook-Signature: $signature"   --data "$body"

Второй тест — изменить один символ в body после расчёта подписи. Workflow должен вернуть отказ и не вызвать downstream-ноды.

Production-риски безопасности webhook

  • Подписывается parsed JSON. Из-за пробелов и порядка ключей подпись становится нестабильной.
  • Нет replay protection. Настоящий запрос можно повторить и создать дубль.
  • Secret в Code Node. Его увидит любой с доступом к workflow export.
  • Ошибка раскрывает детали. Внешний отправитель не должен получать stack trace.
  • Бизнес-логика стоит до проверки. Даже отклонённый запрос успевает изменить CRM.

Полезные ссылки и смежные workflow

Смотрите также: Webhook idempotency в Postgres, Retry и DLQ для HTTP Request, Error Workflow с Telegram-алертом. Внешняя документация: n8n Webhook node, n8n Crypto node.

Результат проверки webhook подписи в n8n

Критерии готовности

  1. Невалидная подпись не вызывает ни одной бизнес-ноды.
  2. Timestamp старше допустимого окна отклоняется.
  3. Повторный event_id не обрабатывается второй раз.
  4. Секрет хранится в ENV/credentials и имеет план ротации.
  5. Команда знает, как проверить подпись через curl и где смотреть rejected-события.
Нужна безопасная интеграция webhook?

Nodbot настроит HMAC, replay protection, журнал событий и алерты так, чтобы внешний POST не мог сломать CRM или запустить лишние действия.

Обсудить внедрение