# Migration guide for v23 This version uses API version `2026-09-30.endive`. If the format of this API version looks new to you, see our new [API release process](https://stripe.com/blog/introducing-stripes-new-api-release-process). Please review our API changelog for [`2026-09-30.endive`](https://docs.stripe.com/changelog/2026-09-30.endive) to understand all the breaking changes to the Stripe API, the reasons behind them and potential alternatives. The Node SDK specific [changelog for v23](https://github.com/stripe/stripe-node/blob/master/CHANGELOG.md#23-0-0) has the corresponding changes in the SDKs as well as SDK specific breaking changes. ## Minimum Node version raised to 20 Node.js versions lower than `20` are no longer supported. **Action required**: Upgrade to Node 20 or later before updating to the latest SDK version. For more information, see our [language version support policy](https://docs.stripe.com/sdks/versioning?lang=node#stripe-sdk-language-version-support-policy). ## Use the V2 list response shape for V2 list methods V2 list methods return `Stripe.V2List`, whose pagination fields are `next_page_url` and `previous_page_url`. In previous versions, `Stripe.V2List` also declared the V1-only `object`, `has_more`, and `url` fields for type compatibility, even though those fields were never populated at runtime. There is no specific migration for this change. To paginate a V2 list response, you can continue to use the V2 pagination URLs: ``` const events: Stripe.V2List = await stripe.v2.core.events.list(); if (events.next_page_url) { // ... } ``` ## Use operation-specific address interfaces for V2 requests V2 request address properties now use operation-specific V2 interfaces instead of shared V1 types such as `Stripe.AddressParam` and `Stripe.JapanAddressParam`. This includes ordinary, shipping, `kana`, and `kanji` addresses. Prefer inferring these nested types from the operation parameters. When an explicit type is needed, use the interface exposed by the operation. For example: ```ts // Before const address: Stripe.JapanAddressParam = { town: 'town', }; const params: Stripe.V2.Core.AccountUpdateParams = { identity: {individual: {address}}, }; const account = await stripe.v2.core.accounts.update(accountId, params); // After const address: Stripe.V2.Core.AccountUpdateParams.Identity.Individual.Address = { town: 'town', }; const params: Stripe.V2.Core.AccountUpdateParams = { identity: {individual: {address}}, }; const account = await stripe.v2.core.accounts.update(accountId, params); ``` V1 addresses continue to use the shared V1 address parameter types. ## Use `Stripe.errors` instead of `Stripe.ErrorType` The `Stripe.ErrorType` namespace has been removed. It only ever provided types (for example, `Stripe.ErrorType.StripeCardError`), and every one of those types is also available under `Stripe.errors`. To migrate, find and replace `Stripe.ErrorType` with `Stripe.errors` in your type annotations: ```ts // Before function handleError(err: Stripe.ErrorType.StripeCardError) { // ... } // After function handleError(err: Stripe.errors.StripeCardError) { // ... } ``` ## Remove deprecated `stripe.constructEventWithoutVerification()` The top-level constructEventWithoutVerification(...) method that was mistakenly added to the Stripe client instance in a recent release has been removed. Use stripe.webhooks.constructEventWithoutVerification(...) instead. ``` // Old (Removed) const event = stripe.constructEventWithoutVerification(payload); // New (Required) const event = stripe.webhooks.constructEventWithoutVerification(payload); ``` ## Handle incomplete response bodies as connection errors If your integration handles `StripeAPIError` specifically for a connection that closes while the response body is being read, update it to handle `StripeConnectionError` instead. Before: ```ts try { await stripe.balance.retrieve(); } catch (error) { if (error instanceof Stripe.errors.StripeAPIError) { handleResponseFailure(error); } else { throw error; } } ``` After: ```ts try { await stripe.balance.retrieve(); } catch (error) { if (error instanceof Stripe.errors.StripeConnectionError) { handleResponseFailure(error); } else { throw error; } } ``` Handling for fully received API error responses and malformed JSON responses continue to throw `StripeAPIError`. ## `verifyHeader` and `verifyHeaderAsync` now default to `DEFAULT_TOLERANCE` `stripe.webhooks.signature.verifyHeader()` and `verifyHeaderAsync()` previously skipped timestamp tolerance verification when the `tolerance` argument was omitted. They now default to `Webhook.DEFAULT_TOLERANCE` If your integration relied on the old default of not checking the timestamp, pass `0` explicitly to preserve that behavior: ```ts // Before: no tolerance check by default const isValid = stripe.webhooks.signature.verifyHeader(payload, header, secret); // After: pass 0 explicitly to skip timestamp tolerance verification const isValid = stripe.webhooks.signature.verifyHeader(payload, header, secret, 0); ``` Most integrations that process webhooks as they arrive should not need this opt-out and can rely on the new default tolerance. This mainly affects tests or offline processing where the delta between an event being sent and received isn't meaningful.