Stripe | Financial Infrastructure to Grow Your Revenue

Stripe | Financial Infrastructure to Grow Your Revenue

4466 articles

Migrate from Payment Intents to the Off-Session Payments API


Private preview

Migrate from Payment Intents to the Off-Session Payments API Private preview

Update your recurring and unscheduled off-session payment integration to use the Off-Session Payments API.

The Off-Session Payments API replaces the pattern of creating off-session PaymentIntent objects with off_session=true for recurring and unscheduled business-initiated transactions. It provides built-in smart retries, asynchronous authorization, and a dedicated payment lifecycle with granular event notifications.

Use this guide if you have a direct Payments integration that currently creates off-session PaymentIntent objects with off_session=true. If you use Stripe Billing subscriptions and invoices, this guide doesn’t apply.

Note

The Off-Session Payments API is a v2 API. Requests require a Stripe-Version: 2026-04-22.preview header and follow the v2 request and response format. See the v2 API overview for details about v2 conventions.

What stays the same

On-session flows remain unchanged. You still use the Payment Intents API and Setup Intents API to collect payment method details and authenticate your customers while they’re active in your UI.

  • Use the Setup Intents API with usage=off_session to save a payment method without an initial charge. For example, use it to collect a payment method for future use without charging the customer.
  • Use the Payment Intents API with setup_future_usage=off_session to charge a customer and save their payment method at the same time.

In both cases, the on-session step can perform required customer authentication up front, which helps later off-session charges proceed without customer interaction. However, issuers can still decline or require additional authentication for a later off-session payment. Your on-session payment method collection and authentication flow doesn’t change.

Charges API usage remains unchanged. The Off-Session Payments API still creates a Charge for each payment attempt, so your existing refund, dispute, reconciliation, and balance reporting workflows that depend on Charge objects can continue to work. However, for payment lifecycle handling, move from payment_intent.* events to the new v2.payments.off_session_payment.* events described in Update your webhook handling.

  • charge.refunded and charge.disputed events for handling refunds and disputes remain the same.
  • Balance Transactions linked to the Charge object continue to work for payout and balance reporting.

What changes

The biggest integration change is that the API response no longer returns authorization results synchronously. The off-session charge moves from POST /v1/payment_intents with off_session=true to POST /the relevant part of the product. The key differences are:

  • Asynchronous authorization: The v2 API returns immediately with a pending status. The authorization result arrives in a webhook event, not in the API response.
  • Built-in smart retries: Failed authorizations are automatically retried according to your configured retry strategy. You don’t need to implement retry logic yourself.
  • Dedicated event type: A new set of v2.payments.off_session_payment.* events replaces payment_intent.payment_failed for off-session retry workflows.
  • API version header: Requests require an explicit Stripe-Version: 2026-04-22.preview header. You must set this header directly—see Private preview release channel for how to configure the preview version with a Stripe SDK. Requests also require Content-Type: application/json, which Stripe SDKs set automatically.
  • Amount format: The amount is a JSON object with value and currency fields instead of separate amount and currency parameters.

Set up event destinations

The Off-Session Payments API uses v2 events, which always use thin payloads. This requires a separate event destination from your existing v1 webhooks. Each event destination has its own signing secret, so update your webhook signature verification to use the signing secret for your v2 destination. See Migrate from snapshot to thin events for details.

Set up a v2 event destination before you migrate:

Command Line

Select a language

cURL

Stripe CLI

Ruby

Python

PHP

Java

Node.js

Go

.NET

No results

See Register event destinations for more details.

Replace the off-session Payment Intent API with the Off-Session Payments API

Replace your off-session POST /v1/payment_intents request, which used off_session=true, with a POST /the relevant part of the product request.

Payment Intents with off_session=true

Off-Session Payments API

Command Line

Select a language

cURL

Stripe CLI

Ruby

Python

PHP

Java

Node.js

Go

.NET

No results

Command Line

Select a language

cURL

Stripe CLI

Ruby

Python

PHP

Java

Node.js

