Skip to content

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.

Hosted Checkout is the recommended starting point. It redirects to Stripe and returns a URL from openCheckout. The technical ID is SUB-CO-H.

Terminal window
proxy stripe doctor --path SUB-CO-H --format json

Install 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

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.

Terminal window
proxy stripe doctor --path SUB-CO-E --format json

Install 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

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

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.

Terminal window
proxy stripe doctor --path SUB-CO-C --format json

Install 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

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

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.

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.

  1. Create one durable Proxy handoff and open it in two payer views.
  2. Confirm both views recover the same Checkout Session and selected presentation.
  3. Test a positive initial Invoice plus the trial or zero-initial-Invoice outcome your product supports.
  4. Confirm the first qualifying completion leaves every other view explicitly completed without another Subscription.
  5. Retry and reorder signed events; confirm fulfillment, renewal, and entitlement logic remains idempotent.
  6. 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 factMinimum
Stripe request API2022-11-15
stripe-node12.18.0
Stripe.js2.4.0
React Stripe.js2.9.0

Restricted-key reads

Checkout Sessions: Read, Subscriptions: Read, Invoices: Read, Payment Intents: Read. Leave Customer and Payment Methods access set to None.

OperationRead
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 profileRequired eventsOptional compatibility events
2022-11-15 through 2024-10-27checkout.session.async_payment_failed
checkout.session.async_payment_succeeded
checkout.session.completed
checkout.session.expired
customer.subscription.created
customer.subscription.deleted
customer.subscription.trial_will_end
customer.subscription.updated
invoice.finalization_failed
invoice.marked_uncollectible
invoice.paid
invoice.payment_action_required
invoice.payment_failed
invoice.voided
payment_intent.canceled
payment_intent.payment_failed
payment_intent.processing
payment_intent.succeeded
customer.subscription.paused
customer.subscription.resumed
2024-10-28.acacia and newercheckout.session.async_payment_failed
checkout.session.async_payment_succeeded
checkout.session.completed
checkout.session.expired
customer.subscription.created
customer.subscription.deleted
customer.subscription.trial_will_end
customer.subscription.updated
invoice.finalization_failed
invoice.marked_uncollectible
invoice.paid
invoice.payment_action_required
invoice.payment_failed
invoice.voided
payment_intent.canceled
payment_intent.payment_failed
payment_intent.processing
payment_intent.succeeded
customer.subscription.paused
customer.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 factMinimum
Stripe request API2023-10-16
stripe-node14.0.0
Stripe.js2.1.8
React Stripe.js2.3.2

Restricted-key reads

Checkout Sessions: Read, Subscriptions: Read, Invoices: Read, Payment Intents: Read. Leave Customer and Payment Methods access set to None.

OperationRead
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 profileRequired eventsOptional compatibility events
2022-11-15 through 2024-10-27checkout.session.async_payment_failed
checkout.session.async_payment_succeeded
checkout.session.completed
checkout.session.expired
customer.subscription.created
customer.subscription.deleted
customer.subscription.trial_will_end
customer.subscription.updated
invoice.finalization_failed
invoice.marked_uncollectible
invoice.paid
invoice.payment_action_required
invoice.payment_failed
invoice.voided
payment_intent.canceled
payment_intent.payment_failed
payment_intent.processing
payment_intent.succeeded
customer.subscription.paused
customer.subscription.resumed
2024-10-28.acacia and newercheckout.session.async_payment_failed
checkout.session.async_payment_succeeded
checkout.session.completed
checkout.session.expired
customer.subscription.created
customer.subscription.deleted
customer.subscription.trial_will_end
customer.subscription.updated
invoice.finalization_failed
invoice.marked_uncollectible
invoice.paid
invoice.payment_action_required
invoice.payment_failed
invoice.voided
payment_intent.canceled
payment_intent.payment_failed
payment_intent.processing
payment_intent.succeeded
customer.subscription.paused
customer.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 factMinimum
Stripe request API2025-03-31.basil
stripe-node18.0.0
Stripe.js7.0.0
React Stripe.js3.6.0

Restricted-key reads

Checkout Sessions: Read, Subscriptions: Read, Invoices: Read, Payment Intents: Read. Leave Customer and Payment Methods access set to None.

OperationRead
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 profileRequired eventsOptional compatibility events
2022-11-15 through 2024-10-27checkout.session.async_payment_failed
checkout.session.async_payment_succeeded
checkout.session.completed
checkout.session.expired
customer.subscription.created
customer.subscription.deleted
customer.subscription.trial_will_end
customer.subscription.updated
invoice.finalization_failed
invoice.marked_uncollectible
invoice.paid
invoice.payment_action_required
invoice.payment_failed
invoice.voided
payment_intent.canceled
payment_intent.payment_failed
payment_intent.processing
payment_intent.succeeded
customer.subscription.paused
customer.subscription.resumed
2024-10-28.acacia and newercheckout.session.async_payment_failed
checkout.session.async_payment_succeeded
checkout.session.completed
checkout.session.expired
customer.subscription.created
customer.subscription.deleted
customer.subscription.trial_will_end
customer.subscription.updated
invoice.finalization_failed
invoice.marked_uncollectible
invoice.paid
invoice.payment_action_required
invoice.payment_failed
invoice.voided
payment_intent.canceled
payment_intent.payment_failed
payment_intent.processing
payment_intent.succeeded
customer.subscription.paused
customer.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