Stripe Embedded Checkout vs Hosted Checkout
By Shawn Michael Thomas II, Founder of Simple Easy Transactions · Published ·
Should I use Stripe's embedded Checkout or the Stripe-hosted Checkout page? If the customer should stay on my site, I use embedded Checkout: my server starts a Checkout Session and Stripe's payment form appears inside my page. If I want the fewest moving parts, I send them to the Stripe-hosted page, which Stripe runs from start to finish. Both are the same Stripe Checkout underneath, and both follow the same rule: nothing ships until Stripe's signed webhook confirms the payment.
AT A GLANCE
Embedded Checkout vs Stripe-hosted Checkout.
| Question | Embedded Checkout | Stripe-hosted Checkout |
|---|---|---|
| Where the customer pays | Inside your page, in a secure Stripe frame | On a Stripe page, after a redirect |
| What your server does | Starts a Checkout Session and gives your page the key it needs to show the form | Starts a Checkout Session and sends the customer to its link |
| Work on your site | Add Stripe's script and an empty spot on the page for the form | A button or a link, nothing else |
| Look and feel | Your page around it, your Stripe brand settings inside it | Stripe's page with your logo and colors |
| After paying | Stripe brings the customer back to your return page | Stripe sends the customer to your success page |
| What proves the payment | Stripe's signed webhook | Stripe's signed webhook |
HOW EACH ONE WORKS
How each one works.
Both options are Stripe Checkout: the same prebuilt payment page, with the same payment methods, discounts, taxes and receipts. The only real question is where that page lives. Stripe's documentation calls it the full-page option and lets you either redirect to it or embed it on your own site.
With the hosted page, your server creates a Checkout Session and sends the customer to the link Stripe returns. Stripe shows the page, takes the payment and sends the customer back to the success page you named. It's the least code you can write and still get a complete checkout.
With embedded Checkout, your server creates the same session in embedded mode and returns a client secret, a one-time key for that checkout. Your page loads Stripe.js and places Checkout in an empty container, and the form runs inside a secure Stripe frame, so card details go straight to Stripe and never touch your server. Stripe's guide warns against putting that form inside another frame, because some payment methods have to redirect to confirm.
There's a third way worth knowing: Checkout Sessions with Stripe Elements, where you place Stripe's payment fields inside a form you design yourself. It takes the most work and gives the most control; the Elements guide covers when that's worth it.
The 2026 renames
initEmbeddedCheckout, EmbeddedCheckoutProvider and the new names.
If you searched for initEmbeddedCheckout, the method has a new name. Stripe's Dahlia release of March 25, 2026 renamed stripe.initEmbeddedCheckout() to stripe.createEmbeddedCheckoutPage(). Stripe's changelog says the options and return type stay the same, and that calling the old name now throws an IntegrationError.
The Checkout Session changed with it. Its ui_mode values are now hosted_page, the default; embedded_page for embedded Checkout; elements for Checkout elements on your own page; and form for the Checkout form. Stripe's changelog says a session set to the old values hosted, embedded or custom will fail.
In React, Stripe's embedded Checkout quickstart still uses EmbeddedCheckoutProvider from @stripe/react-stripe-js: you pass it the promise from loadStripe and an options object with fetchClientSecret. Stripe's Stripe.js reference marks the older clientSecret option as deprecated in favour of fetchClientSecret, and calls onComplete only for sessions created with redirect_on_completion set to if_required.
The Elements side was renamed too: initCheckout() is now initCheckoutElementsSdk(), and CheckoutProvider is now CheckoutElementsProvider. A session with ui_mode form uses initCheckoutFormSdk() instead.
Two rules did not change. The Checkout Session is created on your server, because creating one needs your secret key; and Stripe's fulfillment guide says to use webhooks so that every payment is fulfilled, not only the ones whose customer reaches the return page.
- Create the Checkout Session with ui_mode embedded_page instead of embedded.
- Call stripe.createEmbeddedCheckoutPage({ fetchClientSecret }) instead of stripe.initEmbeddedCheckout().
- Mount it with checkout.mount("#checkout"); to show a new session, destroy the old instance first and create a new one.
- Keep fulfillment on the signed webhook, not the return page.
// Server: the secret key never leaves it
const session = await stripe.checkout.sessions.create({
ui_mode: "embedded_page",
mode: "payment",
line_items: [{ price: "price_123", quantity: 1 }],
return_url: "https://example.com/return?session_id={CHECKOUT_SESSION_ID}",
});
// Send session.client_secret to the page.
// Browser: Stripe.js loaded from https://js.stripe.com/dahlia/stripe.js
const checkout = await stripe.createEmbeddedCheckoutPage({ fetchClientSecret });
checkout.mount("#checkout");FIELD NOTES
What I've learned building this.
- This site's own checkout uses that third way. Open Payment lets the customer type the amount, and I wanted that field right next to Stripe's payment fields, so I used a Checkout Session with Stripe's Payment Element inside my own form.
- Stripe won't take a pay-what-you-want price in that setup, so I build the charge from the amount the customer typed and attach it to one saved product. Otherwise every payment would add a new product to the catalog.
- When a Stripe call fails, answer with your own error message. Mine once let the error escape; the server sent back an empty page, and the buyer saw a cryptic parser error instead of a sentence.
- My return page only says thank you. The order is recorded when Stripe's signed webhook arrives, and it's stored so that a repeated delivery can't record it twice.
- The $49 Embedded Checkout Starter Kit sold on this site is written against the new names: its Worker creates the session with ui_mode embedded_page, and its page loads Stripe.js from js.stripe.com/dahlia and calls stripe.createEmbeddedCheckoutPage with fetchClientSecret.
HOW TO CHOOSE
How I'd choose.
- Choose embedded Checkout when leaving your site would break the flow, such as paying inside an app, a members area or a multi-step booking.
- Choose the hosted page when you want the least code and a short trip to Stripe is fine for your customers.
- Choose Checkout Sessions with Elements when the payment fields have to sit inside a form you already own.
QUESTIONS
Questions people ask.
Why does stripe.initEmbeddedCheckout throw an IntegrationError?
Stripe renamed it to stripe.createEmbeddedCheckoutPage in its Dahlia release of March 25, 2026, and its changelog says calling the old name throws an IntegrationError. The options and return type are the same, so the fix is the new method name, plus ui_mode embedded_page on the Checkout Session.
What ui_mode does Stripe embedded Checkout use?
embedded_page. Stripe's changelog lists hosted_page, embedded_page, elements and form as the current values and says a session set to the old values hosted, embedded or custom will fail.
Is EmbeddedCheckoutProvider still used in React?
Yes. Stripe's embedded Checkout quickstart passes the promise from loadStripe and an options object with fetchClientSecret to EmbeddedCheckoutProvider from @stripe/react-stripe-js. On the Elements side, CheckoutProvider was renamed to CheckoutElementsProvider.
SOURCES
Where these facts come from.
Sources checked on . Providers change their products and rules, so the linked pages are the final word.
- Stripe Docs: Build a payments page (Checkout options)
- Stripe Docs: Embed a checkout page in your site
- Stripe Docs: Stripe Web Elements
- Stripe Docs: Fulfill orders
- Stripe Docs: Receive Stripe events in your webhook endpoint
- Stripe Changelog: initEmbeddedCheckout renamed to createEmbeddedCheckoutPage (2026-03-25.dahlia)
- Stripe Changelog: Checkout Session ui_mode values (2026-03-25.dahlia)
- Stripe Changelog: initCheckout renamed to initCheckoutElementsSdk (2026-03-25.dahlia)
- Stripe.js reference: Create an embedded Checkout page