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.

15 Weekly Downloads
32 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.

From our network Other tools and sites we build