Stripe Checkout subscriptions
Use this guide when Stripe Checkout should collect the initial payment method and create the
Subscription. Your authenticated merchant server calls openCheckout from
@proxy-checkout/stripe-server-js with its own injected Stripe client. Proxy’s API, webhook
workers, replay workers, and reconciliation workers never create or mutate Checkout Sessions,
Subscriptions, Invoices, PaymentIntents, refunds, or disputes.
Choose the payer presentation
Section titled “Choose the payer presentation”Hosted presentation
Section titled “Hosted presentation”Hosted Checkout is the recommended starting point. It redirects to Stripe and
returns a URL from openCheckout. The technical ID is SUB-CO-H.
proxy stripe doctor --path SUB-CO-H --format jsonInstall the Stripe server package before copying this path's backend example. The exact version comes from the same canonical capability manifest as stripe doctor.
npm install --save-exact stripe@12.18.0
pnpm add --save-exact stripe@12.18.0
yarn add --exact stripe@12.18.0
Embedded presentation
Section titled “Embedded presentation”Choose embedded when your page mounts Stripe Embedded Checkout. Use
commercialMode: "subscription" and uiMode: "embedded"; openCheckout
returns a Checkout client secret. The technical ID is SUB-CO-E. Follow the
embedded client-secret lifecycle while
keeping the subscription params and lifecycle authority in this guide.
proxy stripe doctor --path SUB-CO-E --format jsonInstall the Stripe server package before copying this path's backend example. The exact version comes from the same canonical capability manifest as stripe doctor.
npm install --save-exact stripe@14.0.0
pnpm add --save-exact stripe@14.0.0
yarn add --exact stripe@14.0.0
Install the Stripe browser packages before copying the client example. These exact versions come from the same canonical capability manifest as stripe doctor.
npm install --save-exact @stripe/stripe-js@2.1.8 @stripe/react-stripe-js@2.3.2
pnpm add --save-exact @stripe/stripe-js@2.1.8 @stripe/react-stripe-js@2.3.2
yarn add --exact @stripe/stripe-js@2.1.8 @stripe/react-stripe-js@2.3.2
Custom Elements presentation
Section titled “Custom Elements presentation”Choose custom when your page composes an Elements-based payment UI backed by a
Checkout Session. Use commercialMode: "subscription" and uiMode: "custom";
openCheckout returns a Checkout client secret. The technical ID is
SUB-CO-C. This is still Checkout-created subscription routing, not a direct
SetupIntent subscription. Follow the
custom client-secret lifecycle.
proxy stripe doctor --path SUB-CO-C --format jsonInstall the Stripe server package before copying this path's backend example. The exact version comes from the same canonical capability manifest as stripe doctor.
npm install --save-exact stripe@18.0.0
pnpm add --save-exact stripe@18.0.0
yarn add --exact stripe@18.0.0
Install the Stripe browser packages before copying the client example. These exact versions come from the same canonical capability manifest as stripe doctor.
npm install --save-exact @stripe/stripe-js@7.0.0 @stripe/react-stripe-js@3.6.0
pnpm add --save-exact @stripe/stripe-js@7.0.0 @stripe/react-stripe-js@3.6.0
yarn add --exact @stripe/stripe-js@7.0.0 @stripe/react-stripe-js@3.6.0
Open Checkout from the merchant server
Section titled “Open Checkout from the merchant server”Reserve before Stripe creation by using the shared openCheckout owner. It injects Proxy’s
contract-v2 metadata into both the Checkout Session and subscription_data, uses one deterministic
Stripe idempotency key, retrieves complete provider-authored Checkout line evidence, and attaches
the returned Session as the acquisition root.
import Stripe from "stripe";import { openCheckout } from "@proxy-checkout/stripe-server-js";
const hostedSubscriptionStripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { apiVersion: "2022-11-15",});
const checkout = await openCheckout({ commercialMode: "subscription", compatibility: { apiVersion: "2022-11-15", serverSdkVersion: "12.18.0", }, pricingMode: "fixed", proxy, proxySessionId, stripe: hostedSubscriptionStripe, uiMode: "hosted", cartSchema, reconcileCart: ({ cart }) => reconcileMerchantCart(cart), buildCheckoutSessionParams: ({ cart }) => ({ mode: "subscription", line_items: [ { price: cart.recurringPriceId, quantity: cart.quantity }, ...(cart.initialOneTimePriceId ? [{ price: cart.initialOneTimePriceId, quantity: 1 }] : []), ], success_url: `${merchantOrigin}/checkout/complete`, }),});
if (checkout.outcome === "ready") { if (checkout.presentation?.kind !== "redirect") { throw new Error("Hosted Checkout did not return a redirect URL"); } return Response.redirect(checkout.presentation.url, 303);}
return Response.json({ cart: checkout.cart, outcome: checkout.outcome, sessionStatus: checkout.sessionStatus,});This client is for the hosted subscription example. For SUB-CO-E, use the 2023-10-16 embedded client shown in the embedded guide. For SUB-CO-C, use the 2025-03-31.basil custom client shown in the custom guide. In every case, the Stripe client API and compatibility.apiVersion must match each other and meet the selected path’s minimum reported by stripe doctor.
For hosted Checkout, omit provider ui_mode from the Stripe params and keep Proxy’s
surface-independent uiMode: "hosted" selection. Stripe 12.18.0 has no create-time ui_mode
field, while the current provider response reports its default hosted surface as hosted_page;
the shared adapter normalizes both to Proxy’s hosted presentation. Both lanes require at least one
fixed recurring Price, support recurring-only or mixed initial carts, and support a free trial with
payment_method_collection: "if_required". Omit initialOneTimePriceId when no one-time Price is
due on the first invoice.
For automatic tax, promotion codes, shipping, or another provider-finalized amount, select
pricingMode: "provider_finalized". Checkout line identity, quantity, and recurring semantics
still have to match the exact merchant cart. The later coherent initial Invoice—not Checkout total
or line ordinal—finalizes the subscription payment and fee basis.
Do not pass payment_intent_data, Connect fields, metered Prices, send_invoice, a Subscription
schedule, or automatic after-expiration recovery. A preselected Stripe customer is also rejected:
it could turn one invited viewer into the shared purchase root. customer_email is allowed only
when it exactly matches the immutable beneficiary email already stored on the Proxy session. An
all-one-time cart is rejected before Proxy reserves the acquisition, so an invalid subscription
request cannot leave an orphaned root.
Every authorized viewer edits the same versioned Proxy cart before Stripe creation. Once a hosted
Session is attached, Stripe does not permit line-item mutation: syncCheckoutCart returns
hosted_replacement_required before any Stripe read or Proxy write. To change the cart, expire the
merchant-owned Session, wait until Proxy observes provider-confirmed expired, update the
versioned Proxy cart, supersede that terminal acquisition, and call openCheckout again.
Use fingerprintHostedCheckoutProviderOptions(nextParams) as the supersession
providerOptionsFingerprint; openCheckout uses that same canonical fingerprint. This produces
one explicitly replaced root for the purchase, never a viewer-specific Session or a hidden
wait-for-another-payer lock. Concurrent viewers either converge on the current versioned cart/root
or receive a visible conflict and reload current state.
The restricted-key reads rendered below configure Proxy’s separate, read-only reconciliation
credential. Do not reuse that key for the merchant-server stripe client: the merchant-controlled
client needs its normal Checkout create/expire permissions, remains inside the merchant backend,
and is never stored by Proxy. Proxy’s credential keeps financial-object writes disabled.
Checkout is correlation, not payment authority
Section titled “Checkout is correlation, not payment authority”A successful checkout.session.completed event links the shared Checkout root to its Stripe
Subscription. It does not grant access, create an Invoice payment, or earn a fee. Checkout,
Subscription, and Invoice events may arrive in any order; exact replays and distinct Stripe Event
IDs for the same provider state converge through the shared lifecycle planner.
| Initial provider state | Proxy result | Merchant action |
|---|---|---|
| Supported positive automatic Invoice paid | One Invoice-keyed payment, one earned fee, and one initial completion winner | Read current state and apply fulfillment idempotently |
| Exact-zero initial Invoice or coherent free trial | Provisionable under the zero/trial rule, with no payment or fee | Apply your trial or free-access policy idempotently |
| Invoice requires authentication or payment fails | Actionable failure on the same Invoice, with no fee or grant | Resume authentication or retry through merchant-owned Stripe UI |
| Invoice finalization fails | Retained failure and targeted recovery, with no fee or grant | Correct the Stripe configuration or retry finalization |
| Incomplete, contradictory, or unsupported evidence | Configuration or reconciliation action required | Investigate retained evidence; do not fulfill |
Nullable Stripe Customer identity is diagnostic only. Proxy may learn the first coherent value from already-required signed webhook or read-only recovery evidence. It is absent from payer/browser responses and ordinary merchant event authority; a contradiction cannot grant or earn.
Every positive Invoice owns its payment and fee
Section titled “Every positive Invoice owns its payment and fee”Proxy fully reads the provider-authored Invoice line collection and derives the merchandise basis from signed line subtotals before tax and after signed proration credits. Mixed recurring and initial one-time lines therefore earn once from the initial Invoice’s complete basis. Later renewals and prorations use their own distinct Invoice identities.
Every supported successful positive Invoice earns exactly 3% of that complete basis plus 30 minor units. Exact-zero and free-trial Invoices earn nothing. A linked Invoice PaymentIntent is correlation evidence and never creates a second payment or fee. Refund or dispute observation is selectable; it never reverses the earned fee, fulfills access, revokes access, or cancels the Subscription.
Complete lifecycle and recovery
Section titled “Complete lifecycle and recovery”The same Subscription root carries trial conversion, renewal, positive and zero proration, failed and recovered renewal, authentication required, finalization failure, fixed plan or quantity changes, pause/resume, cancellation scheduling and removal, terminal deletion, and bounded targeted recovery. A lifecycle wakeup is a prompt to read current state, not an entitlement command.
The capability manifest lists the exact merchant wakeups for hosted,
embedded, and custom Subscription Checkout. That set includes asynchronous
Checkout failure/expiry acquisition signals, session expiry and
merchant-action-required signals, and every supported Subscription
activation, trial, Invoice failure, renewal, configuration, pause/resume, and
cancellation signal. The server SDK resolves every one from current state;
only subscription.renewed can resolve as a renewal.
Keep your independent Stripe webhook and your merchant fulfillment state machine. Route Stripe and Proxy signed wakeups into one idempotent merchant transition keyed by the durable purchase and current state. Reordered or duplicate wakeups must not grant twice, extend twice, or revoke from stale evidence.
Validate
Section titled “Validate”- Create one durable Proxy handoff and open it in two payer views.
- Confirm both views recover the same Checkout Session and selected presentation.
- Test a positive initial Invoice plus the trial or zero-initial-Invoice outcome your product supports.
- Confirm the first qualifying completion leaves every other view explicitly completed without another Subscription.
- Retry and reorder signed events; confirm fulfillment, renewal, and entitlement logic remains idempotent.
- Exercise authentication-required, payment failure, Invoice finalization failure, cancellation, and your selected presentation’s unavailable state.
Continue with Entitlements and Stripe events and permissions.
Hosted capability requirements
Capability subscription.checkout.hosted uses capability manifest v2 and parser contract v2. Its current availability isgeneral_availability.
| Compatibility fact | Minimum |
|---|---|
| Stripe request API | 2022-11-15 |
| stripe-node | 12.18.0 |
| Stripe.js | 2.4.0 |
| React Stripe.js | 2.9.0 |
Restricted-key reads
Checkout Sessions: Read, Subscriptions: Read, Invoices: Read, Payment Intents: Read. Leave Customer and Payment Methods access set to None.
| Operation | Read |
|---|---|
checkout_sessions.retrieve | /v1/checkout/sessions/:id |
checkout_sessions.line_items | /v1/checkout/sessions/:id/line_items |
subscriptions.retrieve | /v1/subscriptions/:id |
subscription_items.list_by_subscription | /v1/subscription_items?subscription=:id&limit=100 |
invoices.retrieve | /v1/invoices/:id |
invoices.list_by_subscription | /v1/invoices?subscription=:id&limit=100 |
invoices.line_items | /v1/invoices/:id/lines |
payment_intents.retrieve | /v1/payment_intents/:id |
checkout_sessions.list_probe | /v1/checkout/sessions?limit=1 |
subscriptions.list_probe | /v1/subscriptions?limit=1 |
invoices.list_probe | /v1/invoices?limit=1 |
payment_intents.list_probe | /v1/payment_intents?limit=1 |
Webhook Endpoint events
| Endpoint API profile | Required events | Optional compatibility events |
|---|---|---|
2022-11-15 through 2024-10-27 | checkout.session.async_payment_failedcheckout.session.async_payment_succeededcheckout.session.completedcheckout.session.expiredcustomer.subscription.createdcustomer.subscription.deletedcustomer.subscription.trial_will_endcustomer.subscription.updatedinvoice.finalization_failedinvoice.marked_uncollectibleinvoice.paidinvoice.payment_action_requiredinvoice.payment_failedinvoice.voidedpayment_intent.canceledpayment_intent.payment_failedpayment_intent.processingpayment_intent.succeeded | customer.subscription.pausedcustomer.subscription.resumed |
2024-10-28.acacia and newer | checkout.session.async_payment_failedcheckout.session.async_payment_succeededcheckout.session.completedcheckout.session.expiredcustomer.subscription.createdcustomer.subscription.deletedcustomer.subscription.trial_will_endcustomer.subscription.updatedinvoice.finalization_failedinvoice.marked_uncollectibleinvoice.paidinvoice.payment_action_requiredinvoice.payment_failedinvoice.voidedpayment_intent.canceledpayment_intent.payment_failedpayment_intent.processingpayment_intent.succeeded | customer.subscription.pausedcustomer.subscription.resumed |
Proxy wakeups
provider_acquisition.expired provider_acquisition.failed proxy_session.expired proxy_session.merchant_action_required proxy_session.paid proxy_session.provisionable subscription.activated subscription.cancel_scheduled subscription.cancel_schedule_removed subscription.cancelled subscription.changed subscription.configuration_action_required subscription.invoice_finalization_failed subscription.invoice_uncollectible subscription.invoice_voided subscription.paused subscription.payment_action_required subscription.payment_failed subscription.resumed subscription.renewed subscription.trial_ending
Visible unsupported configurations
adjustable_quantity after_expiration_recovery automatic_tax_liability_account connect customer_account preselected_customer managed_payments metered_billing multiple_invoice_payments optional_items out_of_band_payment pending_invoice_items payment_records send_invoice subscription_schedules
Embedded capability requirements
Capability subscription.checkout.embedded uses capability manifest v2 and parser contract v2. Its current availability isgeneral_availability.
| Compatibility fact | Minimum |
|---|---|
| Stripe request API | 2023-10-16 |
| stripe-node | 14.0.0 |
| Stripe.js | 2.1.8 |
| React Stripe.js | 2.3.2 |
Restricted-key reads
Checkout Sessions: Read, Subscriptions: Read, Invoices: Read, Payment Intents: Read. Leave Customer and Payment Methods access set to None.
| Operation | Read |
|---|---|
checkout_sessions.retrieve | /v1/checkout/sessions/:id |
checkout_sessions.line_items | /v1/checkout/sessions/:id/line_items |
subscriptions.retrieve | /v1/subscriptions/:id |
subscription_items.list_by_subscription | /v1/subscription_items?subscription=:id&limit=100 |
invoices.retrieve | /v1/invoices/:id |
invoices.list_by_subscription | /v1/invoices?subscription=:id&limit=100 |
invoices.line_items | /v1/invoices/:id/lines |
payment_intents.retrieve | /v1/payment_intents/:id |
checkout_sessions.list_probe | /v1/checkout/sessions?limit=1 |
subscriptions.list_probe | /v1/subscriptions?limit=1 |
invoices.list_probe | /v1/invoices?limit=1 |
payment_intents.list_probe | /v1/payment_intents?limit=1 |
Webhook Endpoint events
| Endpoint API profile | Required events | Optional compatibility events |
|---|---|---|
2022-11-15 through 2024-10-27 | checkout.session.async_payment_failedcheckout.session.async_payment_succeededcheckout.session.completedcheckout.session.expiredcustomer.subscription.createdcustomer.subscription.deletedcustomer.subscription.trial_will_endcustomer.subscription.updatedinvoice.finalization_failedinvoice.marked_uncollectibleinvoice.paidinvoice.payment_action_requiredinvoice.payment_failedinvoice.voidedpayment_intent.canceledpayment_intent.payment_failedpayment_intent.processingpayment_intent.succeeded | customer.subscription.pausedcustomer.subscription.resumed |
2024-10-28.acacia and newer | checkout.session.async_payment_failedcheckout.session.async_payment_succeededcheckout.session.completedcheckout.session.expiredcustomer.subscription.createdcustomer.subscription.deletedcustomer.subscription.trial_will_endcustomer.subscription.updatedinvoice.finalization_failedinvoice.marked_uncollectibleinvoice.paidinvoice.payment_action_requiredinvoice.payment_failedinvoice.voidedpayment_intent.canceledpayment_intent.payment_failedpayment_intent.processingpayment_intent.succeeded | customer.subscription.pausedcustomer.subscription.resumed |
Proxy wakeups
provider_acquisition.expired provider_acquisition.failed proxy_session.expired proxy_session.merchant_action_required proxy_session.paid proxy_session.provisionable subscription.activated subscription.cancel_scheduled subscription.cancel_schedule_removed subscription.cancelled subscription.changed subscription.configuration_action_required subscription.invoice_finalization_failed subscription.invoice_uncollectible subscription.invoice_voided subscription.paused subscription.payment_action_required subscription.payment_failed subscription.resumed subscription.renewed subscription.trial_ending
Visible unsupported configurations
adjustable_quantity after_expiration_recovery automatic_tax_liability_account connect customer_account preselected_customer managed_payments metered_billing multiple_invoice_payments optional_items out_of_band_payment pending_invoice_items payment_records send_invoice subscription_schedules
Custom capability requirements
Capability subscription.checkout.custom uses capability manifest v2 and parser contract v2. Its current availability isgeneral_availability.
| Compatibility fact | Minimum |
|---|---|
| Stripe request API | 2025-03-31.basil |
| stripe-node | 18.0.0 |
| Stripe.js | 7.0.0 |
| React Stripe.js | 3.6.0 |
Restricted-key reads
Checkout Sessions: Read, Subscriptions: Read, Invoices: Read, Payment Intents: Read. Leave Customer and Payment Methods access set to None.
| Operation | Read |
|---|---|
checkout_sessions.retrieve | /v1/checkout/sessions/:id |
checkout_sessions.line_items | /v1/checkout/sessions/:id/line_items |
subscriptions.retrieve | /v1/subscriptions/:id |
subscription_items.list_by_subscription | /v1/subscription_items?subscription=:id&limit=100 |
invoices.retrieve | /v1/invoices/:id |
invoices.list_by_subscription | /v1/invoices?subscription=:id&limit=100 |
invoices.line_items | /v1/invoices/:id/lines |
payment_intents.retrieve | /v1/payment_intents/:id |
checkout_sessions.list_probe | /v1/checkout/sessions?limit=1 |
subscriptions.list_probe | /v1/subscriptions?limit=1 |
invoices.list_probe | /v1/invoices?limit=1 |
payment_intents.list_probe | /v1/payment_intents?limit=1 |
Webhook Endpoint events
| Endpoint API profile | Required events | Optional compatibility events |
|---|---|---|
2022-11-15 through 2024-10-27 | checkout.session.async_payment_failedcheckout.session.async_payment_succeededcheckout.session.completedcheckout.session.expiredcustomer.subscription.createdcustomer.subscription.deletedcustomer.subscription.trial_will_endcustomer.subscription.updatedinvoice.finalization_failedinvoice.marked_uncollectibleinvoice.paidinvoice.payment_action_requiredinvoice.payment_failedinvoice.voidedpayment_intent.canceledpayment_intent.payment_failedpayment_intent.processingpayment_intent.succeeded | customer.subscription.pausedcustomer.subscription.resumed |
2024-10-28.acacia and newer | checkout.session.async_payment_failedcheckout.session.async_payment_succeededcheckout.session.completedcheckout.session.expiredcustomer.subscription.createdcustomer.subscription.deletedcustomer.subscription.trial_will_endcustomer.subscription.updatedinvoice.finalization_failedinvoice.marked_uncollectibleinvoice.paidinvoice.payment_action_requiredinvoice.payment_failedinvoice.voidedpayment_intent.canceledpayment_intent.payment_failedpayment_intent.processingpayment_intent.succeeded | customer.subscription.pausedcustomer.subscription.resumed |
Proxy wakeups
provider_acquisition.expired provider_acquisition.failed proxy_session.expired proxy_session.merchant_action_required proxy_session.paid proxy_session.provisionable subscription.activated subscription.cancel_scheduled subscription.cancel_schedule_removed subscription.cancelled subscription.changed subscription.configuration_action_required subscription.invoice_finalization_failed subscription.invoice_uncollectible subscription.invoice_voided subscription.paused subscription.payment_action_required subscription.payment_failed subscription.resumed subscription.renewed subscription.trial_ending
Visible unsupported configurations
adjustable_quantity after_expiration_recovery automatic_tax_liability_account connect customer_account preselected_customer managed_payments metered_billing multiple_invoice_payments optional_items out_of_band_payment pending_invoice_items payment_records send_invoice subscription_schedules