Stripe | Financial Infrastructure to Grow Your Revenue

Stripe | Financial Infrastructure to Grow Your Revenue

5282 articles

How Off-Session Payments work


Private preview

How Off-Session Payments work Private preview

Learn how to build an integration with the Off-Session Payments API

Before you begin

  • Your integration must use Stripe API v2 , which uses JSON encoding (application/json) for requests and responses. Set the Stripe-Version request header to 2026-08-26. preview .
  • The Off-Session Payments API supports Card, Apple Pay, Google Pay, the related setting Direct Debit, and ACH Direct Debit payment methods.

Register an event destination Server-side

Because authorisations run asynchronously, you need a v2 event destination to receive webhook notifications about payment state changes. Responses to create requests alone won’t carry final payment outcomes. This v2 API requires a separate setup from your existing v1 webhook integrations. Learn how to register event destinations.

Command Line

Select a language

cURL

Stripe CLI

Ruby

Python

PHP

Java

Node.js

Go

.NET

No results

The following events are available for off-session payments:

EventDescription
v2.payments.off_session_payment.createdSent immediately following a successful request to the Off-Session Payments create endpoint.
v2.payments.off_session_payment.attempt_startedSent whenever Stripe initiates an asynchronous attempt at authorisation, whether it’s a retry or an initial authorisation.
v2.payments.off_session_payment.attempt_failedSent after an authorisation attempt fails, and there are still retries remaining on the retry schedule configured for smart retries.
v2.payments.off_session_payment.succeededSent immediately after a successful authorisation when using automatic capture, or immediately after calling the capture endpoint when using manual capture.
v2.payments.off_session_payment.failedSent after a failed authorisation attempt when there are no retries remaining, or if the failure isn’t retryable based on the decline code.
v2.payments.off_session_payment.canceledSent after a request to cancel to terminate the Off-Session Payment or subsequent retries.
v2.payments.off_session_payment.pausedSent immediately after a successful call to the pause endpoint.
v2.payments.off_session_payment.resumedSent immediately after a successful call to the resume endpoint.

Create an off-session payment Server-side

Pass the customer and payment method that you saved earlier, along with the desired amount and currency:

Command Line

Select a language

cURL

Stripe CLI

Ruby

Python

PHP

Java

Node.js

Go

.NET

No results

The following is an example of an Off-Session Payments API response:

{
	"id": "osp_test_123456abcedf",
	"object": "v2.payments.off_session_payment",
	"amount_requested": {
 "value": 1000,
 "currency": "usd"
	},
	"cadence": "recurring",
	"created": "2025-05-15T16:09:26.693838357Z",
	"customer": "cus_abc123",
	"description": null,
	"failure_reason": null,
	"last_authorization_attempt_error": null,
	"latest_payment_attempt_record": null,
	"livemode": false,
	"metadata": {},
	"on_behalf_of": null,
	"payment_method": "pm_abc123",
	"payment_record": null,
	"retry_details": {
 "attempts": 0,
 "retry_strategy": "smart"
	},
	"statement_descriptor_suffix": null,
	"status": "pending",
	"test_clock": null
}

Optional Specify a retry policy

Listen for events Server-side

Stripe attempts the first authorisation shortly after returning the response to your request to /the relevant part of the product. After we receive the result from the authorisation, we send an event to your event destination.

On successful authorisation

You receive an event v2.payments.off_session_payments.succeeded. The payload of the event contains the ID of the off-session payment object, which you can then retrieve with the API

Example v2.payments.off_session_payment.succeeded Event:

Command Line

Select a language

cURL

Stripe CLI

Ruby

Python

PHP

Java

Node.js

Go

.NET

No results

Example Off-Session Payments GET API Response:

{
 "id": "osp_test_123456abcedf",
 "object": "v2.payments.off_session_payment",
 "amount_requested": {
 "value": 1000,
 "currency": "usd"
 },
 "cadence": "recurring",
 "created": "2025-05-15T16:09:26.693838357Z",
 "customer": "cus_abc123",
 "description": null,
 "failure_reason": null,
 "last_authorization_attempt_error": null,
 "latest_payment_attempt_record": "par_test_abc123",
 "livemode": false,
 "metadata": {},
 "on_behalf_of": null,
 "payment_method": "pm_abc123",
 "payment_record": "pr_test_abc123",
 "retry_details": {
 "attempts": 1,
 "retry_strategy": "smart"
 },
 "statement_descriptor": "Subscription PLUS",
 "statement_descriptor_suffix": null,
 "status": "succeeded",
 "transfer_data": null,
 "test_clock": "clock_12345abced"
}

To learn more about the authorisation that was performed, you can retrieve the Payment Record object included in the OffSessionPayment object. It contains roughly the same information that’s on the Charge object when you’re using the Payment Intents API.

After you receive this event, you can move forward with fulfilling the customer’s order.

On unsuccessful authorisation

You receive an event v2.payments.off_session_payment.attempt_failed. The payload of the event contains the ID of the OffSessionPayment object, which you can then retrieve with the API:

Command Line

Select a language

cURL

Stripe CLI

Ruby

Python

PHP

Java

Node.js

Go

.NET

No results

Example Off-Session Payments API response:

