A payment flow is the one place where a security gap turns directly into lost money, either yours or a fraudster's. AI tools are good at getting Stripe to work and bad at getting it safe, because the safe version has steps the demo does not need. Before you flip on real payments, check these four things, in order of how much they matter.
1. Verify your webhook signatures
This is the big one. Stripe tells your app about completed payments through a webhook, a public URL anyone can POST to. If your handler grants access on a "payment succeeded" event without verifying it came from Stripe, anyone can forge that event and get access for free. Verify the signature against the raw body with your webhook secret, and reject anything that fails. The full how-to is in webhook signature verification.
Want to see exactly what Prbl flags? Watch it scan a demo app, no repo or account needed.
See a live scan →2. Keep the secret key server-side
Stripe gives you two keys. The publishable key (pk_) belongs in the browser. The secret key (sk_) can create charges and read your account, and must never leave your server. AI tools sometimes inline the secret key to make a call work, which ships it to every visitor. If a sk_live_ key is in your bundle, rotate it now and move the call server-side; see why a key in an env var still leaks.
3. Decide the amount on the server
Never charge an amount the client sent you. A user can change it and pay a penny. Have the client tell you what they are buying, and let your server look up the price from your own data:
// the risky pattern: trust the client's amount
const session = await stripe.checkout.sessions.create({
line_items: [{ price_data: { unit_amount: req.body.amount }, quantity: 1 }],
});
// safe: server decides the price from a product id
const product = PRODUCTS[req.body.productId]; // your own data
const session = await stripe.checkout.sessions.create({
line_items: [{ price: product.stripePriceId, quantity: 1 }],
});4. Let Stripe handle the card data
Do not store card numbers. Stripe's elements and hosted checkout handle the card through its own systems, so the raw data never touches your server, which keeps you out of most compliance scope. If your app is holding card details, switch to Stripe's flows; the safest card data is the data you never store.
Frequently asked questions
What is the most common payment mistake in AI-built apps?
Trusting a webhook without verifying its signature. The generated handler receives a 'payment succeeded' event and grants access, but never checks the event actually came from Stripe. Since the webhook URL is public, anyone can POST a fake event and get access without paying. Verifying the signature against the raw body with your webhook secret is the fix, and it is the single highest-value check.
Is it safe that my Stripe key is in the frontend?
It depends which key. The publishable key (pk_) is meant to be in the browser and is fine there. The secret key (sk_) must never reach the client; it can create charges and refunds and read your account. If you find an sk_ key in your bundle, rotate it immediately and move every call that uses it to your server. AI tools sometimes inline the secret key to make a call work.
Why does verifying the amount on the server matter?
Because anything the client sends can be tampered with. If your server creates a charge using a price the browser passed in, a user can change it and pay one cent. The server should decide the amount from your own product data, keyed by a product id, never trust a price submitted by the client. Let the client say what they want to buy, not what it costs.
Do I need to store card details securely?
You should not store card details at all. Stripe handles the card data through its own elements and tokens, so the raw card number never touches your server, which keeps you out of most compliance scope. If you find yourself storing card numbers, stop and use Stripe's hosted or embedded flows. The safest card data is the card data you never hold.
Check your payment flow
Two of these, a leaked secret key and an unverified webhook, a scan can flag directly. Before you take real money, it is worth confirming neither is in your app. Run a free scan and check the parts of your payment flow that ship to the browser.