Go

.NET

No results

The v2 API returns a pending status immediately. The authorization result arrives asynchronously as a webhook event.

  • (Required) cadence : Set to recurring for payments on a regular schedule, or unscheduled for merchant-initiated transactions that don’t follow a fixed schedule. This value is used for network compliance classification.

** (Optional) retry_details: Configures the retry strategy for failed authorizations. If omitted, the default strategy is none, meaning no automatic retries are enabled. Set retry_strategy to best_available to let Stripe retry using the optimal schedule for the payment method.

As with Payment Intents, send an idempotency key when creating an off-session payment to avoid creating duplicate payment objects during retries or network failures.

Update your webhook handling

For off-session payment lifecycle handling, replace your payment_intent.succeeded and payment_intent.payment_failed handlers with the corresponding v2.payments.off_session_payment.* events. The v2 API also introduces a terminal failure state and built-in automatic retries that have no equivalent in the Payment Intents API.

Payment Intents events

Off-Session Payments events

payment_intent.succeeded
payment_intent.payment_failed
v2.payments.off_session_payment.succeeded
v2.payments.off_session_payment.attempt_failed
v2.payments.off_session_payment.failed

The new events have distinct meanings:

Payment Intents eventOff-Session Payments eventMeaning
payment_intent.succeededv2.payments.off_session_payment.succeededThe payment completed successfully.
payment_intent.payment_failed. The PaymentIntent moves to requires_payment_method status. Automatic retries are not available. You must implement retry logic yourself.v2.payments.off_session_payment.attempt_failedA single authorization attempt failed. Stripe might retry automatically based on your retry strategy.
Not available. Payment Intents has no terminal failure state for off-session payments. A failed PaymentIntent remains in requires_payment_method indefinitely.v2.payments.off_session_payment.failedThe off-session payment reached a terminal failure state. Retries are exhausted or no longer possible.

Because v2 events always use thin payloads, the event contains a related_object with the off-session payment ID. Fetch the full object to get payment details:

Command Line

Select a language

cURL

Stripe CLI

Ruby

Python

PHP

Java

Node.js

Go

.NET

No results

The payment_record field on the off-session payment object contains payment outcome data. See Payment Records for details.

To find the Charge ID associated with a payment attempt, fetch the Payment Attempt Record from the off-session payment object. The Charge ID is available in processor_details.stripe.charge on the Payment Attempt Record.

Parameter mapping

Use this table to translate your existing Payment Intents parameters to the Off-Session Payments API.

Payment Intents parameterOff-Session Payments parameterNotes
amountamount.valueAmount is now a nested object with value and currency
currencyamount.currencyMoved inside the amount object
customercustomerUnchanged
payment_methodpayment_methodUnchanged
descriptiondescriptionUnchanged
metadatametadataUnchanged
statement_descriptor_suffixstatement_descriptor_suffixUnchanged
on_behalf_ofon_behalf_ofUnchanged
transfer_datatransfer_dataUnchanged
off_session=trueReplaced by the v2 endpoint itselfThe endpoint is inherently off-session; no parameter needed
confirm=trueNot applicableAuthorization is always asynchronous; no confirmation step
payment_method_typesNot applicablePayment method type is inferred from the saved payment method
confirmation_methodNot applicableAll authorizations are asynchronous
Not applicablecadenceRequired. Set to recurring or unscheduled to classify the payment for network compliance.
Not applicableretry_details.retry_strategyOptional. Defaults to none (no retries). Set to best_available to enable automatic retries.

Test before migrating

Before migrating production traffic, validate your integration in the Stripe sandbox:

  1. Set up your v2 event destination and verify webhook signature verification using the destination’s signing secret.
  2. Create off-session payments in the sandbox and confirm your webhook handler receives and processes v2. payments. off _ session _ payment. * events.
  3. Test both the attempt _ failed and terminal failed paths to verify your retry and failure handling.

Once your sandbox integration is working, migrate your production integration.

Next steps

Last verified 2026-09-24

Is this helpful?