Back to Nodes

WebhookHmac

Last updated Nov 4, 2025

Hardened webhook trigger for n8n with HMAC/JWT (JWKS), replay protection, IP policies, mTLS proxy checks, per-IP rate limiting, Redis HA state, and audit export.

4 Weekly Downloads
17 Monthly Downloads

Included Nodes

WebhookHmac
WebhookJwks
WebhookApiKey
WebhookCombo

Description


prokodo โ€“ Empowering Digital Innovations

n8n Secure Webhook

Defense-in-Depth for Public APIs

Production-grade webhook trigger for n8n with HMAC/JWT auth, replay protection, IP policies, mTLS header checks, per-IP rate limiting, Redis HA support, and external audit export โ€” developed by prokodo.

๐Ÿ‡บ๐Ÿ‡ธ Need help implementing secure n8n webhooks & workflows (HMAC, JWT/JWKS, mTLS, Redis, rate limits)?
prokodo โ€” n8n Security & Automation โ†’ click here

๐Ÿ‡ฉ๐Ÿ‡ช Sie brauchen Unterstรผtzung bei sicheren n8n Webhooks & Workflows (HMAC, JWT/JWKS, mTLS, Redis, Rate Limits)?
prokodo โ€” n8n Security & Automation โ†’ hier klicken

npm
License: MIT


โœจ Features

  • ๐Ÿ” Authentication profiles
    • HMAC: raw-body or extended signature (method + path + query + timestamp + nonce + bodyhash)
    • JWT (JWKS): verify Authorization: Bearer <jwt> against your IdP
    • API Key: lightweight static header (best for internal calls)
    • Combo: HMAC + IP allow/deny (CIDR)
  • ๐Ÿ›ก๏ธ Replay protection (timestamp + nonce, one-time use)
  • ๐Ÿงฑ Rate limiting per IP (in-memory token bucket or Redis Lua fixed-window)
  • ๐ŸŒ IP policies (allow/deny by CIDR) for partner/VPN hardening
  • ๐Ÿ“œ mTLS proof via proxy headers (e.g., x-ssl-client-verify: SUCCESS)
  • ๐Ÿงพ Audit export (best-effort HTTPS POST of accept/reject metadata)
  • ๐Ÿงฐ HA/Cluster-ready with optional Redis for rate+replay state
  • โš™๏ธ Strict input hygiene: Content-Type allowlist & body size cap
  • ๐Ÿ” Dual-key rotation window for HMAC secret rollovers
  • ๐Ÿงฉ UX-friendly node options with comprehensive inline help

Internal node name: prokodoSecureWebhook
In the node picker: โ€œSecure Webhookโ€

๐Ÿ†š Why not the default n8n Webhook?

Capability n8n Default Webhook Secure Webhook (this node)
Auth Token in URL or basic header patterns HMAC / JWT (JWKS) / API Key / Combo
Replay protection โŒ Timestamp + Nonce
Rate limiting โŒ Per-IP (memory or Redis)
IP policies โŒ Allow/Deny (CIDR)
mTLS awareness โŒ Proxy header checks
Audit trail Minimal External HTTPS audit events
HA state N/A Redis for nonce & rate data
Signature scope N/A Raw body or Extended (method+path+query+โ€ฆ)

Bottom line: If your webhook is public-facing or exposed to the internet, this hardened trigger offers defense-in-depth that the default webhook does not.

โœ… Requirements

  • Node.js 18+ / 22 LTS
  • n8n โ‰ฅ 1.88 (tested on 1.116.2+)
  • (Optional) Redis for HA/cluster setups
  • For HMAC: a shared secret in n8n credentials (hmacSharedSecretApi)
  • For JWT: a reachable JWKS URL (jwtJwksApi) and optional iss/aud
  • For API Key: header name + value (apiKeyHeaderApi)
  • For mTLS headers: a reverse proxy (Nginx/Envoy/Cloudflare) that terminates TLS and forwards verification headers

Using an older n8n (e.g. 1.88)? It may still work if you align n8n-core / n8n-workflow versions. For best results, upgrade n8n.

๐Ÿ“ฆ Install

