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.
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:
- Signing secret: When you create a webhook endpoint, Stripe provides a secret starting with
whsec_ - Timestamp: Stripe includes a Unix timestamp in the
Stripe-Signatureheader - Signed payload: The signed content is
{timestamp}.{raw_body}(timestamp, a dot, then the raw JSON body) - 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
v1field may contain multiple signatures during secret rotation, allowing zero-downtime key changes
Common Mistakes
- 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.
- Ignoring timestamp checks: Signature verification alone does not prevent replay attacks. Always check the timestamp.
- Using string comparison: Always use constant-time comparison to prevent timing attacks.
- 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.