# How to Secure Webhooks in n8n

- Tool: n8n
- Difficulty: Intermediate
- Time required: 20-30 minutes
- Compatibility: n8n 1.0+, all deployment methods
- Last updated: March 2026

## TL;DR

Unsecured n8n webhooks can be triggered by anyone who knows the URL, exposing your workflows to abuse and data injection. Secure them by enabling authentication (Basic Auth, Header Auth, or JWT), validating HMAC signatures from providers like GitHub and Stripe, and adding IP allowlisting with a reverse proxy. These layers ensure only authorized services can trigger your workflows.

## Why Webhook Security Matters in n8n

By default, n8n webhooks accept requests from any source without authentication. In production, this means anyone who discovers or guesses your webhook URL can trigger your workflow, potentially injecting malicious data, consuming resources, or manipulating downstream systems. This tutorial covers three layers of webhook security: built-in authentication options in the Webhook node, HMAC signature verification for provider webhooks, and IP allowlisting at the network level. Implementing at least two of these layers provides strong protection.

## Before you start

- A running n8n instance with at least one webhook workflow
- Access to the n8n editor
- Basic understanding of HTTP headers and authentication
- A reverse proxy (Nginx or Caddy) for IP allowlisting (optional but recommended)

## Step-by-step guide

### 1. Enable Basic Auth on a webhook

Open your Webhook node and expand the Authentication section. Select 'Basic Auth' from the dropdown. Enter a username and password. When Basic Auth is enabled, every request to the webhook must include an Authorization header with the base64-encoded credentials. Requests without valid credentials receive a 401 Unauthorized response. This is the simplest authentication method and works well for internal integrations.

```
# Send a request with Basic Auth using curl
curl -X POST https://n8n.yourdomain.com/webhook/my-endpoint \
  -u "myuser:mysecretpassword" \
  -H "Content-Type: application/json" \
  -d '{"event": "order_created", "order_id": 123}'
```

**Expected result:** Requests with valid credentials trigger the workflow. Requests without credentials receive a 401 response.

### 2. Use Header Auth for API key-style security

In the Webhook node's Authentication section, select 'Header Auth'. Set the Header Name (for example, X-API-Key) and the Header Value (your secret key). n8n checks every incoming request for this header and value. This method is common for service-to-service integrations where the calling service can include custom headers.

```
# Send a request with Header Auth
curl -X POST https://n8n.yourdomain.com/webhook/my-endpoint \
  -H "X-API-Key: your_secret_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"event": "payment_received", "amount": 49.99}'
```

**Expected result:** Only requests with the correct header name and value trigger the workflow.

### 3. Verify HMAC signatures from webhook providers

Services like GitHub, Stripe, and Shopify sign their webhook payloads with an HMAC-SHA256 signature. The signature is sent in a header (for example, X-Hub-Signature-256 for GitHub). Add a Code node immediately after the Webhook node to verify the signature before processing the payload. If the signature does not match, stop the workflow and return an error.

```
const crypto = require('crypto');

// Get the raw body and signature from the webhook
const rawBody = JSON.stringify($input.first().json.body);
const signature = $input.first().json.headers['x-hub-signature-256'];
const secret = 'your_webhook_secret_here';

// Calculate expected signature
const expectedSignature = 'sha256=' + crypto
  .createHmac('sha256', secret)
  .update(rawBody)
  .digest('hex');

// Compare signatures using timing-safe comparison
const isValid = crypto.timingSafeEqual(
  Buffer.from(signature || ''),
  Buffer.from(expectedSignature)
);

if (!isValid) {
  throw new Error('Invalid webhook signature');
}

return $input.all();
```

**Expected result:** Only requests with a valid HMAC signature pass through to the rest of the workflow.

### 4. Add IP allowlisting with a reverse proxy

Configure your reverse proxy to only forward requests to the webhook path from known IP addresses. This is especially useful for provider webhooks where the sending service publishes their IP ranges (GitHub, Stripe, etc.). Add an allow list in your Nginx or Caddy configuration for the /webhook/ path.

```
# Nginx configuration for IP allowlisting on webhook paths
location /webhook/ {
    # GitHub webhook IP ranges (check GitHub docs for current list)
    allow 140.82.112.0/20;
    allow 185.199.108.0/22;
    allow 192.30.252.0/22;
    deny all;

    proxy_pass http://localhost:5678;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}
```

**Expected result:** Only requests from allowed IP addresses reach the n8n webhook. All other IPs receive a 403 Forbidden response.

### 5. Use a random, unguessable webhook path

As a baseline security measure, use a long random string as your webhook path instead of a descriptive name. While this is not a replacement for authentication, it makes it much harder for attackers to discover your webhook URL through guessing or scanning. Generate a UUID or random string and use it as the path.

```
# Generate a random webhook path
openssl rand -hex 16
# Example output: a3f8b2c1d4e5f6a7b8c9d0e1f2a3b4c5

# Use it as the webhook path: /webhook/a3f8b2c1d4e5f6a7b8c9d0e1f2a3b4c5
```

**Expected result:** The webhook URL contains a random path that cannot be guessed by scanning.

