Protect your webhook endpoints from the 5 most common attacks with code examples and a manual checklist.
Webhooks are powerful, but they expose your server to incoming HTTP traffic from the internet. A misconfigured webhook endpoint can lead to:
The good news: every one of these attacks is preventable. Review the security checklist
An attacker captures a legitimate webhook request and re-sends it minutes, hours, or days later. Without timestamp validation, your system processes it again — double-charging customers, duplicating orders, or re-triggering deployments.
Defense: Check the timestamp header (e.g., Stripe's t= field). Reject any request older than 5 minutes. Store processed event IDs and skip duplicates.
If your endpoint accepts plain HTTP, anyone on the network path can read and modify webhook payloads. Payment amounts, customer emails, and API keys in transit are all exposed.
Defense: Only accept HTTPS. Redirect HTTP to HTTPS. Use HSTS headers. Verify your TLS certificate is valid and not expired.
An attacker sends a fake webhook to your endpoint, pretending to be Stripe, GitHub, or any other provider. Without signature verification, your system trusts and processes the forged payload.
Defense: Verify the HMAC signature on every request. Never skip verification, even in development. See code examples below.
If your system allows users to configure webhook destination URLs, an attacker can point them to internal services like http://169.254.169.254 (cloud metadata) or http://localhost:6379 (Redis). This can leak secrets and compromise your infrastructure.
Defense: Validate destination URLs. Block private IP ranges (127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 169.254.0.0/16). Resolve DNS and check the IP before connecting. Block DNS rebinding.
Webhook payloads often contain PII, payment details, or access tokens. If you log full request bodies to stdout, error trackers, or monitoring tools, this sensitive data ends up in places it shouldn't.
Defense: Redact sensitive fields before logging. Never log full webhook bodies in production. Use structured logging with field-level controls.
Signature verification is the single most important webhook security measure. Here's how to implement it for the two most popular languages:
const crypto = require('crypto'); function verifyWebhookSignature(req, secret) { const signature = req.headers['x-webhook-signature']; const timestamp = req.headers['x-webhook-timestamp']; // 1. Reject old timestamps (prevent replay attacks) const age = Math.abs(Date.now() / 1000 - parseInt(timestamp)); if (age > 300) throw new Error('Timestamp too old'); // 2. Compute expected signature const payload = timestamp + '.' + req.body; const expected = crypto .createHmac('sha256', secret) .update(payload) .digest('hex'); // 3. Constant-time comparison (prevents timing attacks) if (!crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected) )) { throw new Error('Invalid signature'); } return true; }
import hmac, hashlib, time def verify_webhook(request, secret): signature = request.headers.get('X-Webhook-Signature') timestamp = request.headers.get('X-Webhook-Timestamp') # 1. Reject old timestamps (prevent replay attacks) if abs(time.time() - int(timestamp)) > 300: raise ValueError('Timestamp too old') # 2. Compute expected signature payload = f'{timestamp}.{request.data.decode()}' expected = hmac.new( secret.encode(), payload.encode(), hashlib.sha256 ).hexdigest() # 3. Constant-time comparison (prevents timing attacks) if not hmac.compare_digest(signature, expected): raise ValueError('Invalid signature') return True
Every webhook endpoint must use HTTPS. Here's a quick checklist:
Strict-Transport-Security: max-age=31536000 to prevent protocol downgrades.Most providers (Stripe, GitHub, Shopify) refuse to send webhooks to HTTP URLs. But if yours doesn't enforce this, you must.
Many webhook providers publish the IP addresses they send from. Configure your firewall or reverse proxy to accept webhook traffic only from these ranges:
https://stripe.com/files/ips/ips_webhooks.jsonhooks field at https://api.github.com/metaCombine IP allowlisting with signature verification. IP-based filtering alone is not enough — IPs can be spoofed or change without notice. Defense in depth.
Even with signature verification, you should rate-limit your webhook endpoints:
Return 200 OK immediately and process the webhook in a background queue. If your handler takes more than 5 seconds, most providers will retry, creating a thundering herd.
Use this checklist to audit your webhook endpoints:
Work through each item in staging and record the request, response, and configuration evidence for your review.
Use the checklist above to verify signatures, replay handling, TLS, rate limits, SSRF controls, and log redaction.
Open the Checklist What is a Webhook?Related guides: What is a Webhook? · Webhook vs API · Retry Strategies
More: What is a Webhook? | All Features | Stripe Webhook Guide | Compare Tools