Guide

Stripe hosted vs embedded Checkout: which one should you use?

By Aman Singh · Published · 8 min read

Stripe Checkout can run on a page Stripe hosts, embedded inside your own page, or as components you lay out yourself. The choice changes how much code you write, what you can brand, and where the customer types their card details. This guide compares Stripe hosted vs embedded Checkout using the current API, where the option is set with the ui_mode parameter.

Parameter names and rules follow Stripe's API reference as of October 2026, and the server code type-checks against the stripe Node.js library 23.0.0 (API version 2026-09-30.endive). It was not run against a live Stripe account.

Picked one? You will need webhooks next: Stripe webhooks in Next.js →

Both create a Checkout Session on your server. The difference is where the customer pays: on Stripe's page, or inside yours.

The four ways to run Checkout

Optionui_modeWhat the customer sees
Full hosted pagehosted_pageA Stripe page at checkout.stripe.com (or your custom domain)
Full embedded pageembedded_pageThe full Checkout UI inside your own page
Embedded formformA Checkout form styled with the Appearance API, inside your layout
ElementselementsIndividual payment components you place and style yourself

Older API versions and many tutorials use different names for these modes, so if an example fails validation, check which API version it was written for.

Hosted vs embedded, side by side

Hosted pageEmbedded page
Where payment happensStripe's pageYour page
Server returnssession.url to redirect tosession.client_secret for Stripe.js
success_url / cancel_urlSupportedNot allowed
return_urlNot usedNeeded for redirect-based payment methods and completion
Custom domainSupported (hosted page only)Not applicable: it is already your domain
BrandingDashboard branding settingsDashboard branding settings
Front-end workA button and a redirectA page that mounts Checkout with Stripe.js
Payment methods, wallets, tax, discountsSame Checkout featuresSame Checkout features

Which one to choose

  1. 1
    Start with the hosted page if you want to ship this week. It is the least code, Stripe maintains the page, and a short redirect rarely hurts conversion for software and services.
  2. 2
    Use the embedded page when keeping the customer on your domain matters, for example inside an app or a branded onboarding flow. The work is one extra page.
  3. 3
    Use the embedded form or Elements when the checkout must match a custom design closely or collect extra steps in your own UI. Budget for more front-end code and testing.

Server code for both

One function covers both styles. The hosted page returns a URL to redirect to; the embedded page returns a client secret that Stripe.js uses to mount Checkout on your page (see Stripe's Checkout quickstart for the front-end part).

lib/checkout-session.ts
import Stripe from "stripe";

// One server function for both Checkout styles. Hosted returns a URL to redirect
// to; embedded returns a client secret for Stripe.js to mount on your page.
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export async function createCheckoutSession(opts: {
  style: "hosted_page" | "embedded_page";
  priceId: string;
  mode: "payment" | "subscription";
  origin: string; // e.g. https://www.example.com
}) {
  const common = {
    mode: opts.mode,
    line_items: [{ price: opts.priceId, quantity: 1 }],
    ui_mode: opts.style,
  } satisfies Stripe.Checkout.SessionCreateParams;

  if (opts.style === "hosted_page") {
    const session = await stripe.checkout.sessions.create({
      ...common,
      // Only allowed for the hosted page.
      success_url: `${opts.origin}/thanks?session_id={CHECKOUT_SESSION_ID}`,
      cancel_url: `${opts.origin}/pricing`,
    });
    return { redirectTo: session.url };
  }

  const session = await stripe.checkout.sessions.create({
    ...common,
    // Embedded: the customer stays on your page; return_url is used after
    // redirect-based payment methods and on completion.
    return_url: `${opts.origin}/thanks?session_id={CHECKOUT_SESSION_ID}`,
  });
  return { clientSecret: session.client_secret };
}
  • Put {CHECKOUT_SESSION_ID} in the return URL so your thank-you page can look up the session and show the right message.
  • Do not pass success_url to an embedded session. The API rejects it; that is the most common error when switching styles.
  • Keep the secret key on the server. Only the client secret or URL goes to the browser.

After payment: fulfil from the webhook

The redirect back to your site can be skipped: a closed tab, a lost connection, a slow bank. The reliable signal is the checkout.session.completed webhook, which arrives either way. Set it up as described in Stripe webhooks in Next.js, and use checkout.session.expired if you want abandoned-cart emails.

Common questions

What is the difference between Stripe hosted and embedded Checkout?+
Hosted Checkout redirects the customer to a Stripe page and back; embedded Checkout shows the same payment form inside your own page. The features are largely the same; the difference is where payment happens and how much front-end code you write.
Is embedded Checkout better for conversion?+
Not automatically. Keeping customers on your domain can help trust, but the hosted page is fast, familiar and maintained by Stripe. Test both if checkout conversion is critical.
Why is my success_url rejected?+
success_url and cancel_url are only allowed for the hosted page. For embedded_page and elements sessions, use return_url instead.
Can I use my own domain with Stripe Checkout?+
Custom domains are available for the full hosted page. With the embedded options, Checkout already runs inside your own page.

Want Checkout and billing set up for you?

From the first Checkout session to renewals and failed payments, see Stripe integration or the wider payments and subscriptions service.

Next steps

Keep reading

Need a hand with this?

Tell me what you are working on. Free scoping call, fixed quote within 48 hours.

Book a call

← All guides