### 6. Combine multiple security layers

For maximum security, combine several of these techniques. Use Header Auth on the Webhook node for authentication, add HMAC verification in a Code node for provider webhooks, set up IP allowlisting in your reverse proxy, and use a random webhook path. Each layer blocks a different type of attack, providing defense in depth.

**Expected result:** Your webhook is protected by multiple independent security layers, each blocking different attack vectors.

## Complete code example

File: `verify-hmac-signature.js`

```javascript
// Code node: Verify HMAC-SHA256 webhook signature
// Supports GitHub (X-Hub-Signature-256), Stripe (Stripe-Signature),
// and generic HMAC signatures
//
// Place this node immediately after the Webhook node

const crypto = require('crypto');

// Configuration — update these for your provider
const WEBHOOK_SECRET = 'your_webhook_secret_here';
const SIGNATURE_HEADER = 'x-hub-signature-256'; // GitHub
// const SIGNATURE_HEADER = 'stripe-signature';   // Stripe
// const SIGNATURE_HEADER = 'x-signature';         // Custom

// Get the request data
const headers = $input.first().json.headers;
const body = $input.first().json.body;
const receivedSignature = headers[SIGNATURE_HEADER];

if (!receivedSignature) {
  throw new Error(`Missing signature header: ${SIGNATURE_HEADER}`);
}

// Calculate expected HMAC signature
const rawBody = typeof body === 'string' ? body : JSON.stringify(body);
const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET);
hmac.update(rawBody);
const calculatedSignature = 'sha256=' + hmac.digest('hex');

// Timing-safe comparison to prevent timing attacks
try {
  const isValid = crypto.timingSafeEqual(
    Buffer.from(receivedSignature),
    Buffer.from(calculatedSignature)
  );

  if (!isValid) {
    throw new Error('Signature verification failed');
  }
} catch (error) {
  if (error.message === 'Signature verification failed') throw error;
  // Buffer length mismatch also means invalid signature
  throw new Error('Signature verification failed: length mismatch');
}

// Signature is valid — pass the data through
return [{
  json: {
    verified: true,
    payload: body
  }
}];
```

## Common mistakes

- **Leaving webhooks open without any authentication in production** — undefined Fix: Enable at least Basic Auth or Header Auth on every production webhook. It takes 30 seconds to configure.
- **Using string comparison (===) instead of timingSafeEqual for HMAC verification** — undefined Fix: Always use crypto.timingSafeEqual to prevent timing attacks that can leak the secret one character at a time.
- **Hardcoding the webhook secret in the Code node** — undefined Fix: Store the secret as an n8n environment variable and access it with {{ $env.WEBHOOK_SECRET }} or process.env.WEBHOOK_SECRET.
- **Not updating IP allowlists when the provider changes their IP ranges** — undefined Fix: Check provider documentation periodically. Set a calendar reminder to review IP ranges quarterly.

## Best practices

- Never leave production webhooks without at least one authentication layer
- Use Header Auth or Basic Auth as the minimum security for all webhooks
- Verify HMAC signatures for provider webhooks (GitHub, Stripe, Shopify) to ensure payload integrity
- Use crypto.timingSafeEqual for signature comparison to prevent timing attacks
- Set up IP allowlisting at the reverse proxy level for known webhook senders
- Use random, unguessable webhook paths as a baseline defense against URL scanning
- Rotate webhook secrets periodically and update both the provider and n8n configuration
- Log rejected requests (invalid auth, bad signatures) for security monitoring

## Frequently asked questions

### Which authentication method should I use for n8n webhooks?

Use Header Auth (API key) for service-to-service integrations where you control both sides. Use Basic Auth for simple setups. Use JWT for multi-tenant applications. For provider webhooks (GitHub, Stripe), use HMAC signature verification.

### Can I use JWT authentication with n8n webhooks?

Yes. Select 'JWT Auth' in the Webhook node's Authentication section. Configure the JWT secret or JWKS endpoint. n8n validates the JWT token in the Authorization header on each request.

### How do I find the IP addresses of webhook providers?

Most providers publish their IP ranges. GitHub: api.github.com/meta. Stripe: stripe.com/docs/ips. Shopify: help.shopify.com. Check the provider's documentation for the current list.

### Does HMAC verification work with the Webhook node's raw body?

The HMAC signature must be computed on the exact raw request body. If n8n parses the body before your Code node runs, the JSON re-serialization may differ from the original. Test with your specific provider to ensure the computed signature matches.

### Can I rate-limit webhook requests in n8n?

n8n does not have built-in rate limiting. Configure rate limiting at the reverse proxy level (Nginx limit_req module or Caddy rate_limit plugin) to throttle requests per IP address.

### Can RapidDev help me secure my n8n webhook integrations?

Yes. RapidDev can implement comprehensive webhook security including authentication, HMAC verification, IP allowlisting, rate limiting, and security monitoring for production n8n deployments.

---

Source: https://www.rapidevelopers.com/n8n-tutorial/how-to-secure-webhooks-in-n8n
© RapidDev — https://www.rapidevelopers.com/n8n-tutorial/how-to-secure-webhooks-in-n8n
