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 →
The four ways to run Checkout
| Option | ui_mode | What the customer sees |
|---|---|---|
| Full hosted page | hosted_page | A Stripe page at checkout.stripe.com (or your custom domain) |
| Full embedded page | embedded_page | The full Checkout UI inside your own page |
| Embedded form | form | A Checkout form styled with the Appearance API, inside your layout |
| Elements | elements | Individual 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 page | Embedded page | |
|---|---|---|
| Where payment happens | Stripe's page | Your page |
| Server returns | session.url to redirect to | session.client_secret for Stripe.js |
| success_url / cancel_url | Supported | Not allowed |
| return_url | Not used | Needed for redirect-based payment methods and completion |
| Custom domain | Supported (hosted page only) | Not applicable: it is already your domain |
| Branding | Dashboard branding settings | Dashboard branding settings |
| Front-end work | A button and a redirect | A page that mounts Checkout with Stripe.js |
| Payment methods, wallets, tax, discounts | Same Checkout features | Same Checkout features |
Which one to choose
- 1Start 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.
- 2Use 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.
- 3Use 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).
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?+
Is embedded Checkout better for conversion?+
Why is my success_url rejected?+
Can I use my own domain with Stripe Checkout?+
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