How do I set up Stripe Checkout for a simple one-time payment flow?
Merchant Payment Processing

How do I set up Stripe Checkout for a simple one-time payment flow?

6 min read

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.completed before 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 flow
  • line_items — what the customer is buying
  • price — the Stripe Price ID for the item
  • quantity — how many units the customer is purchasing
  • success_url — where Stripe sends the customer after payment
  • cancel_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_succeeded
  • checkout.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_id query 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_url and cancel_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