Skip to content

Embedded Checkout Fresh

Embed Stripe Checkout directly in your website so customers never leave your domain. The embedded checkout renders in an iframe using the Checkout Sessions API with ui_mode: 'embedded'.

How it works

  1. Your server creates a Checkout Session with ui_mode: 'embedded' and returns the client_secret
  2. Your client initializes the embedded checkout with the client_secret
  3. Stripe renders the checkout UI in your page
  4. On completion, Stripe redirects to your return_url

Server: create the session

javascript
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

app.post('/create-checkout-session', async (req, res) => {
  const session = await stripe.checkout.sessions.create({
    ui_mode: 'embedded',
    line_items: [
      {
        price_data: {
          currency: 'usd',
          product_data: { name: 'Pro Subscription' },
          unit_amount: 2000,
        },
        quantity: 1,
      },
    ],
    mode: 'payment',
    return_url: `${req.headers.origin}/return?session_id={CHECKOUT_SESSION_ID}`,
  });

  res.json({ clientSecret: session.client_secret });
});

Client: mount the checkout

html
<!-- Include Stripe.js -->
<script src="https://js.stripe.com/v3/"></script>
<div id="checkout"></div>
javascript
const stripe = Stripe('pk_test_YOUR_PUBLISHABLE_KEY');

// Fetch the client secret from your server
const { clientSecret } = await fetch('/create-checkout-session', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
}).then(r => r.json());

// Initialize and mount the embedded checkout
const checkout = await stripe.initEmbeddedCheckout({ clientSecret });
checkout.mount('#checkout');

React implementation

jsx
import { useState, useEffect } from 'react';
import { loadStripe } from '@stripe/stripe-js';
import { EmbeddedCheckoutProvider, EmbeddedCheckout } from '@stripe/react-stripe-js';

const stripePromise = loadStripe('pk_test_YOUR_KEY');

function CheckoutPage() {
  const [clientSecret, setClientSecret] = useState('');

  useEffect(() => {
    fetch('/create-checkout-session', { method: 'POST' })
      .then(r => r.json())
      .then(data => setClientSecret(data.clientSecret));
  }, []);

  return (
    <EmbeddedCheckoutProvider stripe={stripePromise} options={{ clientSecret }}>
      <EmbeddedCheckout />
    </EmbeddedCheckoutProvider>
  );
}

Handle the return

After checkout completes, retrieve the session to verify payment:

javascript
app.get('/return', async (req, res) => {
  const session = await stripe.checkout.sessions.retrieve(req.query.session_id);

  if (session.status === 'complete') {
    // Payment succeeded — show success state
    res.send('Payment successful!');
  } else {
    // Payment not complete
    res.send('Payment incomplete');
  }
});

Customization

Use the Appearance API to match your brand (70+ configurable settings):

javascript
const checkout = await stripe.initEmbeddedCheckout({
  clientSecret,
  // Appearance is set at session creation time in the Dashboard
  // or via the Appearance API when using Elements directly
});

Configure appearance in Dashboard → Settings → Branding, or pass appearance settings when creating elements-based integrations.

Key differences from hosted checkout

FeatureHostedEmbedded
Customer domainstripe.comYour domain
Customization15 settings70+ settings
Order summaryFullLimited
redirect on completionRequiredNot needed (return_url)
3DS handlingAutomaticAutomatic

Testing

Use 4242 4242 4242 4242 with any future expiry. See test cards for decline and 3DS scenarios.

Stripe API Reference - Self-contained docs reference, refreshed 2026-05-18