Stripe Integration
Part of the Full Stack Developer Roadmap
In short
Taking money online sounds scary, but with Stripe your server never touches a card number. Your job is to create the checkout, then trust only Stripe's webhook when deciding an order is paid.
Adding real payment processing to a full-stack app — a frontend checkout UI backed by a backend that creates charges/subscriptions and never touches raw card details directly.

Think of it like this
The card machine at your table
At a good restaurant, the waiter brings the card machine to your table. The owner never sees your PIN, and the bill is marked paid only when the machine says 'approved', not when the customer says 'done, I paid'. Stripe is the machine, and its webhook is that approval slip.
Watch it happen
Checkout to 'paid', the safe way
POST /api/checkout
1/7The user clicks Pay. The browser sends only the cart's item ids, never prices.
The big ideas
What you need to know
Cards never touch your server
Stripe Checkout or Elements collect card details on Stripe's side, which keeps most card-security work off your plate.
Prices come from the server
Never trust an amount sent by the browser. Look up prices or Stripe Price ids on the server.
Webhooks decide 'paid'
The success redirect can be skipped or faked. The signed webhook event is the reliable signal.
Test mode first
Use test keys and the test card 4242 4242 4242 4242 with any future date. The Stripe CLI forwards webhooks to localhost.
See it in code
Walk through the code
1import Stripe from "stripe";2const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);3 4app.post("/api/checkout", requireUser, async (req, res) => {5 const order = await createPendingOrder(req.user.id, req.body.itemIds); // prices from the DB6 const session = await stripe.checkout.sessions.create({7 mode: "payment",8 line_items: order.items.map((i) => ({ price: i.stripePriceId, quantity: i.qty })),9 success_url: `${process.env.APP_URL}/orders/${order.id}`,10 cancel_url: `${process.env.APP_URL}/cart`,11 metadata: { orderId: String(order.id) },12 });13 res.json({ url: session.url });14});15 16// Webhook: stripe.webhooks.constructEvent(rawBody, req.headers["stripe-signature"], WEBHOOK_SECRET)1/4Your own helper saves a pending order using prices from your database, not from the request.
The webhook route needs express.raw({ type: 'application/json' }) so the signature check sees the exact bytes Stripe sent.
Save yourself the pain
Common mistakes
Oops
Marking the order paid when the success page loads.
Do this instead
The success page only shows status. Mark it paid in the webhook, after verifying Stripe's signature.
Oops
Parsing the webhook body with express.json() before verifying it.
Do this instead
Use express.raw on that one route. The signature check fails once the body has been parsed and re-serialised.
Oops
Putting the secret key in frontend code.
Do this instead
Only the publishable key may reach the browser. The secret key lives in backend env vars.
Your mission
Take a test payment
Quick check
A user closes the tab right after paying, before the success page loads. How does your app learn the order is paid?
Go deeper
- 1Build a checkout flow using Stripe's test mode and Stripe Elements on the frontend.
- 2Create the actual charge/subscription from your backend, never the frontend, using your secret key.
- 3Handle Stripe webhooks on the backend to confirm payment success reliably.
More topics at this stop
Stuck on this topic? Ask an Insider
Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.