How to Secure Stripe Webhooks in SaaS: Signature Verification, Replay Attacks, and 2026 CVEs
Stripe webhooks are a common SaaS attack surface. This guide covers signature verification, replay attacks, the 2026 CVE bypass patterns, and pre-launch testing.
Stripe webhooks are the backbone of SaaS billing automation — they are how your application learns about successful payments, subscription changes, failed charges, and disputed transactions. Get them right and your billing flows run automatically. Get them wrong and you have built an open endpoint that any attacker can use to credit paid features to unpaying accounts, trigger refund flows, or confirm orders that were never paid. The security of a Stripe webhook endpoint is not a detail to revisit after launch — it directly determines whether your billing logic can be trusted.
Two CVEs disclosed in 2026 demonstrated exactly how this breaks in production. CVE-2026-41432, affecting a widely-deployed API billing platform, showed how an attacker could forge Stripe payment events and credit arbitrary quota to any account with no actual payment made — because the webhook secret was unset and the verification check was silently skipped. CVE-2026-9832, affecting a WooCommerce Stripe plugin, described the same root cause: when the webhook secret is not configured, the signature check is bypassed entirely, treating any incoming POST as a legitimate Stripe event. These are not edge cases. They are the predictable consequence of misconfigured webhook verification, and the same pattern appears across SaaS codebases built with every major framework and AI coding tool.
This guide covers how Stripe webhook signature verification works at the protocol level, what an attacker does when it is misconfigured, how to prevent replay attacks, and how to test your endpoint before you go live. For the complete pre-launch security checklist, see our SaaS pre-launch security checklist. For webhook delivery reliability — retry logic, dead-letter queues, and idempotency at the infrastructure level — the reliable webhook delivery guide covers the operational side.
How Stripe Webhook Signature Verification Works
Stripe signs every webhook event it sends using HMAC-SHA256. The computed signature travels in the Stripe-Signature request header alongside a timestamp. Your endpoint reconstructs the same computation and compares results — if they match, the event is genuine and unmodified. If they do not match, you reject the request before your handler processes any payload data.
- →Stripe-Signature header format: the header contains t=<timestamp> and one or more v1=<signature> values. The timestamp is a Unix epoch integer. The v1 value is an HMAC-SHA256 computed over the concatenation of the timestamp, a period, and the raw request body bytes, using your webhook signing secret as the key.
- →Always use the raw body buffer, not parsed JSON. Express, Fastify, and most other frameworks parse the request body before middleware runs. If you apply express.json() before your webhook handler, the bytes are transformed and the signature will fail to verify. Use express.raw({ type: 'application/json' }) or your framework's equivalent to preserve the unmodified body bytes.
- →Use the official SDK — never reimplement the check. Stripe's official libraries (stripe-node, stripe-python, stripe-go, stripe-ruby, stripe-php, stripe-java) expose a constructEvent method that handles signature verification and timestamp validation correctly in one call. The most common verification bugs appear when developers reimplement the check manually and miss the timestamp component.
- →The signing secret starts with whsec_ and is separate from your Stripe API keys. Configure one per webhook endpoint in the Stripe Dashboard. Store it as an environment variable — STRIPE_WEBHOOK_SECRET is the conventional name — never hardcoded in source code.
The Empty-Secret Bypass: What CVE-2026-41432 and CVE-2026-9832 Have in Common
Both 2026 CVEs shared the same structural failure: the webhook handler checked whether a signing secret was configured and, when it was not, skipped verification rather than rejecting the request. This is a fail-open pattern — one of the most dangerous design decisions in security-critical code. From an attacker's perspective the exploit is straightforward: identify the webhook endpoint URL, craft a JSON payload resembling a checkout.session.completed or invoice.payment_succeeded event, POST it directly, and the handler processes it as legitimate.
- →Fail-closed on missing configuration: if STRIPE_WEBHOOK_SECRET is not set, your handler must return HTTP 400 immediately and log a configuration error. A missing secret is not a degraded mode to handle gracefully — it is a misconfiguration that must block all processing.
- →Check environment variables on every deployment: a misconfigured staging secret that goes missing during a production deploy is the most common path to an empty-secret exposure in production. Add a startup check that confirms the webhook secret is present and non-empty before your server accepts connections.
- →Never share secrets across environments: your local development, staging, and production Stripe accounts each have separate webhook endpoints with separate signing secrets. Reusing a key across environments means a rotation or compromise in one affects all.
- →Rotate immediately on potential exposure: if your webhook endpoint URL or signing secret appeared in a public repository, a client-side bundle, or a leaked configuration file, rotate the secret in the Stripe Dashboard immediately. URL knowledge alone cannot forge a valid HMAC signature, but combined with an empty-secret misconfiguration it is sufficient for the attack.
Preventing Replay Attacks: Why Timestamp Validation Matters
A replay attack against a webhook endpoint works by capturing a legitimate signed event in transit and resubmitting it to trigger the handler a second time. If your handler is not idempotent and does not validate the event timestamp, a captured checkout.session.completed event can be replayed to credit a purchase multiple times. Stripe's signature verification includes a built-in replay defence, but it requires that you use it correctly.
- →Stripe's constructEvent enforces a 300-second timestamp tolerance by default: events where the Stripe-Signature timestamp is more than five minutes old are rejected with a SignatureVerificationError. This window is a deliberate security boundary. Never extend it to unlimited — doing so removes the replay protection entirely.
- →Store processed event IDs and skip duplicates: Stripe's guaranteed-delivery model means the same event may be delivered more than once when your endpoint returns a non-2xx response or times out. Record each processed event.id in your database before executing state-changing business logic, and skip any event.id you have already processed.
- →Acknowledge receipt immediately, process asynchronously: return HTTP 200 within Stripe's 30-second window before executing slow processing logic. Long-running handlers risk timing out, causing Stripe to retry and creating the duplicate-processing scenario the idempotency check is there to prevent. Enqueue the event to a job queue and return 200 immediately.
- →Do not trust payload fields before verifying the signature: event.type and all payload data are attacker-controlled values in any unsigned request. Read the event type only after constructEvent has confirmed the HMAC-SHA256 signature is valid.
Webhook Endpoint Security Beyond Signature Verification
Signature verification is the core control, but several surrounding decisions determine the overall security posture of your webhook integration. These are the patterns that appear most frequently as gaps in manual security reviews of SaaS billing implementations.
- →Isolate webhook routes from authenticated routes: webhook endpoints must be reachable without user session tokens, but they should not share an authentication middleware chain with your API. Route them through a dedicated middleware stack that applies only signature verification — no JWT parsing, no session lookup, no CORS headers that advertise the endpoint path.
- →Validate event type before processing: check that event.type matches a type your handler actually handles before executing any business logic. Unknown types should return HTTP 200 (Stripe treats non-2xx as failure and retries) but execute no handler logic. This prevents undocumented or future event types from inadvertently triggering your handler branches.
- →Log every webhook event with structured metadata: record the event ID, type, timestamp, and processing result for every received event. A structured audit trail is essential for debugging billing edge cases and for demonstrating to enterprise customers and auditors that billing events are tracked. Do not log raw request bodies if they may contain payment method details.
- →Add IP allowlisting as defense in depth: Stripe publishes its webhook IP ranges. Adding an allowlist at your load balancer or WAF does not replace signature verification but significantly reduces the attack surface from internet-wide probes against your endpoint URL.
Managing Webhook Secrets Across Environments and Teams
Secret management mistakes are the most common source of webhook misconfiguration in production. The technical verification logic is usually correct; the configuration that makes it run correctly in every environment is often not.
- →One secret per endpoint, per environment: your Stripe Dashboard creates separate webhook endpoints for each URL, each with its own signing secret. Use distinct secrets for development, staging, and production. A rotation or exposure in one environment must not force you to rotate all environments simultaneously.
- →Use a secrets manager for production values: STRIPE_WEBHOOK_SECRET should be injected at runtime from a secrets manager (AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault, or your CI/CD provider's encrypted environment secrets). Never commit it to your repository, not even in a .env.production file you expect to gitignore.
- →Rotate using Stripe's multi-signature window: Stripe supports adding a new signing secret to an endpoint while the old one remains active. During rotation, add the new secret, deploy a handler that validates against both secrets, verify the new secret is working, then remove the old one. This avoids downtime or missed events during the rotation.
- →Practice secret rotation in staging before you need it in production: the rotation procedure involves a coordinated deploy and a configuration change. Running through it under non-incident conditions in staging ensures the process is understood before you need it for a real exposure.
Testing Stripe Webhook Security Before Launch
Testing webhook security has two distinct goals: verifying that legitimate events are processed correctly, and verifying that illegitimate requests are rejected correctly. Most teams test the first extensively and the second not at all. Both must be in your test suite before launch. For the automated configuration baseline, DeployReady (npx deployready) scans your codebase and environment configuration for empty webhook secrets, hardcoded signing keys, and missing verification patterns in under two minutes.
- →Use the Stripe CLI for local integration testing: stripe listen --forward-to localhost:3000/webhook proxies real test-mode Stripe events to your local endpoint with valid Stripe-Signature headers. Use stripe trigger payment_intent.succeeded to fire specific event types on demand without real transactions.
- →Write an adversarial test for missing or invalid signatures: your test suite must include a test that POSTs a webhook payload with no Stripe-Signature header and confirms HTTP 400. A second test should send a payload with a signature computed against a different secret and confirm the same rejection. These tests verify your handler fails closed on bad input.
- →Test the empty-secret path explicitly: add a test that sets the webhook secret environment variable to an empty string and confirms the handler returns HTTP 400 rather than processing the event. This test prevents a regression to the CVE-2026-41432 fail-open pattern.
- →Verify idempotency by sending the same event ID twice: send an event, confirm it was processed, send the same event.id again, and confirm no duplicate business logic executed. Idempotency bugs cause double-billing and are hard to detect in production without an explicit regression test.
Frequently Asked Questions
What happens if I do not verify Stripe webhook signatures?
Any HTTP client can POST a forged payload to your webhook endpoint URL. Without signature verification, your handler cannot distinguish a legitimate Stripe event from a crafted request, and an attacker can trigger any webhook-driven action — crediting paid features, activating subscriptions, marking orders as paid — without making any real payment. CVE-2026-41432 demonstrated this exact attack against a production billing system where the webhook secret was unset and verification was silently bypassed.
How do I prevent replay attacks on Stripe webhooks?
Stripe's constructEvent function rejects events where the Stripe-Signature timestamp is more than 300 seconds old, blocking events captured in transit and replayed later. Never disable this timestamp tolerance or extend it to zero. Additionally, store processed event IDs in your database and skip re-processing any event.id you have already handled — Stripe may legitimately deliver the same event more than once as part of its guaranteed-delivery retry mechanism, and idempotent handlers prevent double-billing.
What is the Stripe-Signature header and what does it contain?
The Stripe-Signature header contains a Unix timestamp (t=) and one or more computed signatures (v1=). The v1 value is an HMAC-SHA256 of the concatenation of the timestamp, a period, and the raw request body bytes, signed with your webhook endpoint's signing secret. Multiple v1= values appear during a secret rotation window, allowing your handler to accept signatures computed against either the old or the new secret during a transition.
Can I test Stripe webhook security locally without live transactions?
Yes. The Stripe CLI provides stripe listen, which proxies test-mode webhook events to your local server with valid signatures. You can trigger specific event types with stripe trigger <event.type> without any real transactions. For adversarial security tests, you do not need the CLI — write unit tests that POST forged requests with missing or incorrect Stripe-Signature headers and assert that your handler returns HTTP 400 without executing any business logic. These tests run in CI against every commit.
How often should I rotate my Stripe webhook signing secret?
Rotate immediately on any potential exposure: a leaked environment file, a compromised deployment pipeline, or a departed team member who had access to production secrets. For systematic rotation in the absence of a known exposure, annual rotation is a common enterprise baseline. Use Stripe's multi-signature rotation window to add the new secret while the old one is still active, deploy an updated handler that accepts both, verify the new secret is working, then remove the old one — zero downtime, no missed events.
How Belsoft Helps Secure Stripe Billing Integrations
Billing logic is one of the highest-consequence areas in any SaaS product. A misconfigured webhook handler can silently translate into revenue loss or fraudulent account access — and unlike a UI bug, it produces no error logs until an attacker or penetration tester finds it. Our security and scalability audit covers the full Stripe integration surface: webhook signature configuration across environments, event handler logic for replay resistance and idempotency, secret storage practices, and test coverage for adversarial paths. We also review the broader data access layer — RLS policies, API authorization checks, and the application-layer controls that protect the billing records your webhook events create and modify.
DeployReady catches the configuration gaps automatically — empty webhook secrets, hardcoded signing keys, and missing security headers — in under two minutes, running entirely locally against your codebase. That automated baseline is the right starting point before any launch. A Belsoft audit goes further: we test the actual handler behavior under adversarial conditions, verify that your event processing is idempotent under Stripe's retry model, and check that the business logic your webhooks trigger is authorization-correct at the data layer. If you are preparing for an enterprise sales cycle or a SOC 2 audit and want to confirm your billing security posture, book a call and we will walk through your Stripe integration against the checklist enterprise security teams use.
“A webhook endpoint that skips signature verification is not an endpoint — it is an open API for your billing logic.”
Written by
Marco Silva
Security Engineer, Belsoft Solutions
More from the blog
Ready to partner?
Let's talk about your company.
30 minutes. No pitch. We talk through how you run today and where AI and automation would help.
Book a free audit