Option A โ€” Custom extensions folder (recommended)

Local n8n (not Docker):

# choose your custom folder (default ~/.n8n)
export N8N_CUSTOM_EXTENSIONS=~/.n8n

# install your built package into that folder
npm install --prefix "$N8N_CUSTOM_EXTENSIONS" @prokodo/n8n-nodes-secure-webhook

# start n8n
n8n start

Docker (example Dockerfile):

FROM n8nio/n8n:latest

ENV N8N_CUSTOM_EXTENSIONS=/home/node/.n8n
ENV NODE_PATH=/home/node/.n8n/node_modules

USER node
RUN npm install --prefix /home/node/.n8n @prokodo/n8n-nodes-secure-webhook@latest

After starting n8n, search in the node picker for โ€œSecure Webhookโ€
Internal name: prokodoSecureWebhook

๐Ÿ›  Dev install (build + link locally)

# in this repo
npm ci
npm run build

# make your package linkable
npm link

# link into your n8n custom extensions folder
npm link @prokodo/n8n-nodes-secure-webhook --prefix ~/.n8n

# start n8n with your custom folder
export N8N_CUSTOM_EXTENSIONS=~/.n8n
n8n start

Publish-ready tip: This package publishes compiled JS from dist/ to npm.
You donโ€™t need to commit dist/ to Git. To support installs straight from GitHub, add:

"scripts": {
  "prepare": "npm run build"
}

โ€ฆand commit src/ (not dist/).

๐Ÿ”ง Webhook Endpoint

Path: /secure, method: POST, response mode: onReceived.
Final URL (default n8n): https://<your-host>/webhook/secure

๐Ÿ” Credentials

  • HMAC โ†’ Create credentials hmacSharedSecretApi and set your shared secret.
  • JWT (JWKS) โ†’ Create jwtJwksApi with your JWKS URL (e.g., Auth0/Okta). Optionally set jwtIss/jwtAud in the node.
  • API Key โ†’ Create apiKeyHeaderApi with headerName (e.g., x-api-key) and value.

Pick the Security Profile in the node to match the credential type you configured.

๐Ÿงฉ Node Options (Field Guide)

Core

Security Profile

  • HMAC โ€” safest default for public internet
  • jwks (JWT) โ€” for IdPs like Auth0/Okta/OIDC
  • apikey (Static header) โ€” simple, for internal clients
  • combo โ€” HMAC + IP allow/deny

Shared safety

  • Trusted Proxy Hops โ€” how many X-Forwarded-For hops you trust (e.g., Cloudflare + Nginx = 2)
  • Max Body (bytes) โ€” reject large payloads early (default 1 MB)
  • Allowed Content-Types โ€” comma-separated allowlist (default: application/json,application/x-www-form-urlencoded)
  • Rate Limit / Minute (per IP) โ€” default 120. With Redis itโ€™s cluster-wide; otherwise per process

Replay Protection

  • Timestamp Header (x-timestamp) โ€” UNIX seconds, 10 digits
  • Nonce Header (x-nonce) โ€” unique per request (UUID v4 or 16โ€“32B hex)
  • Max Skew (sec) โ€” default 300; tune based on client/server time sync

HMAC (recommended/combo)

  • HMAC Signature Header (x-signature) โ†’ value like sha256=<hex>
  • HMAC Algorithm โ€” sha256 (default) or sha512

Signature Mode

  • body โ€” sign raw body with HMAC (compatibility)
  • extended โ€” method + path + canonical query + timestamp + nonce + bodyhash (strongest)

Dual-Key Rotation

  • Previous Secret โ€” optional, for rolling changes
  • Dual-Key Window (min) โ€” accept previous secret briefly during rotation

IP Policies (combo)

  • IP Allow (CIDR) โ€” only allow these ranges
  • IP Deny (CIDR) โ€” block these ranges first (deny takes precedence)

JWT (JWKS)

  • JWT Issuer (iss) โ€” optional but recommended
  • JWT Audience (aud) โ€” optional but recommended
  • JWKS Cooldown (ms) โ€” min interval between JWKS fetches (default 60s)
  • JWKS Fetch Timeout (ms) โ€” network timeout (default 3s)

