Webhook security verification
Validate webhook signatures and detect spoofed or replayed webhooks.
Webhooks are powerful — they let external providers notify you of events in real time. But they're also a security risk: any endpoint on the internet that accepts HTTP POSTs can be sent fake webhooks by attackers who know the URL. This template shows the verification dance: signature validation (proving the webhook came from the real provider), timestamp freshness (preventing replays of old webhooks), and the constant-time comparison (dodging timing attacks).
The flow is always the same: extract the signature header, rebuild the HMAC-SHA256 of the request body using your shared secret, compare them with constant-time comparison, check that the timestamp is recent, and only then queue the message for processing. Get any of these wrong — skip signature validation, use loose string comparison, accept old timestamps — and you've opened a window for spoofing or replay attacks.
When to use this template
- Webhook handler design — when you're building a handler for Stripe, GitHub, Twilio, or any provider, follow this flow to implement signature validation correctly.
- Security review — walk the security team through this diagram to confirm your webhook handlers validate signatures and timestamps.
- Incident postmortem — if a webhook was spoofed or a duplicate charged customers, this diagram shows where the check failed.
How to adapt it
Customize to your webhook provider and security policy:
- Replace
HMAC-SHA256with your provider's algorithm (Stripe uses SHA256, GitHub uses SHA256, others may use SHA1). - Adjust the
5 minfreshness window based on your network latency and clock skew tolerance — use 10 minutes if your servers have worse sync, 2 minutes if you're paranoid. - Add an idempotency key check after the timestamp validation if you want to prevent duplicate processing of replayed or retried webhooks.
Visual edits regenerate clean Mermaid code, so you can adapt the sequence to your provider's exact signature scheme and your team's security policy without editing syntax directly.
Mermaid code
Copy it anywhere Mermaid is supported — GitHub, Notion, or your docs.
sequenceDiagram
participant Provider as External Provider
participant Server as Your Server
participant Handler as Webhook Handler
Provider ->> Server: POST /webhook<br/>X-Signature: HMAC-SHA256
Server ->> Server: Extract signature<br/>from header
Server ->> Server: Fetch secret<br/>from config
Server ->> Server: Rebuild HMAC<br/>over request body
Server ->> Server: Constant-time<br/>comparison
Server ->> Server: Comparison fails?
alt Signatures Don't Match
Server ->> Provider: 401 Unauthorized
else Signatures Match
Server ->> Server: Check timestamp<br/>within 5 min?
alt Timestamp Expired
Server ->> Provider: 403 Forbidden
else Timestamp Valid
Server ->> Handler: Queue message
Handler ->> Handler: Process webhook
Handler ->> Server: Update state
Server ->> Provider: 200 OK
end
end
Frequently asked questions
- Why do webhooks need signatures?
- A webhook is an unauthenticated HTTP POST to a URL the provider knows. Without signatures, anyone on the internet could impersonate the provider and send fake webhooks to that URL — triggering false payments, notifications, or state changes. A signature proves the webhook came from the real provider who knows the shared secret, not an attacker guessing the URL.
- How do webhook signatures work?
- The provider and your server share a secret (e.g. 'my_webhook_secret'). When sending a webhook, the provider computes HMAC-SHA256(secret, request_body) and includes it in a header like X-Signature. Your server receives the webhook, recomputes the HMAC over the same body using your copy of the secret, and compares them. If they match, the webhook is authentic. This is the same cryptographic proof banks use for payment transfers.
- What is constant-time comparison?
- A naive string comparison like `if (signature == expected) { ... }` exits early when it finds a mismatch. An attacker timing how long the comparison takes can guess the signature character-by-character. Constant-time comparison always compares the full string regardless, preventing timing attacks. Use crypto.timingSafeEqual (Node.js) or similar in your language.
- What is the timestamp check for?
- An attacker who intercepts a valid webhook can replay it hours or days later to trigger the same action twice (double-charge, duplicate notification, etc.). Including a timestamp (signed with the rest of the webhook) and checking that it's within 5 minutes of now prevents replay attacks. If someone tries to resend an old webhook, the timestamp check catches it. For sensitive operations like payments, always check timestamp freshness.
Related templates
Webhook signature verification sequence
Validate webhook authenticity before processing events.
API token authentication sequence
Client obtaining and using bearer tokens for API requests.
JWT token validation sequence
Client requests protected resource using access token, server validates signature.