Stripe
Stripe API breaking changes: Checkout Sessions, Basil to Endive
Three major Stripe releases changed Checkout Sessions in ways that break old code: discount coupons moved, ui_mode values were renamed, payment_method_types now returns a 400, and line_items[].dynamic_tax_rates left the spec. What changed, when, and what replaces it.
Published 1 November 2026 · 5 min read
If your Stripe integration was written against a Basil API version (2025), upgrading it to the current one means getting through three major releases. Per Stripe's versioning reference, since 2024-09-30.acacia Stripe ships monthly versions with no breaking changes, and twice a year a major release whose first version contains breaking changes. The majors since Basil are Clover (2025-09-30), Dahlia (2026-03-25) and Endive (2026-09-30); the current version on 31 October 2026 is 2026-10-28.endive. This article lists the breaking changes to Checkout Sessions across that range, from two sources: Stripe's changelog, and a diff of Stripe's published OpenAPI spec run with apibreak diff (made by the publisher of this site).
How the list was made
The spec diff compares openapi/spec3.json at three commits: 62fbe07 (19 August 2025, version 2025-08-27.basil), a54c388 (23 March 2026, version 2026-03-25.dahlia) and 2bab976 (25 September 2026, version 2026-09-30.endive). The findings below are the breaking ones on the Checkout Session operations. Each is matched to a changelog entry where one exists. A spec diff shows what the contract says; it cannot show whether Stripe rejects or ignores a removed field at runtime, so the HTTP behaviour quoted here comes from the changelog, not the diff.
Clover: discount.coupon is gone from responses
Between the Basil and Dahlia commits, the diff reports that line_items.data[].discounts[].discount.coupon no longer appears in the response of POST /v1/checkout/sessions, GET /v1/checkout/sessions/{session}, POST /v1/checkout/sessions/{session} and POST /v1/checkout/sessions/{session}/expire, together with its 17 nested fields. GET /v1/checkout/sessions/{session}/line_items loses data[].discounts[].discount.coupon the same way. The cause is the Clover entry Adds the Discount source property and removes the Discount coupon property: the auto-expanded coupon on the Discount object is removed, and a new source hash carries a type and the coupon. Stripe notes this applies to Discount objects everywhere, including within event notifications. To read the coupon now, retrieve it separately or expand it through source.
Dahlia: ui_mode values are renamed
The diff reports that the ui_mode request parameter no longer accepts custom, embedded or hosted and now accepts elements, embedded_page and hosted_page. Stripe's entry Updates Checkout Session UI mode enum values gives the mapping: hosted becomes hosted_page, embedded becomes embedded_page, and custom becomes elements. Setting ui_mode to one of the old values fails. The same entry also announces a form value for the Checkout form UI, although the published spec lists it only from the Endive commit; at the Dahlia commit ui_mode accepts elements, embedded_page and hosted_page. Responses can now return the new values too, so code that switches on session.ui_mode needs the new names, not only code that creates sessions. Dahlia also renamed the Stripe.js methods that initialise Checkout and Embedded Checkout, listed as separate breaking Stripe.js entries in the Dahlia changelog.
Endive: payment_method_types now returns a 400
Between the Dahlia and Endive commits, the diff reports that payment_method_types no longer appears in the POST /v1/checkout/sessions request. Stripe's entry Removes the payment method types parameter from Checkout Sessions says passing it now returns a 400 error. The replacements are dynamic payment methods managed from the Dashboard, excluded_payment_method_types, or the new allowed_payment_method_types. The behaviour differs: payment_method_types returned an error for an incompatible payment method, while allowed_payment_method_types silently filters the list down to methods compatible with the session's currency, amount and customer. The compatible subset still appears in the payment_method_types property of the response, so reading that field is fine; sending it is not.
The same Endive release has separate breaking entries that remove payment_method_types from Payment Intents and Setup Intents and the paymentMethodTypes option from Elements. If you set payment method types in one place, check all of them.
Endive: line_items[].dynamic_tax_rates leaves the spec
The diff also reports that line_items[].dynamic_tax_rates no longer appears in the POST /v1/checkout/sessions request. Here the sources do not line up. The Endive changelog list, as read on 31 October 2026, has no entry that names the field. Stripe's manual tax rates guide still documents it, under a notice that line_items.dynamic_tax_rates is deprecated and that Stripe Tax or line_items.tax_rates should be used instead. Whether a request that still sends it is rejected on Endive is not stated anywhere we found. Treat it as a removal: move to Stripe Tax or fixed tax rates, and test the call with the new version before you rely on either outcome.
Non-breaking, but check your parsers
Between Dahlia and Endive the spec also adds values to response enums on Checkout Sessions: ui_mode may return form, and line_items.data[].taxes[].rate.tax_type may return digital_excise_tax, mass_transit_parking_tax, parking_tax and utility_users_tax. Stripe's versioning page tells integrations to ignore unfamiliar fields and event types; treat new enum values the same way. A parser that throws on an unknown value turns a new value into an outage.
Watching these endpoints
To get the same diff for your own integration, list the Checkout operations you call in an apibreak.json manifest with the date your integration was built against as the baseline, and run apibreak check weekly or before an upgrade:
{
"version": 1,
"integrations": [
{
"vendor": "stripe",
"baseline": "2025-08-19",
"endpoints": [
"POST /v1/checkout/sessions",
"GET /v1/checkout/sessions/{session}",
"GET /v1/checkout/sessions/{session}/line_items"
]
}
]
}The baseline is a commit in stripe/openapi or a date that resolves to the last commit on or before it. Because Stripe pins every request to a version, a finding here is upgrade impact: it reaches you when you move your version, not while you stay on the old one.
Upgrade checklist for Checkout
- Search your code for payment_method_types on session create, for hosted, embedded or custom as ui_mode values, for discount.coupon reads, and for dynamic_tax_rates.
- Replace them: allowed_payment_method_types or dynamic payment methods; hosted_page, embedded_page or elements; discount.source.coupon with expand where you need the object; Stripe Tax or line_items.tax_rates.
- Make response parsing tolerant of new enum values, including ui_mode and tax_type.
- Test with the target version in the Stripe-Version header in a sandbox before changing the account default.
- Upgrade the API version of your webhook endpoints, which Stripe's upgrade steps list separately: an endpoint created with an API version keeps it, and Discount objects in events change shape too.
- After upgrading in Workbench, Stripe's upgrade steps say you can roll back for 72 hours. Move your apibreak baseline forward once you are done.
Check the endpoints you actually call
APIBreak reads the endpoints you declare, compares the vendor's own OpenAPI specification against the revision you pinned, and fails the build only when a change touches one of yours. It runs in your CI with no credentials and nothing uploaded, and the check is free.
Install the check · read a real report · what changed at each vendor