Verify Stripe Webhook Signatures

Step-by-step guide to verifying Stripe webhook signatures using HMAC-SHA256. Learn Stripe's timestamp-prefixed signing scheme and how to implement replay attack prevention.

Implementation

Detailed Explanation

Verifying Stripe Webhook Signatures

Stripe uses HMAC-SHA256 to sign all webhook events, allowing you to verify that incoming webhooks are genuinely from Stripe and have not been tampered with. Understanding Stripe's specific signing scheme is essential for secure payment integrations.

Stripe's Signing Scheme

Stripe's approach includes built-in replay protection:

  1. Signing secret: When you create a webhook endpoint, Stripe provides a secret starting with whsec_
  2. Timestamp: Stripe includes a Unix timestamp in the Stripe-Signature header
  3. Signed payload: The signed content is {timestamp}.{raw_body} (timestamp, a dot, then the raw JSON body)
  4. Signature header format: t=1614556800,v1=a3f2b8c1...,v0=legacy...

Verification Steps

# 1. Extract timestamp and signatures from header
header = "t=1614556800,v1=5257a869..."
timestamp = extract(header, "t")
signatures = extract_all(header, "v1")

# 2. Construct the signed payload
signed_payload = timestamp + "." + raw_request_body

# 3. Compute expected signature
expected = HMAC_SHA256(webhook_secret, signed_payload)

# 4. Compare using constant-time comparison
if any(constant_time_equal(sig, expected) for sig in signatures):
    # 5. Check timestamp tolerance (reject if > 300 seconds old)
    if current_time - timestamp > 300:
        reject("Timestamp too old")
    else:
        process_event()

Why Stripe Signs timestamp.body

By including the timestamp in the signed payload, Stripe ensures that:

  • Replay prevention: An old webhook cannot be replayed because its timestamp will be outside the tolerance window
  • Timestamp integrity: An attacker cannot modify the timestamp without invalidating the signature
  • Multiple signatures: The v1 field may contain multiple signatures during secret rotation, allowing zero-downtime key changes

Common Mistakes

  1. Parsing JSON before verifying: Always verify the signature against the raw request body. If your web framework parses JSON automatically, you need to access the raw body before parsing.
  2. Ignoring timestamp checks: Signature verification alone does not prevent replay attacks. Always check the timestamp.
  3. Using string comparison: Always use constant-time comparison to prevent timing attacks.
  4. Hardcoding the secret: Store whsec_ secrets in environment variables or a secrets manager, never in source code.

Use Case

Stripe webhook verification is required for any application that processes payment events such as successful charges, subscription updates, refunds, disputes, and payout notifications through Stripe's webhook delivery system.

Try It — HMAC Generator

Open full tool