Skip to content
Create account
or
Sign in
The Stripe Docs logo
/
Ask AI
Create account
Sign in
Get started
Payments
Revenue
Platforms and marketplaces
Money management
Developer resources
Overview
About Stripe payments
Upgrade your integration
Payments analytics
Online payments
OverviewFind your use caseUse Managed Payments
Use Payment Links
Use a prebuilt checkout page
    Overview
    Quickstarts
    Customize look and feel
    Collect additional information
    Collect taxes
    Dynamically update checkout
    Manage your product catalog
    Subscriptions
    Manage payment methods
    Let customers pay in their local currency
    Add discounts, upsells, and optional items
    Set up future payments
    Save payment details during payment
    After the payment
      Fulfill orders
      Send receipts and paid invoices
      Customize redirect behavior
      Recover abandoned carts
      Analyze conversion funnel
    Migrate from legacy Checkout
    Migrate Checkout to use Prices
Build a custom integration with Elements
Build an in-app integration
Payment methods
Add payment methods
Manage payment methods
Faster checkout with Link
Payment interfaces
Payment Links
Checkout
Web Elements
In-app Payments
Payment scenarios
Handle multiple currencies
Custom payment flows
Flexible acquiring
Orchestration
In-person payments
Terminal
Beyond payments
Incorporate your company
Crypto
Agentic commerce
Financial Connections
Climate
Understand fraud
Radar fraud protection
Manage disputes
Verify identities
HomePaymentsUse a prebuilt checkout pageAfter the payment

Recover abandoned carts

Learn how to recover abandoned Checkout pages and boost revenue.

In e-commerce, cart abandonment is when customers leave the checkout flow before completing their purchase. To help bring customers back to Checkout, create a recovery flow where you follow up with customers over email to complete their purchases.

Cart abandonment emails fall into the broader category of promotional emails, which includes emails that inform customers of new products and that share coupons and discounts. Customers must agree to receive promotional emails before you can contact them. Checkout helps you:

  1. Collect consent from customers to send them promotional emails.
  2. Get notified when customers abandon Checkout so you can send cart abandonment emails.

Collect promotional consent

Configure Checkout to collect consent for promotional content. If you collect the customer’s email address and request consent for promotional content before redirecting to Checkout, you can skip using consent_collection[promotions].

Command Line
cURL
Stripe CLI
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
curl https://api.stripe.com/v1/checkout/sessions \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d "line_items[0][price]"=
{{PRICE_ID}}
\ -d "line_items[0][quantity]"=1 \ -d customer=
{{CUSTOMER_ID}}
\ -d mode=payment \ --data-urlencode success_url="https://example.com/success" \ -d "consent_collection[promotions]"=auto

Configure recovery

A Checkout Session becomes abandoned when it reaches its expires_at timestamp and the customer hasn’t completed checking out. When this occurs, the session is no longer accessible and Stripe fires the checkout.session.expired webhook, which you can listen to and try to bring the customer back to a new Checkout Session to complete their purchase. To use this feature, enable after_expiration.recovery when you create the session.

Command Line
cURL
Stripe CLI
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
curl https://api.stripe.com/v1/checkout/sessions \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d "line_items[0][price]"=
{{PRICE_ID}}
\ -d "line_items[0][quantity]"=1 \ -d mode=payment \ --data-urlencode success_url="https://example.com/success" \ -d customer=
{{CUSTOMER_ID}}
\ -d "consent_collection[promotions]"=auto \ -d "after_expiration[recovery][enabled]"=true \ -d "after_expiration[recovery][allow_promotion_codes]"=true

Get notified of abandonment

Listen to the checkout.session.expired webhook to be notified when customers abandon Checkout and sessions expire. When the session expires with recovery enabled, the webhook payload contains after_expiration, which includes a URL denoted by after_expiration.recovery.url that you can embed in cart abandonment emails. When the customer opens this URL, it creates a new Checkout Session that’s a copy of the original expired session. The customer uses this copied session to complete the purchase.

Note

For security purposes, the recovery URL for a session is usable for 30 days, denoted by the after_expiration.recovery.expires_at timestamp.