mTLS via Proxy

  • Require mTLS Header โ€” expect proof from your proxy (e.g., x-ssl-client-verify: SUCCESS)
  • Verify Header โ€” default x-ssl-client-verify
  • Subject Header โ€” default x-ssl-client-dn
  • Subject Allow Regex โ€” optional (^CN=partner\.), fail-closed on bad regex

Audit Export

  • Audit Export โ€” none or https
  • Audit Endpoint URL โ€” HTTPS receiver of compact JSON events
  • Audit Bearer Token โ€” optional Authorization: Bearer <token>

๐Ÿ” Client Integration Scenarios

1) HMAC (Recommended)

Server:

  • Create credentials hmacSharedSecretApi with your shared secret.
  • Profile: Empfohlen (HMAC).
  • Keep default timestamp/nonce headers unless you need custom names.

Client (Node.js, extended mode):

  import crypto from 'node:crypto';

  const url = 'https://your-n8n.example/secure?foo=bar';
  const body = JSON.stringify({ hello: 'world' });
  const ts = Math.floor(Date.now()/1000).toString();
  const nonce = crypto.randomUUID();
  const algo = 'sha256';

  // body hash
  const bodyHash = crypto.createHash(algo).update(Buffer.from(body)).digest('hex');

  // canonicalize query
  const { URL } = await import('node:url');
  const u = new URL(url);
  const query = [...u.searchParams.entries()]
    .sort(([a],[b]) => a.localeCompare(b))
    .map(([k,v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
    .join('&');

  // base string
  const base = ['POST', u.pathname, query, ts, nonce, bodyHash].join('\n');

  // hmac
  const signatureHex = crypto.createHmac(algo, process.env.WEBHOOK_SECRET).update(base).digest('hex');

  const resp = await fetch(url, {
    method: 'POST',
    headers: {
      'content-type': 'application/json',
      'x-timestamp': ts,
      'x-nonce': nonce,
      'x-signature': `${algo}=${signatureHex}`,
    },
    body,
  });
  console.log(resp.status, await resp.text());

curl (body mode):

TS=$(date +%s)
NONCE=$(uuidgen)
BODY='{"hello":"world"}'
SIG_HEX=$(printf %s "$BODY" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" -binary | xxd -p -c 256)

curl -X POST "https://your-n8n.example/secure" \
  -H "content-type: application/json" \
  -H "x-timestamp: $TS" \
  -H "x-nonce: $NONCE" \
  -H "x-signature: sha256=$SIG_HEX" \
  --data "$BODY"

2) JWT (JWKS)

Server:

  • Create credentials jwtJwksApi with your JWKS URL.
  • Profile: JWT (JWKS). Optionally set jwtIss/jwtAud.

Client:

curl -X POST "https://your-n8n.example/secure" \
  -H "content-type: application/json" \
  -H "authorization: Bearer $JWT" \
  --data '{"ok":true}'

3) API Key (Internal)

Server:

  • Create credentials apiKeyHeaderApi with:
    • headerName: e.g., x-api-key
    • value: your static secret
  • Profile: Einfacher API Key.

Client:

curl -X POST "https://your-n8n.example/secure" \
  -H "content-type: application/json" \
  -H "x-api-key: YOUR_SECRET" \
  --data '{"ok":true}'

4) Combo (HMAC + IP Allow/Deny)

Server:

  • Profile: Kombiniert.
  • Configure HMAC and IP Allow/Deny CIDRs.

Client: Same as HMAC, but requests must originate from allowed networks.


๐Ÿ” HMAC Secret Rotation (Playbook)

  1. Set Previous Secret = current secret; deploy the node with a Dual-Key Window (e.g., 60 min).
  2. Distribute the new secret to clients and switch their signing key.
  3. After the window elapses and traffic is fully migrated, clear Previous Secret.

๐Ÿงญ Reverse Proxy (mTLS Header) Example

Nginx:

ssl_verify_client on;          # request client cert
ssl_client_certificate /etc/nginx/ca_chain.pem;

location /secure {
  proxy_pass http://n8n:5678/secure;
  proxy_set_header x-ssl-client-verify $ssl_client_verify; # "SUCCESS" on pass
  proxy_set_header x-ssl-client-dn     $ssl_client_s_dn;
}

Node settings

  • Enable Require mTLS Header
  • Keep defaults or adjust header names
  • Optionally Subject Allow Regex like ^CN=partner\.

๐Ÿงฎ Rate Limiting Details

  • In-memory (default): token bucket (burst friendly), per process
  • Redis (optional): fixed window via atomic Lua (EVALSHA), cluster-safe

Fields

  • Rate Limit / Minute (per IP): max hits per 60s
  • Redis aktivieren / URL / Namespace: shared state across nodes
  • Returns 429 with Retry-After when exceeded

๐Ÿ—‚ Audit Export (Best-Effort)

If enabled, the node POSTs a compact JSON event to your endpoint:

{
  "ts": "2025-01-01T10:00:00.000Z",
  "rid": "9f2aโ€ฆ",
  "ip": "203.0.113.42",
  "profile": "recommended",
  "outcome": "accept",
  "reason": "bad_signature"
}
  • No request payload is sent (metadata only).
  • Failures are swallowed to avoid breaking the main request path.

๐Ÿ”’ Security Model (At a Glance)

  • Input hygiene: strict Content-Type allowlist, body size cap
  • Auth: HMAC/JWT/API Key; extended HMAC defends against path/query tampering
  • Replay: timestamp + nonce (short TTL); requires client time sync
  • Abuse: per-IP rate limiting; optional IP allow/deny (CIDR)
  • Transport: terminate TLS at edge; optional mTLS forwarding
  • State: Redis for HA (nonces & rate); memory fallback for simple installs
  • Audit: external events for SIEM/forensics (no payload leakage)

๐Ÿงช Testing Checklist

  • โœ… Valid Content-Type and within Max Body
  • โœ… Include x-timestamp (10-digit seconds) within Max Skew
  • โœ… Include x-nonce (unique per request)
  • โœ… HMAC: correct signature (body/extended mode)
  • โœ… JWT: valid token & reachable JWKS; iss/aud match if configured
  • โœ… Rate limiting: send > rateMax/min to observe 429 + Retry-After
  • โœ… IP policy behavior (allow/deny)
  • โœ… (Optional) mTLS headers forwarding via proxy
  • โœ… Observe x-request-id and audit events

๐Ÿงฏ Troubleshooting

  • 415 unsupported_content_type โ†’ Add clientโ€™s type to Allowed Content-Types
  • 413 payload_too_large โ†’ Increase Max Body or shrink payload
  • 401 bad_or_missing_timestamp โ†’ Ensure x-timestamp within Max Skew
  • 401 replay_detected โ†’ Use a fresh x-nonce for every request
  • 401 bad_signature / bad_signature_algo โ†’ Check algorithm/header and base string mode
  • 401 invalid_token / missing_bearer โ†’ Send Authorization: Bearer <jwt>; verify IdP/JWKS
  • 401 mtls_* โ†’ Confirm proxy sets verification headers
  • 403 ip_denied / ip_not_allowed โ†’ Adjust IP Deny/Allow CIDRs
  • 429 rate_limited โ†’ Decrease traffic, raise threshold, or enable Redis

๐Ÿง  Best Practices

  • Prefer HMAC extended mode for public endpoints.
  • Keep Max Skew low (120โ€“300s) and ensure NTP on all systems.
  • Rotate HMAC secrets regularly; use Dual-Key Window during rollouts.
  • For HA/Cluster, enable Redis (protect with TLS/network policy).
  • Terminate TLS at the edge; add mTLS for partner integrations.
  • Use precise CIDRs in IP allow lists (avoid wildcards).
  • Log/trace with x-request-id end-to-end.

๐Ÿ™Œ Contributing

PRs welcome!

npm ci
npm run build

Open a PR with what changed and how to test it.

๐Ÿ“„ License

This library is published under MIT.

ยฉ 2025 prokodo.
Visit us at prokodo.com.