Skip to content
BuildThenTestedSee packages

Price or plan taken from the client

A buyer can edit the request in their browser, pay one cent for the top plan, and your app records it as a full purchase. Everything between the pay button and the money arriving. Check it yourself with the steps below; our test adds what a script and a second account can find.

What it looks like

// POST /api/checkout. Body comes from the browser.
const { amount, plan } = await req.json();
const session = await stripe.checkout.sessions.create({
  mode: "payment",
  line_items: [{ price_data: { currency: "usd", unit_amount: amount,
    product_data: { name: plan } }, quantity: 1 }],
});
A server route that builds the Stripe Checkout Session (the object that defines what the customer pays for) from an amount and plan name sent by the browser.

What happens

A buyer can edit the request in their browser, pay one cent for the top plan, and your app records it as a full purchase.

Where it shows up

Descriptive, not a verdict: builders and stacks whose defaults make this reachable, or that a public incident names. Each hub states the tool's own security features.

Seen in public

Two days after announcing a SaaS built with Cursor and no hand-written code, its founder reported subscription bypasses, maxed-out API keys and unwanted database writes; he shut the app down three days later.

  • 2025-03-15 Launch announcement
  • 2025-03-17 Report of subscription bypass and API key abuse
  • 2025-03-20 Shutdown announcement

Mar 2025 · Leo (Enrichlead founder) · all sources

Check it yourself

Uses your builder's dashboard and your browser. No tools to install.

  1. Open your app's pricing page in Chrome, press F12 to open developer tools (the browser's built-in inspector), and select the Network tab.
  2. Click a buy or subscribe button and find the request your app sends before Stripe's page opens. It is often named checkout, create-checkout-session or similar.
  3. Select it and read the Payload or Request tab.
  4. Bad result: the payload contains an amount, a price in cents or a plan name that the server appears to use as sent. A good result sends only a plan key or a Stripe price ID starting price_, and the server chooses the amount.

What we check

We replay the checkout request with altered amounts, plan names and price IDs, complete the session in test mode, and read back which plan your database granted and what Stripe recorded.

Fix direction

Keep prices in Stripe as Price objects and let the server map a plan key from the browser to a fixed price_ ID, treating any amount from the client as untrusted. After payment, grant the plan from the verified webhook event's line items rather than from what the browser claimed.

Mechanism documented in: How Checkout works (verified 2026-09-21); Create a Checkout Session (verified 2026-09-21).

Covered by

Ready-to-charge check ($390, 48 hours) and Full audit ($1,900, 5 business days)

Both include a re-check after your fixes.

Compare packages

Related patterns

Questions

price taken from client stripe checkout

Stripe lets the server create a Checkout Session either from a stored Price ID or from inline price_data. When the inline amount comes straight from the browser request, anyone can change it before sending. The check is to inspect the checkout request in developer tools and see whether an amount travels with it.

can users change the price in stripe checkout

Not on Stripe's hosted page, but they can change what your server asks Stripe to charge if your server copies the amount from the request. The founder of one SaaS built with Cursor reported subscription bypasses two days after launch (Leo (Enrichlead founder), 2025); replaying your own checkout request with a different amount is the quickest way to know where you stand.

how do i stop users editing the plan before checkout

Send only a plan key such as pro-monthly from the browser, look up the matching Stripe Price ID on the server, and ignore any amount or name in the request. Then activate the plan from the verified webhook, using the line items Stripe reports rather than the plan the browser said it bought.

Back to the catalogue, or run the self-check for your stack.