Tips > Data, APIs & Webhooks

Secure Webhooks with Header Auth and Rotating Tokens

The default n8n webhook URL is public.

TipIntermediate2 min read

The default n8n webhook URL is public. Anyone who discovers it can trigger your workflow. Use Header Authentication with strong tokens, and rotate those tokens periodically. For critical webhooks, layer in IP allowlisting or HMAC signature validation.

Real-world example: A CRM vendor sends lead notifications to your webhook. Secure it with a shared secret in a custom header, and rotate the token quarterly.

Webhook node configuration:

{
  "authentication": "headerAuth",
  "path": "crm/leads"
}
```text
Create an **Header Auth credential** in n8n:

```text
Name: CRM Lead Webhook Auth
Header Name: X-Webhook-Secret
Header Value: wh_prod_a8f3k29d4m5n6p7q8r9s0t1u2v3w4x5y
```text
For HMAC signature validation (used by Stripe, GitHub, Slack), add a Code node:

```javascript
// Code node: "Verify HMAC Signature"
const crypto = require('crypto');

const secret = 'whsec_your_signing_secret_here';
const signature = $input.first().json.headers['x-hub-signature-256'];
const payload = $input.first().json.rawBody;

const expected = 'sha256=' + crypto
  .createHmac('sha256', secret)
  .update(payload)
  .digest('hex');

// Use constant-time comparison to prevent timing attacks
const signatureBuffer = Buffer.from(signature || '', 'utf8');
const expectedBuffer = Buffer.from(expected, 'utf8');

if (signatureBuffer.length !== expectedBuffer.length ||
    !crypto.timingSafeEqual(signatureBuffer, expectedBuffer)) {
  return [{
    json: {
      valid: false,
      error: 'Invalid signature',
      statusCode: 401
    }
  }];
}

return [{
  json: {
    valid: true,
    data: $input.first().json.body,
    statusCode: 200
  }
}];
```text
> **Warning: Enable Raw Body and store secrets securely**
>
> This code requires the Webhook node's **Raw Body** option to be enabled so that `rawBody` is available. Never use `JSON.stringify()` on the parsed body for signature verification -- the stringified output may differ from the original bytes. Never hard-code secrets in Code nodes in production. Use n8n credentials or environment variables (`$env.WEBHOOK_SECRET`) to store signing keys.

This prevents unauthorized triggers. Rotating tokens quarterly limits the blast radius of a leaked secret.

**Related:** [Set a Unique Encryption Key and Back It Up](../security-best-practices/01-set-a-unique-encryption-key-and-back-it-up.md) | [Use the HTTP Request Node as a Universal Connector](../integration-patterns/01-use-the-http-request-node-as-a-universal-connector.md)

Want this running in your stack?

I build production n8n and Cloudflare automation for teams — the same engineering behind HarperFlow. Fixed-price, escrow-protected, US-based.