All templates
Sequence template

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-SHA256 with your provider's algorithm (Stripe uses SHA256, GitHub uses SHA256, others may use SHA1).
  • Adjust the 5 min freshness 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