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...tis the time Stripe signed this delivery.v1is the signature that counts. Stripe computes an HMAC with SHA-256. The key is your endpoint's signing secret, thewhsec_value. The message is the timestamp, a., and the raw request body joined together.v0is a fake scheme Stripe adds to test events. Stripe tells you to ignore every scheme exceptv1, 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:
- Split the header on
,and then on=to gettand everyv1value. - Build the signed payload from the timestamp as a string, a
., and the body exactly as received. - Compute the expected HMAC SHA-256 with the endpoint secret as the key.
- Compare it with each
v1using 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 listenprints 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.