Stripe test-mode events cannot grant live entitlements: livemode is checked or test and live use separate endpoints and secrets
Why it matters
Every Stripe event carries a livemode boolean, and test and live mode use different webhook signing secrets. The failure is an app that serves both modes from one endpoint with one secret variable, typically because the developer pasted the test secret into production, or registered a single endpoint that receives both. Once production accepts test events, any state they create is indistinguishable from a real purchase: a checkout.session.completed generated in Stripe's test mode, which costs nothing and can be produced by anyone holding that endpoint's test signing secret (your team, a test card, a local stripe listen run, a leaked secret), grants the same plan as a real payment. The same shared setup also means a developer's local test run can mutate production entitlements. The safe designs are either separate endpoints and separately named secrets per environment (STRIPE_WEBHOOK_SECRET_LIVE/_TEST, or per-deployment values) plus a livemode check, or at minimum an explicit guard that rejects events whose livemode does not match the environment. Stripe documents livemode on the Event object for exactly this purpose. The harm is entitlements and revenue records that do not correspond to money.
Severity rationale
High because a test-mode event accepted by the production handler lets test cards, local runs, or anyone holding the test signing secret create "paid" subscriptions that the live app honors, so exposure is mostly your own team and tooling plus any leak of that secret.
Remediation
Reject events whose mode does not match the environment, and keep test and live secrets separate. Add a guard after verification: if (event.livemode !== (process.env.VERCEL_ENV === 'production')) return new Response('mode mismatch', { status: 200 }) (acknowledge so Stripe does not retry, but do not act). Register separate webhook endpoints for test and live mode in the Stripe Dashboard and load each secret from a differently named variable or per-environment value.
const isLive = process.env.VERCEL_ENV === 'production'
if (event.livemode !== isLive) return new Response('ignored: mode mismatch', { status: 200 })
DetectionPro
The detection method for this pattern is part of AuditBuffet Pro.
The full method (what to look for, pass and fail conditions, and how results get reported) ships inside the Stripe Webhooks & Billing Integrity audit. The pattern definition, severity rationale, and remediation remain openly licensed under CC-BY-4.0.
External references
- owasp:2021 · A08 — Software and Data Integrity Failures
- external · stripe-event-object-livemode — Stripe API reference: Event object livemode attribute
- external · stripe-webhooks-test-vs-live — Stripe docs: Webhook endpoints and signing secrets differ between test and live mode
Taxons
History
- 2026-10-05·v1.0.0·Initial authoring — livemode guard or separate endpoints/secrets for test vs live events.·by stripe-billing-integrity-v1-build