{ "id": "evt_123456789", "object": "event", "type": "checkout.session.expired", // ...other webhook attributes "data": { "object": { "id": "cs_12356789", "object": "checkout.session", // ...other Checkout Session attributes "consent_collection": { "promotions": "auto", }, "consent": { "promotions": "opt_in" }, "after_expiration": { "recovery": { "enabled": true, "url": "https://buy.stripe.com/r/live_asAb1724", "allow_promotion_code": true, "expires_at": 1622908282, } } } } }

Send recovery emails

To send recovery emails, create a webhook handler for expired sessions and send an email that embeds the session’s recovery URL. A customer might abandon multiple Checkout Sessions, each triggering its own checkout.session.expired event so make sure to record when you send recovery emails to customers and avoid spamming them.

Node.js
No results
// Find your endpoint's secret in your Dashboard's webhook settings const endpointSecret = 'whsec_...'; // Using Express const app = require('express')(); // Use body-parser to retrieve the raw body as a buffer const bodyParser = require('body-parser'); const sendRecoveryEmail = (email, recoveryUrl) => { // TODO: fill me in console.log("Sending recovery email", email, recoveryUrl); } app.post('/webhook', bodyParser.raw({type: 'application/json'}), (request, response) => { const payload = request.body; const sig = request.headers['stripe-signature']; let event; try { event = stripe.webhooks.constructEvent(payload, sig, endpointSecret); } catch (err) { return response.status(400).send(`Webhook Error: ${err.message}`); } // Handle the checkout.session.expired event if (event.type === 'checkout.session.expired') { const session = event.data.object; // When a Checkout Session expires, the customer's email isn't returned in // the webhook payload unless they give consent for promotional content const email = session.customer_details?.email const recoveryUrl = session.after_expiration?.recovery?.url // Do nothing if the Checkout Session has no email or recovery URL if (!email || !recoveryUrl) { return response.status(200).end(); } // Check if the customer has consented to promotional emails and // avoid spamming people who abandon Checkout multiple times if ( session.consent?.promotions === 'opt_in' && !hasSentRecoveryEmailToCustomer(email) ) { sendRecoveryEmail(email, recoveryUrl) } } response.status(200).end(); });

OptionalAdjust session expiration

By default, Checkout Sessions expire 24 hours after they’re created, but you can shorten the expiration time by setting expires_at to get notified of abandonment sooner. The minimum expires_at allowed is 30 minutes from when the session is created.

Command Line
cURL
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
curl https://api.stripe.com/v1/checkout/sessions \ -u
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:
\ -d customer=
{{CUSTOMER_ID}}
\ -d "line_items[0][price]"=
{{PRICE_ID}}
\ -d "line_items[0][quantity]"=1 \ -d mode=payment \ -d success_url="https://example.com/success" \ -d expires_at="{{NOW_PLUS_TWO_HOURS}}"

OptionalTrack conversion

When the customer opens the recovery URL for an expired Checkout Session, it creates a new Checkout Session that’s a copy of the abandoned session.

To verify whether a recovery email resulted in a successful conversion, check the recovered_from attribute in the checkout.session.completed webhook for the new Checkout Session. This attribute references the original session that expired.

Node.js
No results
// Find your endpoint's secret in your Dashboard's webhook settings const endpointSecret = 'whsec_...'; // Using Express const app = require('express')(); // Use body-parser to retrieve the raw body as a buffer const bodyParser = require('body-parser'); const logRecoveredCart = (sessionId, recoveredFromSessionId) => { // TODO: fill me in console.log("Recording recovered session", sessionId, recoveredFromSessionId); } app.post('/webhook', bodyParser.raw({type: 'application/json'}), (request, response) => { const payload = request.body; const sig = request.headers['stripe-signature']; let event; try { event = stripe.webhooks.constructEvent(payload, sig, endpointSecret); } catch (err) { return response.status(400).send(`Webhook Error: ${err.message}`); } // Handle the checkout.session.completed event if (event.type === 'checkout.session.completed') { const session = event.data.object; const recoveryFromSessionId = session.recovered_from if (recoveryFromSessionId) { // Log relationship between successfully completed session and abandoned session logRecoveredCart(session.id, recoveryFromSessionId) } // Handle order fulfillment } response.status(200).end(); });

OptionalOffer promotion codes in recovery emails

Offering discounts to customers who abandon their carts can incentivize them to complete their purchase. You can configure whether the Checkout Session created by the recovery URL has an option for promotion codes by setting after_expiration.recovery.allow_promotion_code.

Command Line
cURL
Stripe CLI
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
curl https://api.stripe.com/v1/checkout/sessions \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d "line_items[0][price]"=
{{PRICE_ID}}
\ -d "line_items[0][quantity]"=1 \ -d mode=payment \ --data-urlencode success_url="https://example.com/success" \ -d customer=
{{CUSTOMER_ID}}
\ -d "consent_collection[promotions]"=auto \ -d "after_expiration[recovery][enabled]"=true \ -d "after_expiration[recovery][allow_promotion_codes]"=true
Was this page helpful?
YesNo
  • Need help? Contact Support.
  • Join our early access program.
  • Check out our changelog.
  • Questions? Contact Sales.
  • LLM? Read llms.txt.
  • Powered by Markdoc