Secure n8n Webhooks: Authentication, IP Filtering, and Abuse Prevention
Your n8n webhooks are publicly exposed attack surfaces. Every webhook URL you create is a door into your automation system, and attackers are constantly scanning for unprotected endpoints.
The Webhook Security Gap
Your n8n dashboard can hide behind a VPN. Your webhooks cannot.
External services need to reach your endpoints:
- Stripe needs to notify you about payments
- GitHub needs to trigger deployments
- Shopify needs to sync orders
This creates a fundamental challenge: How do you keep the door open for legitimate traffic while blocking everyone else?
Most n8n security guides cover general protections: SSL certificates, editor authentication, credential encryption. But webhook security is different. You’re not just protecting access. You’re validating that every single request is legitimate before your workflow executes.
What You’ll Learn
| Protection Layer | What It Does |
|---|---|
| Authentication | Verify who is sending the request |
| HMAC Signatures | Prove the payload hasn’t been tampered with |
| IP Filtering | Restrict access to known senders |
| Rate Limiting | Prevent abuse and resource exhaustion |
| Reverse Proxy | Add defense in depth at the network edge |
By the end of this guide, you’ll have production-ready patterns for:
- Configuring n8n’s built-in authentication options
- Verifying Stripe, GitHub, and Shopify webhook signatures
- Setting up nginx rate limiting and IP filtering
- Separating your admin dashboard from public webhook endpoints
- Monitoring for suspicious activity and attacks
Why Webhook Security Matters
Webhooks are different from other API endpoints. They’re designed to be called by external services, which means they must be publicly accessible. This exposure creates several attack vectors.
Resource exhaustion: An attacker can flood your webhook with requests, consuming n8n execution credits, API quotas for downstream services, and server resources. Even if each request fails validation, the processing overhead adds up.
Data manipulation: Without proper authentication, attackers can send fake webhook payloads. Imagine someone sending fabricated “order completed” events to your e-commerce workflow, triggering fulfillment for orders that never happened.
Information disclosure: Error messages that reveal internal system details help attackers map your infrastructure. A verbose error like “Failed to connect to database at 192.168.1.50” tells them more than they should know.
Workflow manipulation: If an attacker can trigger your workflows with crafted payloads, they can potentially access connected systems, exfiltrate data through your integrations, or cause cascading failures.
| Threat | Impact | Primary Countermeasure |
|---|---|---|
| Unauthorized access | Fake events trigger workflows | Authentication (API keys, HMAC) |
| DDoS/Resource abuse | Execution costs, downtime | Rate limiting, WAF |
| Replay attacks | Duplicate processing | Timestamp validation, idempotency |
| Data injection | Malicious payloads processed | Input validation, type checking |
| Information leakage | Attack surface mapping | Generic error responses |
The good news: n8n provides built-in tools for many of these threats, and reverse proxies can handle the rest. Let’s start with what’s available out of the box.
Built-in n8n Authentication Methods
The n8n Webhook node supports several authentication schemes. These work by validating incoming requests before your workflow logic executes.
Header Authentication
Header auth is the simplest approach. You define a custom header name and secret value. Any request without the correct header is rejected.
How to configure:
- Add a Webhook node to your workflow
- Set Authentication to “Header Auth”
- Create a new credential with your header name and value
For example, you might use:
- Header Name:
X-Webhook-Secret - Header Value:
your-randomly-generated-secret-here
Callers must include this exact header:
curl -X POST https://your-n8n.example.com/webhook/orders \
-H "X-Webhook-Secret: your-randomly-generated-secret-here" \
-H "Content-Type: application/json" \
-d '{"order_id": 12345}'
When to use header auth:
- Internal system integrations where you control both sides
- Simple API integrations without built-in signature support
- Quick protection for development and staging webhooks
Limitations:
Header auth proves the caller knows a secret, but doesn’t verify payload integrity. Someone who intercepts the header value can send any payload they want. For sensitive workflows, combine header auth with input validation or upgrade to HMAC verification.
Basic Authentication
Basic auth uses standard HTTP authentication with a username and password. The credentials are Base64-encoded and sent in the Authorization header.
Configuration:
- Set Authentication to “Basic Auth”
- Create credentials with your username and password
Callers authenticate like this:
curl -X POST https://your-n8n.example.com/webhook/data \
-u "webhook_user:secure_password" \
-H "Content-Type: application/json" \
-d '{"event": "test"}'
Or with the explicit header:
curl -X POST https://your-n8n.example.com/webhook/data \
-H "Authorization: Basic d2ViaG9va191c2VyOnNlY3VyZV9wYXNzd29yZA==" \
-H "Content-Type: application/json" \
-d '{"event": "test"}'
When to use basic auth:
- Legacy systems that only support basic authentication
- Quick setups where header auth isn’t convenient
- Services that have built-in basic auth support
Important: Basic auth credentials are only Base64-encoded, not encrypted. Always use HTTPS to prevent credential interception. For troubleshooting authentication issues, see our guide to fixing n8n authentication errors.
JWT Authentication
JSON Web Tokens (JWT) provide a more robust authentication mechanism. They’re commonly used with modern identity providers like Auth0, Firebase, or custom OAuth implementations.
With JWT auth, the caller sends a signed token in the Authorization header. n8n validates the token’s signature and optionally checks claims like expiration time or issuer.
Workflow pattern for JWT validation:
- Webhook receives request with
Authorization: Bearer <token>header - Code node extracts and validates the JWT
- IF node routes based on validation result
- Invalid tokens get a generic error response
Here’s a Code node that validates JWTs:
const jwt = require('jsonwebtoken');
const authHeader = $input.first().headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return { valid: false, error: 'Missing or invalid authorization header' };
}
const token = authHeader.substring(7);
const secret = $env.JWT_SECRET; // Store in n8n environment variables
try {
const decoded = jwt.verify(token, secret, {
algorithms: ['HS256'],
issuer: 'your-expected-issuer'
});
return {
valid: true,
user: decoded.sub,
claims: decoded
};
} catch (err) {
return { valid: false, error: 'Token validation failed' };
}
When to use JWT auth:
- Integration with identity providers (Auth0, Firebase, Okta)
- User-context webhooks where you need to know who triggered the request
- Complex authorization scenarios with role-based access
Authentication Method Comparison
| Method | Security Level | Complexity | Best For |
|---|---|---|---|
| None | Low | Trivial | Public endpoints, testing only |
| Header Auth | Medium | Easy | Internal integrations, simple APIs |
| Basic Auth | Medium | Easy | Legacy system compatibility |
| JWT | High | Complex | Identity provider integrations |
| HMAC Signature | High | Medium | Payment/platform webhooks |
For most production use cases, header auth provides sufficient protection when combined with HTTPS and rate limiting. However, payment webhooks and high-value integrations warrant HMAC signature verification, which we’ll cover next.
HMAC Signature Verification
Authentication tells you who is sending the request. HMAC signature verification tells you the request hasn’t been tampered with.
Major platforms like Stripe, GitHub, and Shopify sign every webhook they send. The signature is a cryptographic hash of the request body combined with a shared secret. Only someone with the secret can produce a valid signature, and any modification to the payload invalidates it.
How HMAC Signatures Work
- The platform (Stripe, GitHub, etc.) takes the raw request body
- They compute an HMAC using SHA-256 (or similar) with your endpoint’s secret
- They send the signature in a custom header with the request
- You receive the request, compute the same HMAC with your copy of the secret
- If the signatures match, the request is authentic and unmodified
This prevents two attack scenarios:
- Forgery: Attackers can’t create valid signatures without the secret
- Tampering: Any change to the payload produces a different signature
Implementing HMAC Verification in n8n
n8n doesn’t have built-in HMAC verification (there’s a feature request for this), but you can implement it with a Code node immediately after your Webhook trigger.
Stripe Webhook Verification Example:
Stripe sends signatures in the Stripe-Signature header with this format:
t=1492774577,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
Here’s a complete verification workflow:
const crypto = require('crypto');
// Get the raw body and signature header
const rawBody = $input.first().json.body; // Ensure webhook is set to receive raw body
const signatureHeader = $input.first().headers['stripe-signature'];
const endpointSecret = $env.STRIPE_WEBHOOK_SECRET;
// Parse the signature header
const elements = signatureHeader.split(',');
let timestamp, signature;
for (const element of elements) {
const [key, value] = element.split('=');
if (key === 't') timestamp = value;
if (key === 'v1') signature = value;
}
// Verify timestamp is within tolerance (5 minutes)
const currentTime = Math.floor(Date.now() / 1000);
if (currentTime - parseInt(timestamp) > 300) {
throw new Error('Webhook timestamp too old - possible replay attack');
}
// Compute expected signature
const signedPayload = `${timestamp}.${rawBody}`;
const expectedSignature = crypto
.createHmac('sha256', endpointSecret)
.update(signedPayload)
.digest('hex');
// Compare signatures using timing-safe comparison
const signatureBuffer = Buffer.from(signature, 'hex');
const expectedBuffer = Buffer.from(expectedSignature, 'hex');
if (signatureBuffer.length !== expectedBuffer.length ||
!crypto.timingSafeEqual(signatureBuffer, expectedBuffer)) {
throw new Error('Invalid webhook signature');
}
// Signature valid - parse and return the payload
return {
verified: true,
payload: JSON.parse(rawBody),
timestamp: new Date(parseInt(timestamp) * 1000).toISOString()
};
GitHub Webhook Verification Example:
GitHub uses the X-Hub-Signature-256 header:
const crypto = require('crypto');
const rawBody = $input.first().json.body;
const signature = $input.first().headers['x-hub-signature-256'];
const secret = $env.GITHUB_WEBHOOK_SECRET;
// GitHub signature format: sha256=<hash>
const expectedSignature = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
// Timing-safe comparison
if (!crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
)) {
throw new Error('Invalid GitHub webhook signature');
}
return { verified: true, payload: JSON.parse(rawBody) };
Important implementation notes:
- Use the raw body: HMAC is computed on the exact bytes received, not a parsed and re-serialized object. Configure your Webhook node to preserve the raw body.
- Timing-safe comparison: Use
crypto.timingSafeEqual()to prevent timing attacks that could leak signature information. - Store secrets securely: Keep webhook secrets in n8n environment variables or credentials, never hardcoded in workflows. See our credential management guide for best practices.
Preventing Replay Attacks
HMAC verification proves the request came from the expected sender with the correct payload. But what if an attacker intercepts a valid request and sends it again?
Replay attacks resend legitimate requests to trigger duplicate processing. Imagine an attacker replaying a “payment successful” webhook multiple times.
Countermeasures:
- Timestamp validation: Most platforms include a timestamp in the signed payload. Reject requests older than 5 minutes:
const MAX_AGE_SECONDS = 300; // 5 minutes
const currentTime = Math.floor(Date.now() / 1000);
if (currentTime - webhookTimestamp > MAX_AGE_SECONDS) {
throw new Error('Webhook expired');
}
- Idempotency tracking: Store processed webhook IDs and reject duplicates:
const webhookId = payload.id; // Most platforms include a unique ID
// Check if already processed (pseudocode - implement with your database)
const alreadyProcessed = await checkWebhookProcessed(webhookId);
if (alreadyProcessed) {
return { skipped: true, reason: 'Already processed' };
}
// Process webhook...
// Mark as processed
await markWebhookProcessed(webhookId);
- Nonce tracking: For platforms that don’t include timestamps, track unique request identifiers and reject seen values.
IP Filtering and Whitelisting
IP filtering restricts which network addresses can reach your webhooks. If you know Stripe only sends webhooks from specific IP ranges, you can reject everything else.
When IP Filtering Makes Sense
IP whitelisting works best when:
- The sender publishes a stable list of IP addresses
- You have control over network-level filtering (firewall, reverse proxy)
- The integration is from a known business system, not end users
Stripe, for example, publishes webhook IPs that you can whitelist.
Limitations of IP Filtering
Before implementing, understand the trade-offs:
- Maintenance burden: IP lists change. Stripe might add new IPs, and if you don’t update your whitelist, webhooks fail.
- CDN complications: If you’re behind Cloudflare or another CDN, you need to configure it to pass the original client IP correctly.
- Not always available: Many services use dynamic IPs or don’t publish their ranges.
- False sense of security: IP filtering isn’t authentication. It reduces attack surface but doesn’t prove request authenticity.
As one n8n community member noted: IP whitelisting is impractical for public-facing integrations like chatbots where traffic comes from many sources.
Implementation via Nginx
If you’re running n8n behind nginx (recommended), implement IP filtering there:
# /etc/nginx/conf.d/n8n-webhooks.conf
# Define allowed IPs for Stripe webhooks
geo $allowed_stripe {
default 0;
3.18.12.63/32 1;
3.130.192.163/32 1;
13.235.14.237/32 1;
# Add other Stripe IPs...
}
server {
listen 443 ssl;
server_name webhooks.example.com;
# Stripe webhook endpoint with IP filtering
location /webhook/stripe {
if ($allowed_stripe = 0) {
return 403;
}
proxy_pass http://localhost:5678;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# Other webhooks without IP filtering (use auth instead)
location /webhook/ {
proxy_pass http://localhost:5678;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Cloudflare IP Access Rules
If you’re using Cloudflare, configure IP Access Rules:
- Go to Security > WAF > Tools
- Create rules for each allowed IP range
- Set action to “Allow” for known webhook senders
- Create a default “Block” rule for the webhook path
Cloudflare also offers firewall rules with more granular conditions, like combining IP checks with header requirements.
Rate Limiting and Abuse Prevention
Rate limiting is your defense against resource exhaustion attacks. Even if every request fails authentication, processing them consumes resources. Rate limiting caps how many requests reach your n8n instance.
Why Rate Limiting Matters
An attacker flooding your webhook endpoint can:
- Exhaust n8n execution credits on cloud plans
- Consume API quotas for connected services
- Slow down legitimate traffic
- Fill logs with garbage, hiding real issues
- Increase hosting costs
Rate limiting is especially important for public integrations discussed in the community like chatbot webhooks that can’t use IP filtering.
Nginx Rate Limiting
Implement rate limiting at the reverse proxy level so blocked requests never reach n8n:
# Define rate limiting zones
limit_req_zone $binary_remote_addr zone=webhook_limit:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=strict_limit:10m rate=1r/s;
server {
listen 443 ssl;
server_name n8n.example.com;
# Standard webhooks: 10 requests/second per IP
location /webhook/ {
limit_req zone=webhook_limit burst=20 nodelay;
limit_req_status 429;
proxy_pass http://localhost:5678;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# Sensitive webhooks: stricter limits
location /webhook/payments {
limit_req zone=strict_limit burst=5 nodelay;
limit_req_status 429;
proxy_pass http://localhost:5678;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Configuration explained:
rate=10r/s: Allow 10 requests per second per IPburst=20: Allow temporary bursts up to 20 requestsnodelay: Process burst requests immediately rather than queuinglimit_req_status 429: Return 429 Too Many Requests when limited
Cloudflare Rate Limiting
Cloudflare provides rate limiting rules that apply before traffic reaches your origin:
- Go to Security > WAF > Rate limiting rules
- Create a rule for your webhook paths:
- If: URI Path contains
/webhook/ - Rate: 100 requests per 10 seconds
- Action: Block for 60 seconds
- If: URI Path contains
- Create stricter rules for sensitive endpoints
Cloudflare rate limiting has several advantages:
- Malicious traffic never reaches your server
- You get analytics on blocked requests
- Rules can combine multiple conditions (IP, country, headers)
n8n Execution Limits
As a secondary defense, configure n8n’s execution settings:
# Limit concurrent executions
EXECUTIONS_PROCESS=main
EXECUTIONS_TIMEOUT=300
EXECUTIONS_TIMEOUT_MAX=3600
# Prune old executions to save resources
EXECUTIONS_DATA_PRUNE=true
EXECUTIONS_DATA_MAX_AGE=168
These settings prevent runaway workflows from consuming all resources. For more on managing API limits in your workflows, see our rate limiting guide.
Reverse Proxy Security
Running n8n behind a reverse proxy like nginx or Caddy is a security best practice. The proxy adds a layer of defense and enables features n8n doesn’t provide natively.
Benefits of a Reverse Proxy
- SSL termination: Handle HTTPS certificates in one place
- Rate limiting: Block abuse before it reaches n8n
- IP filtering: Restrict access by source IP
- Request filtering: Block malicious payloads at the edge
- Separation of concerns: Different rules for admin vs. webhooks
Separating Dashboard from Webhooks
A common question in the n8n community: how do you keep the admin dashboard private while exposing webhooks publicly?
Here’s an nginx configuration that accomplishes this:
# Internal: n8n dashboard (VPN or internal network only)
server {
listen 443 ssl;
server_name n8n-admin.internal.example.com;
# Only allow internal IPs
allow 10.0.0.0/8;
allow 192.168.0.0/16;
deny all;
location / {
proxy_pass http://localhost:5678;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# External: webhooks only
server {
listen 443 ssl;
server_name webhooks.example.com;
# Block access to admin paths
location / {
return 404;
}
location /rest/ {
return 404;
}
# Only allow webhook paths
location /webhook/ {
limit_req zone=webhook_limit burst=20 nodelay;
proxy_pass http://localhost:5678;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /webhook-test/ {
# Block test webhooks in production
return 404;
}
}
This setup:
- Exposes the admin dashboard only to internal networks
- Blocks all paths except
/webhook/on the public domain - Hides test webhook endpoints from production
- Applies rate limiting to all webhooks
For complete setup instructions, see our self-hosting guide.
Cloudflare WAF Integration
If you’re using Cloudflare as a CDN and WAF, layer it in front of your nginx:
Internet → Cloudflare (WAF, DDoS protection, rate limiting)
→ nginx (additional filtering, SSL to origin)
→ n8n
Configure Cloudflare to:
- Enable “Under Attack Mode” for automatic DDoS protection
- Set up WAF managed rules for common attacks
- Configure rate limiting rules for webhook paths
- Enable Bot Fight Mode if you don’t need bot traffic
Monitoring and Alerting
Security isn’t set-and-forget. You need visibility into what’s hitting your webhooks and alerts when something looks wrong.
What to Monitor
- Request volume: Sudden spikes may indicate attacks
- Error rates: High 4xx/5xx rates suggest probing or misconfigured clients
- Authentication failures: Track failed validation attempts
- Geographic distribution: Unexpected countries may warrant investigation
- Payload patterns: Log and analyze unusual request structures
Setting Up Alerts in n8n
Create an error monitoring workflow that alerts you to webhook issues:
Error Trigger → Filter (webhook errors only) → Aggregate (batch alerts) → Slack/Email
The Error Trigger node catches any workflow failure. Filter for webhook-related errors and send notifications to your monitoring channel.
// In a Code node to format the alert
const error = $input.first().json;
return {
alert: {
workflow: error.workflow.name,
node: error.execution.lastNodeExecuted,
error: error.execution.error.message,
timestamp: new Date().toISOString(),
webhookPath: error.execution.data?.webhookPath || 'unknown'
}
};
Log Aggregation
For production deployments, ship logs to a centralized platform:
- ELK Stack (Elasticsearch, Logstash, Kibana)
- Grafana Loki
- Datadog
- CloudWatch (AWS)
Configure n8n to output structured logs:
N8N_LOG_LEVEL=info
N8N_LOG_OUTPUT=console
Then parse and forward logs from your container or host. Use our workflow auditor tool to identify workflows that might need better error handling.
Security Patterns by Use Case
Different integrations have different security requirements. Here’s how to approach common scenarios.
Payment Webhooks (Stripe, Shopify)
Payment webhooks are high-value targets. A single forged “payment successful” event could trigger fulfillment for an unpaid order.
Required protections:
- HMAC signature verification (mandatory)
- Timestamp validation to prevent replays
- Idempotency checking for duplicate prevention
- Strict input validation on payload fields
// Payment webhook validation pattern
const isValid = verifyHMACSignature(rawBody, signature, secret);
const isRecent = (Date.now() / 1000 - timestamp) < 300;
const isNew = !await wasProcessed(eventId);
if (!isValid || !isRecent || !isNew) {
// Return 200 to prevent retries, but don't process
return { processed: false };
}
// Safe to process payment event
Chatbot and Public Integrations
Chatbots and form handlers receive traffic from end users, not just platform servers. IP filtering isn’t practical.
Security approach:
- Rate limiting as primary protection
- Optional API key for premium users
- Input validation and sanitization
- Generic error responses
// Validate and sanitize chatbot input
const message = $input.first().json.message;
if (!message || typeof message !== 'string') {
return { error: 'Invalid message format' };
}
// Truncate to prevent resource exhaustion
const sanitized = message.slice(0, 2000).trim();
// Process...
For detailed webhook testing, use our webhook tester tool to validate payloads before deployment.
Internal System Webhooks
Webhooks between your own systems don’t need public exposure.
Options:
- VPN/Tailscale network: Keep webhooks entirely private
- Internal-only nginx server blocks
- Strong authentication with rotating secrets
# n8n environment for internal webhooks
WEBHOOK_URL=https://n8n.internal.company.local/
The community has shared patterns for private networks with selective public exposure.
Production Security Checklist
Before deploying webhooks to production, verify each item:
Authentication:
- Every webhook has authentication enabled (header auth, basic auth, or HMAC)
- Secrets are stored in n8n credentials or environment variables
- Secrets are randomly generated and sufficiently long (32+ characters)
- Test webhooks are disabled or protected in production
HMAC Verification (for payment/platform webhooks):
- Signature verification implemented in Code node
- Timing-safe comparison used for signature matching
- Timestamp validation enabled (5-minute window)
- Raw request body preserved for HMAC calculation
Network Security:
- HTTPS enforced for all webhooks
- Reverse proxy configured (nginx/Caddy)
- Rate limiting enabled at proxy level
- Admin dashboard separated from webhook endpoints
- IP filtering implemented where applicable
Monitoring:
- Error alerting configured for webhook failures
- Request logging enabled for audit trails
- Metrics collection for rate and error monitoring
- Regular review of blocked/failed requests
Error Handling:
- Generic error messages returned to callers
- Detailed errors logged internally only
- Graceful degradation for downstream failures
- Retry logic for transient errors
For professional security audits and implementation, explore our consulting services or self-hosted setup service.
When to Get Help
Webhook security involves multiple layers: application logic, network configuration, and ongoing monitoring. If you’re unsure about your setup, consider professional review.
Signs you might need expert help:
- Processing financial transactions or sensitive data
- Compliance requirements (PCI-DSS, HIPAA, SOC 2)
- Complex multi-system integrations
- High-traffic production workloads
- Recent security incidents or failed audits
Our n8n consulting team can audit your webhook security, recommend improvements, and implement hardened configurations. For ongoing protection, our support and maintenance packages include security monitoring and incident response.
Frequently Asked Questions
How do I protect my n8n webhook from DDoS attacks?
DDoS protection requires multiple layers. At minimum, place n8n behind a reverse proxy (nginx or Caddy) with rate limiting configured to cap requests per IP. For production workloads, use a service like Cloudflare that provides DDoS mitigation at the edge before traffic reaches your infrastructure. Enable Cloudflare’s “Under Attack Mode” during active attacks. Self-managed DDoS defense at scale is impractical for most teams. The combination of Cloudflare’s network-level protection plus nginx rate limiting provides defense in depth.
Can I use IP whitelisting for webhooks that receive traffic from multiple sources?
IP whitelisting becomes impractical when traffic comes from many sources or dynamic IPs. For chatbots, form handlers, or any public-facing integration, rely on rate limiting and authentication instead. IP whitelisting works best for platform webhooks (Stripe, GitHub) where the sender publishes stable IP ranges. Even then, treat it as one layer of defense, not the only protection. Always combine IP filtering with authentication for critical endpoints.
What authentication should I use for a public-facing chatbot webhook?
For chatbots receiving end-user traffic, use a tiered approach. First, implement aggressive rate limiting at your reverse proxy (start with 10 requests per second per IP). Second, validate and sanitize all input before processing. Third, consider API key authentication for premium or registered users while allowing anonymous access with stricter limits. Return generic errors to prevent information leakage. The goal is balancing accessibility with abuse prevention.
How do I verify Stripe or Shopify webhook signatures in n8n?
Use a Code node immediately after your Webhook trigger to verify signatures. Extract the signature header (Stripe-Signature for Stripe, X-Shopify-Hmac-Sha256 for Shopify), compute the expected HMAC using the raw request body and your endpoint secret, then compare using timing-safe comparison. Reject requests where signatures don’t match or timestamps are too old. Store webhook secrets in n8n environment variables, never in workflow code. See the HMAC verification section above for complete code examples.
Should I expose my n8n instance to the internet to use webhooks?
You don’t need to expose the entire n8n instance. The recommended architecture separates webhook endpoints from the admin dashboard. Use nginx or another reverse proxy to route webhook paths (/webhook/*) to n8n while blocking access to admin paths. Serve the dashboard only on an internal network or through a VPN. This way, webhooks are publicly accessible for external services while your admin interface remains protected. For more on this architecture, see our self-hosting mistakes guide and best practices guide.