Проверка подписи webhook в n8n: HMAC, timestamp и защита от replay ¶
Обновлено: 2026-05-30
Импортируйте workflow в n8n, задайте WEBHOOK_SIGNING_SECRET и проверьте подпись до подключения CRM.
- Проблема: почему webhook без подписи можно подделать
- Архитектура workflow проверки HMAC-подписи
- Контракт входных данных и headers
- Code Node: HMAC SHA256, timestamp tolerance и replay key
- Готовый workflow JSON: скачать и импортировать
- Пошаговая настройка webhook signature validation
- Тесты через curl: валидная и битая подпись
- Production-риски безопасности webhook
- Полезные ссылки и смежные workflow
- Критерии готовности
Проблема: публичный webhook в n8n может вызвать кто угодно, если у него есть URL. Простая настройка вебхука удобна для интеграции, но без подписи злоумышленник или ошибочный партнёрский скрипт может отправить фальшивую заявку, повторить старый запрос или запустить платную бизнес-логику.
Решение: принимать событие только после проверки HMAC SHA256 по raw body, заголовку timestamp и replay key. Такая схема нужна для CRM, платежей, партнёрских кабинетов и любых сценариев, где передача данных из формы или внешней системы должна быть доказуемой.
Проблема: почему 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 и headers | Production URL, POST, доступ к заголовкам |
| Verify HMAC signature | Считает sha256 и сравнивает подпись | Secret из ENV, timing-safe compare |
| Check replay key | Проверяет уникальность event_id | Postgres 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 ¶
- Создайте секрет подписи и передайте его партнёру через защищённый канал.
- Добавьте в n8n ENV
WEBHOOK_SIGNING_SECRET. - Импортируйте workflow JSON и включите production URL webhook.
- Подключите Postgres/Redis для replay key с уникальным индексом или TTL.
- Проверьте валидный 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.
Критерии готовности ¶
- Невалидная подпись не вызывает ни одной бизнес-ноды.
- Timestamp старше допустимого окна отклоняется.
- Повторный
event_idне обрабатывается второй раз. - Секрет хранится в ENV/credentials и имеет план ротации.
- Команда знает, как проверить подпись через curl и где смотреть rejected-события.
Nodbot настроит HMAC, replay protection, журнал событий и алерты так, чтобы внешний POST не мог сломать CRM или запустить лишние действия.
Обсудить внедрение