5 Fouten bij Webhook Beveiliging
Real-time webhook debugging voor Nederlandse developers
Webhooks are the backbone of modern event-driven architectures. At WebhookWatch, we process over 2.4 million webhook deliveries per month for Dutch startups and agencies. Across that volume, one pattern stands out: security is the most common failure point. This article breaks down the five most critical webhook security mistakes we see in production โ and exactly how to fix each one.
Whether you're integrating with Stripe, GitHub, Shopify, or a custom internal service, the same vulnerabilities appear again and again. The cost of getting it wrong ranges from data leakage to full account takeover. Let's walk through every mistake, with code examples you can apply today.
Fout 1: Not Verifying Webhook Signatures
The single most common mistake. When you receive a webhook POST request, you must verify the signature header before processing the payload. Without signature verification, any attacker who discovers your endpoint can send forged events โ triggering fraudulent charges, creating fake orders, or injecting malicious data into your pipeline. Stripe sends a Stripe-Signature header, GitHub uses X-Hub-Signature-256, and Shopify provides X-Shopify-Hmac-Sha256. Each provider documents the exact HMAC algorithm and secret key format. Never skip this step, not even in staging.
Fout 2: Exposing Webhook URLs in Public Repositories
Webhook endpoint URLs are effectively API keys. We've found live webhook URLs committed to GitHub repositories at least 47 times in the past quarter alone. When a URL is public, bots and scanners can discover it within hours. Always treat webhook URLs as secrets: store them in environment variables, use a secrets manager like AWS Secrets Manager or HashiCorp Vault, and rotate them immediately if they appear in a public commit. If possible, use a reverse proxy with IP allowlisting to add an extra layer of access control.
Fout 3: Hardcoding Signing Secrets in Source Code
Even when developers implement signature verification, the signing secret is often hardcoded directly in the application code. A single const WEBHOOK_SECRET = 'whsec_abc123...' in a JavaScript file means every developer with repository access holds a copy of your secret. If that repository becomes public โ through an accidental open-source push or a leaked fork โ your entire webhook infrastructure is compromised. Use environment variables for local development, and a dedicated secrets management service for production deployments.
Fout 4: Missing Idempotency and Duplicate Handling
Reliable webhook providers like Stripe and GitHub will retry failed deliveries. If your endpoint returns a non-2xx status code, the same event arrives again โ sometimes dozens of times. Without idempotency checks, a single payment event could trigger three invoice creations, three database writes, or three customer notifications. Store a unique event ID from the payload and check against your database before processing. Use database-level unique constraints or a Redis set with TTL for fast duplicate detection. Always respond with 200 OK as quickly as possible, then process the event asynchronously.
Fout 5: No Timeout Handling or Request Validation
Webhook endpoints must respond within 5โ10 seconds, depending on the provider. If your endpoint performs a slow database query, calls an external API, or processes heavy data before sending a response, the provider will mark the delivery as failed and retry. This creates a cascade: slow processing โ timeout โ retry โ duplicate processing โ even slower processing. The fix is architectural: acknowledge receipt with a 200 response immediately, then hand the payload to a message queue like RabbitMQ or AWS SQS for background processing. Additionally, validate the Content-Type header (should be application/json) and reject requests with unexpected or missing headers before any business logic runs.
Quick Reference: Signature Verification in Node.js
Here's a production-ready pattern for verifying Stripe webhook signatures. Adapt the header name and algorithm for your specific provider:
const crypto = require('crypto');
function verifyStripeSignature(payload, signature, timestamp, secret) {
const signedPayload = `${timestamp}.${payload}`;
const expected = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
const sig256 = signature.split(',')[0].replace('sha256=', '');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(sig256)
);
}
// Usage in Express
app.post('/webhooks/stripe', (req, res) => {
const signature = req.headers['stripe-signature'];
const valid = verifyStripeSignature(
req.rawBody,
signature,
signature.split(',')[0].split('=')[1],
process.env.STRIPE_WEBHOOK_SECRET
);
if (!valid) {
return res.status(400).send('Invalid signature');
}
// Process event asynchronously
queue.processEvent(req.body);
res.status(200).send('OK');
});
About the Author
Daan de Vries is a senior backend engineer and founder of WebhookWatch. Based in Amsterdam, Daan has spent six years building event-driven integrations for companies like Mollie, Bol.com, and various Y Combiner-backed startups. He's personally debugged over 18,000 webhook delivery failures and maintains an open-source webhook signature verification library used by 3,200+ developers. When he's not tracing HTTP requests, he writes about API architecture on his personal blog.
Related Articles
How to Build a Webhook Relay That Survives Outages
Designing resilient webhook infrastructure with dead-letter queues, exponential backoff, and automatic replay โ a complete architecture guide for production systems.
Testing Webhooks Locally Without Exposing Your Machine
A comparison of ngrok, cloudflare tunnels, and WebhookWatch's built-in tunnel feature for safe local development. Includes setup instructions and security considerations.
Webhook Delivery Metrics You Should Monitor in 2025
Beyond uptime: tracking latency percentiles, retry rates, signature failure counts, and payload size distributions to keep your integration pipeline healthy.