Webhook Signature Verification

Stripe webhook signature verification checks the Stripe-Signature header against your endpoint secret to prove an event came from Stripe and wasn't altered.

Updated October 1, 2026

Webhook signature verification is the check your server runs to prove that a request to your webhook endpoint came from Stripe and that nobody changed it on the way. The endpoint is a public URL, so anyone who finds it can send a fake invoice.paid or payment_intent.succeeded. Without the check, your code could grant access or ship an order for a payment that never happened.

How Stripe signs a webhook

Every delivery carries a Stripe-Signature header on a single line, with a timestamp and one or more signatures. For example:

t=1492774577,v1=5257a869e7ec...,v0=6ffbb59b2300...
  • t is the time Stripe signed this delivery.
  • v1 is the signature that counts. Stripe computes an HMAC with SHA-256. The key is your endpoint's signing secret, the whsec_ value. The message is the timestamp, a ., and the raw request body joined together.
  • v0 is a fake scheme Stripe adds to test events. Stripe tells you to ignore every scheme except v1, which blocks downgrade attacks.

Stripe creates a new timestamp and signature for every delivery attempt, including retries. A retried event doesn't fail because the first attempt was hours ago.

How to verify a Stripe webhook signature

Use Stripe's official libraries where you can. Pass the raw body, the header, and the endpoint secret to the webhook helper. In Node that's stripe.webhooks.constructEvent, in Ruby Stripe::Webhook.construct_event, and in Python stripe.Webhook.construct_event. You get back a parsed event or an error, and your endpoint should answer an error with a 400.

To verify by hand, Stripe's docs give four steps:

  1. Split the header on , and then on = to get t and every v1 value.
  2. Build the signed payload from the timestamp as a string, a ., and the body exactly as received.
  3. Compute the expected HMAC SHA-256 with the endpoint secret as the key.
  4. Compare it with each v1 using a constant-time comparison, then check the timestamp is within your tolerance.

Why Stripe signature verification fails

Most signature errors have one of four causes.

  • A parsed body. Stripe needs the raw bytes. If your framework parses the JSON and you serialize it again, one changed space breaks the signature. Read the raw body on the webhook route before any middleware touches it.
  • The wrong secret. Each endpoint has its own secret, and the same URL has different secrets for test and live mode. stripe listen prints yet another secret for local forwarding.
  • Clock drift. Stripe's libraries reject a delivery whose timestamp is more than 5 minutes off your server's clock by default. Keep the clock synced with NTP. Never set the tolerance to 0, which turns the recency check off.
  • A rolled secret and old code. When you roll a secret, you can keep the old one active for up to 24 hours. During that window Stripe sends one signature per active secret, so accept any valid v1.

Stripe also publishes the IP addresses it sends webhooks from and recommends allowlisting them. That's a second layer on top of signatures, not a replacement.

Why it matters beyond security

A failed check costs more than one rejected request. Your endpoint returns an error, Stripe marks the delivery failed, and in live mode it retries for up to three days. If a deploy broke raw-body handling, every webhook in that window fails. That includes the invoice.created responses Stripe waits for before finalizing subscription invoices with automatic collection, which it can hold for up to 72 hours.

Engineering should alert on a jump in signature failures the same way it alerts on server errors, and check the endpoint's Event deliveries tab in Workbench after any deploy that touches the webhook route. Skipping verification is worse. An unverified endpoint can be tricked into marking unpaid orders as paid.

ChargeBell and signatures

If you only want Stripe activity in Slack, you can skip this layer. ChargeBell connects through Stripe's read-only OAuth, receives and verifies events on its side, and posts plain-English alerts, such as a refund alert with the amount and customer, to the channels you pick. There's no signing secret for you to store, rotate, or debug. For the trade-off between building this yourself and using a tool, see how to get Stripe payment notifications in Slack.

Get alerted in Slack

ChargeBell posts Stripe events to the Slack channel you pick, written in plain English.

Related terms