{
 "id": "osp_test_123456abcedf",
 "object": "v2.payments.off_session_payment",
 "amount_requested": {
 "value": 1000,
 "currency": "usd"
 },
 "cadence": "recurring",
 "created": "2025-05-15T16:09:26.693838357Z",
 "customer": "cus_abc123",
 "description": null,
 "failure_reason": null,
 "last_authorization_attempt_error": "generic_decline",
 "latest_payment_attempt_record": "par_test_abc123",
 "livemode": false,
 "metadata": {},
 "on_behalf_of": null,
 "payment_method": "pm_abc123",
 "payment_record": "pr_test_abc123",
 "retry_details": {
 "attempts": 1,
 "retry_strategy": "smart"
 },
 "statement_descriptor": "Subscription PLUS",
 "statement_descriptor_suffix": null,
 "status": "pending_retry",
 "transfer_data": null,
 "test_clock": "clock_12345abced"
}

Similarly, you can retrieve information about the failure using the Payment Record object, which contains information similar to what might be on a failed charge.

No action is necessary if you receive this event, because Stripe automatically retries the transaction using our Smart Retry logic. Continue to listen for events on this OffSessionPayment object.

Handle terminal states Server-side

After Stripe exhausts the retries available on a payment, you receive the v2.payments.off_session_payment.failed event. The payload of the event contains the ID of the Off-Session Payment object, which you can then retrieve with the API.

Command Line

Select a language

cURL

Stripe CLI

Ruby

Python

PHP

Java

Node.js

Go

.NET

No results

Example off-session payments GET API response:

{
 "id": "osp_test_123456abcedf",
 "object": "v2.payments.off_session_payment",
 "amount_requested": {
 "value": 1000,
 "currency": "usd"
 },
 "cadence": "recurring",
 "created": "2025-05-15T16:09:26.693838357Z",
 "customer": "cus_abc123",
 "description": null,
 "failure_reason": "retries_exhausted",
 "last_authorization_attempt_error": "generic_decline",
 "latest_payment_attempt_record": "par_test_abc123",
 "livemode": false,
 "metadata": {},
 "on_behalf_of": null,
 "payment_method": "pm_abc123",
 "payment_record": "pr_test_abc123",
 "retry_details": {
 "attempts": 5,
 "retry_strategy": "smart"
 },
 "statement_descriptor_suffix": null,
 "status": "failed",
 "transfer_data": null,
 "test_clock": "clock_12345abced"
}

The off-session payment is now in a terminal state, and you won’t be able to perform any more operations on it. The failure_reason field indicates why the payment failed:

  • retries _ exhausted – Stripe attempted all scheduled retries without success.
  • exceeded _ retry _ window – The payment was paused and its retry window elapsed before it was resumed.
  • no _ valid _ payment _ method – The payment method was detached from the customer, the underlying card was deleted, or the attached mandate was deactivated.

If you need to bring the customer back on-session to collect payment, you need to do so using other on-session payments APIs, such as PaymentIntents or CheckoutSessions.

Optional Pause and resume a payment Server-side

Optional Cancel an off-session payment Server-side

Payment Records and Payment Attempt Records

PaymentRecords represents a record of an individual payment and includes all attempts and outcomes associated with it. It’s the primary reference point for understanding the lifecycle and status of a payment.

Learn more about the Payment Records API in our integration guide.

Configure Smart Retries

Go to the Revenue Recovery section of the Dashboard if you want to customise your Smart Retries configuration.

Make sure the Smart Retry policy is selected, and set your desired retry frequency and duration. Our ML engine automatically determines the best times to retry within the parameters you specify. You can configure the Smart Retry policy to retry 4-8 times over intervals that you can configure from 1 week up to 2 months.

Test mode

To test your integration with off-session payments without moving real money, you need to use a Sandbox. After you set up a sandbox, you can use the secret key associated with it to make POST requests to the Off-Session Payments API as in step 1.

To test Smart Retries, you can pass the test_clock parameter on the Off-Session Payments API. Advancing the test clock simulates the passage of time and triggers a retry at the scheduled time.

Command Line

Select a language

cURL

Stripe CLI

Ruby

Python

PHP

Java

Node.js

Go

.NET

No results

The following is an example of an Off-Session Payments API response in a sandbox using a test clock:

{
	"id": "osp_test_123456abcedf",
	"object": "v2.payments.off_session_payment",
	"amount_requested": {
 "value": 1000,
 "currency": "usd"
	},
	"cadence": "recurring",
	"created": "2025-05-15T16:09:26.693838357Z",
	"customer": "cus_abc123",
	"description": null,
	"failure_reason": null,
	"last_authorization_attempt_error": null,
	"latest_payment_attempt_record": null,
	"livemode": false,
	"metadata": {},
	"on_behalf_of": null,
	"payment_method": "pm_abc123",
	"payment_record": null,
	"retry_details": {
 "attempts": 0,
 "retry_strategy": "smart"
	},
	"statement_descriptor_suffix": null,
	"status": "pending",
	"test_clock": "clock_12345abcefd"
}

Test payment methods

Go to test cards to see card numbers you can use for testing. Off-session payments also supports special test payment methods that simulate a failed authorisation followed by a successful retry:

  • Use pm _ osp _ decline _ then _ succeed as the payment _ method parameter to simulate a card decline followed by a successful retry.
  • Use pm _ osp _ usBankAccount _ decline _ then _ succeed as the payment _ method parameter to simulate a US bank account insufficient-funds failure followed by a successful retry. The debit payment remains pending until its network submission event and succeeds only after its guarantee event.
Last verified 2026-09-25

Is this helpful?