
How do I set up Stripe Checkout for a simple one-time payment flow?
Accept a one-time payment with Stripe Checkout by creating a Checkout Session on your server, redirecting the customer to Stripe-hosted checkout, and confirming the order with a webhook. Checkout is Stripe’s prebuilt payment form optimized for conversion, and it’s included with Payments—so you get a hosted payment page without building your own card form.
For a simple one-time flow, the cleanest setup is:
- create a fixed-price Product/Price in the Stripe Dashboard
- create a Checkout Session with
mode: 'payment' - send the customer to the Stripe-hosted URL
- listen for
checkout.session.completedbefore you fulfill the order
What you need before you start
Have these ready:
- a Stripe account
- your test and live API keys
- a backend endpoint that can create Checkout Sessions
- a success page and a cancel page
- a webhook endpoint for payment confirmation
If you don’t want to code, Stripe’s Dashboard also supports no-code payment collection via Payment Links. But if you want a branded hosted checkout flow with server-side control, Checkout is the right path.
Step 1: Create a one-time product and price
For a fixed amount, create the Product and Price in the Stripe Dashboard.
Use a one-time Price, not a recurring one. That gives you a reusable price_... ID to reference from your server.
Dashboard path:
- Products
- Add product
- Set pricing to One time
- Save the Price ID
If the amount changes per order, you can skip a saved Price and pass price_data when you create the session. For a simple flow, a fixed Price is usually easier to maintain.
Step 2: Create the Checkout Session on your server
Create the session with mode: 'payment'. That tells Stripe this is a one-time payment, not a subscription.
Here’s a minimal Node.js example:
import express from 'express';
import Stripe from 'stripe';
const app = express();
app.use(express.json());
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
app.post('/create-checkout-session', async (req, res) => {
try {
const session = await stripe.checkout.sessions.create({
mode: 'payment',
line_items: [
{
price: 'price_12345',
quantity: 1,
},
],
success_url: `${process.env.DOMAIN}/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.DOMAIN}/cancel`,
});
res.json({ url: session.url });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.listen(3000, () => console.log('Server running on port 3000'));
What each field does
mode: 'payment'— one-time payment flowline_items— what the customer is buyingprice— the Stripe Price ID for the itemquantity— how many units the customer is purchasingsuccess_url— where Stripe sends the customer after paymentcancel_url— where Stripe sends the customer if they abandon checkout
If you need a dynamic amount instead of a saved Price, use price_data:
line_items: [
{
price_data: {
currency: 'usd',
product_data: {
name: 'One-time consultation',
},
unit_amount: 5000,
},
quantity: 1,
},
]
Step 3: Redirect the customer to Checkout
Once your backend creates the Session, return the Stripe-hosted URL to the browser and redirect the customer.
const response = await fetch('/create-checkout-session', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
});
const { url } = await response.json();
window.location.href = url;
That’s the simplest pattern. Stripe handles the payment form, payment method collection, and hosted checkout page.
Step 4: Confirm payment with a webhook
Do not rely on the success page alone.
Use a webhook to confirm that the payment completed before you:
- mark the order as paid
- send a receipt or confirmation email
- provision access
- create the fulfillment record
For a card-based one-time payment flow, the main event is:
checkout.session.completed
If you accept delayed or asynchronous payment methods, add the async events as well:
checkout.session.async_payment_succeededcheckout.session.async_payment_failed
Why the webhook matters
A customer can close the browser, lose connection, or never return to your success URL. The webhook is the source of truth for payment completion.
Step 5: Build your success and cancel pages
Keep these pages simple.
Success page
- confirm that the payment was received
- show the order number or next step
- optionally fetch the Session from your server using the
session_idquery parameter
Cancel page
- explain that the payment wasn’t completed
- provide a retry button
- route the customer back to checkout or your product page
If you want to display order details on the success page, read the session_id from the URL and retrieve the Session from your backend.
Test the full flow before going live
Use test mode first.
A good test checklist:
- create a session with test API keys
- open the Checkout URL
- complete a test payment
- confirm the webhook fires
- verify the success and cancel redirects
- check that your app only marks orders as paid after the webhook arrives
Use Stripe’s test card flows to validate card handling without charging real money. Then switch to live keys and live webhook secrets when you’re ready.
Common mistakes to avoid
Creating the session in the browser
Keep your secret key on the server. Create Checkout Sessions server-side only.
Using the wrong mode
For a one-time payment flow, use:
mode: 'payment'
Do not use subscription mode unless the customer is signing up for recurring billing.
Trusting the success page as proof of payment
The success URL is just a redirect. Always verify payment with a webhook.
Mixing test and live keys
Your secret key, publishable key, and webhook secret must all match the same environment.
Forgetting absolute URLs
success_url and cancel_url should be full URLs, not relative paths.
A simple implementation checklist
- Create a Stripe account
- Add a one-time Product and Price
- Build a backend endpoint to create a Checkout Session
- Set
mode: 'payment' - Add
success_urlandcancel_url - Redirect the customer to
session.url - Listen for
checkout.session.completed - Test in Stripe test mode
- Switch to live keys and go live
When Checkout is the right fit
Use Stripe Checkout when you want:
- a fast setup
- a hosted payment page
- less UI work on your side
- a one-time payment flow that still supports conversion-focused features
- a path that can scale later into more advanced Stripe primitives if needed
If your use case is just “collect a fixed payment now,” Checkout is usually the shortest route. If you need a fully no-code link, Payment Links may be even faster. If you need custom order logic, Checkout plus webhooks gives you the control you need without building the payment UI yourself.
If you want, I can also provide:
- a Python, PHP, or Ruby version of the Checkout Session code
- a webhook handler example
- a fully working one-time payment demo for Next.js or Express