Your Stripe Webhook Trusts Strangers. It Shouldn't.
Your Stripe webhook endpoint is a URL. It accepts POST requests. When Stripe sends a payment event, your app processes it — activates a subscription, grants credits, marks an order as fulfilled.
But Stripe isn't the only one who can send a POST request to that URL.
Without signature verification, anyone who knows your webhook endpoint can send a fake payment event. Your app processes it the same way it processes a real one. A forged checkout.session.completed event grants premium access. A forged invoice.payment_succeeded event extends a subscription. No payment was made. No money changed hands. Your app just trusted the request because it arrived at the right URL.
This is not a theoretical attack. CVE-2026-21894 confirmed this exact vulnerability in n8n's Stripe integration — missing signature verification allowed any HTTP client to trigger real workflows by sending forged Stripe events. And in AI-generated apps, missing webhook verification is one of the most frequently observed billing failures.
Who This Is For
- Founders who integrated Stripe through AI tools and aren't sure if webhook signature verification is in place
- Developers who built a webhook handler that "works" but may be skipping the
stripe.webhooks.constructEvent()call - Teams who disabled signature verification during development ("temporarily") and may not have re-enabled it
- Anyone whose app grants access, credits, or fulfillment based on incoming webhook events
If your webhook handler processes events without verifying the Stripe-Signature header, your financial workflows are running on the honor system.
What Founders Experience
- Everything works in testing. Stripe sends events, the app processes them, subscriptions activate. The happy path is flawless.
- There are no alerts when it's exploited. A forged event looks exactly like a real one to your handler. There's no error, no failed payment, no Stripe notification. The app simply grants access it shouldn't.
- Discovery is accidental. The founder notices when premium user counts don't match Stripe revenue. Or when a security researcher demonstrates the bypass. Or when someone posts the exploit publicly.
- The reverse problem is worse. Some founders disable signature verification because it keeps failing — usually due to body parsing issues. They intend to fix it later. Meanwhile, the endpoint is wide open. The app still "works" because real Stripe events are still processed. The vulnerability is invisible.
What's Actually Happening
Stripe signs every webhook event with a Stripe-Signature header using your endpoint's signing secret. Your handler is supposed to verify this signature using stripe.webhooks.constructEvent() before processing the event. If verification fails, the event should be rejected.
Three failure modes break this:
1. Signature Verification Never Implemented
The simplest case. The AI generates a webhook handler that reads the event body, parses the JSON, and processes it — without ever calling constructEvent(). The handler works perfectly with real Stripe events. It also works perfectly with fake ones.
This is what caused CVE-2026-21894: n8n's Stripe integration accepted any POST request as a legitimate payment event. Forged events could trigger real workflows, including premium access, subscription changes, and downstream data updates.
2. Body Parsing Destroys the Signature
This is the most common failure in Next.js applications. Stripe's signature verification requires the raw, unparsed request body. In Next.js, verification often fails because the webhook handler doesn't preserve the raw body in the format Stripe expects. The exact fix depends on whether you're using the App Router (request.text()) or Pages Router (disabling the built-in body parser).
This generates hundreds of Stack Overflow questions. The dangerous "fix": developers disable verification entirely rather than fixing the body parsing.
3. Verification Disabled During Development, Never Re-Enabled
During development, the Stripe CLI uses a different signing secret than the production dashboard. Developers who test locally often struggle with mismatched secrets. The quick fix — commenting out the verification — becomes permanent when the code deploys to production.
One documented case: a team disabled signature verification for "just a few hours" while debugging a client's webhook. The webhook URL leaked in a GitHub commit. Three fake payment events were sent within hours.
What This Puts at Risk
Free access at scale. An attacker who discovers your webhook URL can forge payment events programmatically. Each forged event creates a premium user who never paid. This can be automated — hundreds of fake activations in minutes.
Revenue manipulation. Beyond granting free access, forged events can cancel legitimate subscriptions, modify payment records, or trigger refund workflows — depending on what events your handler processes.
Stripe compliance risk. Stripe's integration documentation explicitly requires webhook signature verification. Operating without it may affect your standing if a dispute or security incident is investigated.
Cascading workflow damage. If your webhook handler triggers downstream operations — sending confirmation emails, provisioning resources, updating CRM records, granting API credits — every forged event triggers all of those side effects.
How Trust Score Detects It
Trust Score checks four patterns that protect webhook integrity:
BIL-02: Webhook signature verification. Scans your webhook handler for the stripe.webhooks.constructEvent() call (or equivalent verification pattern). This is the primary check — without it, the endpoint accepts any POST request as a legitimate Stripe event.
BIL-03: Raw body preservation in webhook. Checks that the webhook handler preserves the raw request body Stripe signs, rather than parsing or transforming it before verification. In Next.js, the safe implementation depends on your router and runtime setup.
BIL-04: Idempotent webhook processing. Verifies that your handler has protection against processing the same event twice. Stripe retries events on failure — without idempotency, a single payment can trigger duplicate fulfillment. (This is closely related but covered in detail on the Double-Charge Spiral page.)
BIL-16: Webhook-only fulfillment. Checks that subscription activation and access granting happen only through verified webhook events — not through client-side callbacks, success URLs, or direct database writes.
Real Incidents
CVE-2026-21894 — n8n Stripe webhook bypass (January 2026). The popular workflow automation platform had no Stripe webhook signature verification. Any HTTP client could send forged Stripe events that triggered real workflows: free premium access, subscription modifications, and data injection into connected systems.
Webhook URL leaked in GitHub commit. A development team disabled signature verification while debugging. The webhook URL was in a commit that was pushed to a public repository. Within hours, three fake payment events were processed. "We disabled signature verification on a client's Stripe webhook for 'just a few hours' while debugging..."
$50K lost from 3 days of silent webhook failure (2026). Not forged events, but a related pattern: a deployment changed the webhook route path, Stripe hit the old URL and got 404s, retried for 72 hours, then disabled the endpoint. Three days of zero subscription activations during an active marketing window. No internal alert. "Stripe had been retrying the webhooks. The dashboard showed the failures clearly. Nobody was watching."
$79/month Pro plan unlocked via forged webhook (LinkedIn PoC). A security researcher demonstrated a complete subscription bypass by sending a crafted POST to the webhook endpoint. "The server assumed the payment was done. No Stripe validation. No webhook confirmation check. No session verification."
DryRun Security finding (March 2026). Analysis found that 87% of AI-generated pull requests introduced at least one security vulnerability. Webhook handlers were consistently missing signature verification — none of the tested AI coding agents produced a fully secure Stripe integration.
Detection: How to Check Your Own App
Check 1: Is signature verification present?
Search your codebase for the verification call:
# Search for Stripe signature verification
grep -r "constructEvent\|webhooks.construct" --include="*.ts" --include="*.tsx" --include="*.js"
Interpretation: If no results are found, that's a strong warning sign. Your webhook handler may not be verifying signatures, or verification may be hidden behind a helper or abstraction that needs manual review.
Check 2: Is the raw body preserved?
If you're using Next.js App Router, check how the body is read:
# Check if webhook route reads raw body
grep -r "req.text()\|request.text()\|getRawBody\|bodyParser.*false" --include="*.ts" --include="*.tsx" --include="*.js"
Interpretation: If your webhook handler uses req.json() or doesn't explicitly read the raw body, signature verification will fail silently — and may have been disabled as a "workaround."
Check 3: Is the signing secret correct for production?
The Stripe CLI uses a different signing secret (whsec_...) than your production webhook endpoint. If your production .env still has the CLI secret, verification will fail on real events.
Check your Stripe Dashboard → Developers → Webhooks → your endpoint → Signing secret, and compare it with your production environment variable.
Related Launch Risks
- Anyone Can Upgrade to Pro for Free. — Client-side billing trust and unverified webhooks are two different paths to the same result: access without payment.
- Stripe Will Retry Until It Breaks You. — Without idempotent processing, legitimate Stripe retries cause duplicate fulfillment.
- Users Bypass Payment by Bookmarking One URL. — Fulfillment through success URLs instead of verified webhooks.
- One Exposed Key Gives Strangers Full Access to Your Database. — A leaked Stripe secret key compounds webhook vulnerabilities — the attacker has both the key and knowledge of your API.
FAQ
Stripe sends events to my endpoint and everything works. Doesn't that mean it's secure?
No. If your endpoint accepts any POST request — not just signed Stripe events — it works for Stripe and for anyone else. The test isn't "does it work with Stripe." The test is "does it reject a request that isn't from Stripe."
Why do so many developers disable signature verification?
Usually because of the body parsing problem. In Next.js, signature verification often fails because the handler doesn't preserve the raw body in the format Stripe expects. The exact fix depends on whether you use the App Router or Pages Router. Rather than figuring out the correct approach, developers disable verification "temporarily" — and AI tools rarely generate it correctly.
Can someone actually find my webhook URL?
Your webhook URL is not public by default, but it often appears in codebases, infrastructure configs, CI output, or leaked commits. Once exposed, obscurity is not a defense.
What exactly can an attacker do with a forged webhook event?
Anything your handler does in response to a Stripe event: activate subscriptions, grant credits, extend trials, mark orders as fulfilled, trigger confirmation emails, update CRM records. The forged event is processed identically to a real one.
We use Stripe's hosted checkout. Are we still at risk?
Stripe's hosted checkout handles the payment side securely. But your webhook handler — which processes the events after checkout — is your code. If that handler doesn't verify signatures, the checkout being hosted doesn't help. The vulnerability is in how you process events, not how you collect payments.