> For the complete documentation index, see [llms.txt](https://developer.paddle.com/llms.txt).

# Present customers with an upsell checkout

Encourage customers to upgrade or purchase additional items after their initial purchase by opening a checkout designed for upsells, removing friction and boosting conversion. Only available for inline checkouts.

---

An [upsell checkout](https://developer.paddle.com/concepts/sell/upsell-checkout.md) is a specialized purchase flow designed to capture additional sales immediately after a customer completes an initial purchase. It's optimized to minimize friction and maximize conversion rates by reusing details and consent acknowledgments from the preceding transaction.

{% callout type="default" %}
Access to Upsell checkout is limited to users who are part of our early access program. If you're interested in joining the program, review this guide and email us at [sellers@paddle.com](mailto:sellers@paddle.com) to apply. We'll reach out when space is available if you meet the program requirements.
{% /callout %}

## How it works

You can use upsell checkouts after a customer has completed an initial transaction using [Paddle Checkout](https://developer.paddle.com/concepts/sell/self-serve-checkout.md). You pass the transaction ID of the initial transaction to the upsell checkout.

An upsell checkout uses the customer, business, and address data from the previous transaction, so customers never have to reenter these details and you don't have to [manually prefill those details](https://developer.paddle.com/build/checkout/prefill-checkout-properties.md).

{% callout type="info" %}
You must open an [inline, one-page checkout](https://developer.paddle.com/concepts/sell/branded-integrated-inline-checkout.md) for the upsell flow to show. If you try to open an [overlay checkout](https://developer.paddle.com/build/checkout/build-overlay-checkout.md) or multi-step variant, it defaults to a standard inline checkout.
{% /callout %}

### Enable a one-click purchase experience

The checkout flow can be streamlined to render a single button for one-click purchasing. It hides elements that were already presented during the initial transaction, like the Merchant of Record disclosure, and automatically selects the same customer payment method.

This happens when:

- [**The transaction is within the same session**](#background-same-session)  
   The customer completed the initial transaction within the last 5 minutes.
- [**The payment methods can be reused**](#background-payment-method-continuity)  
   The customer saved their payment method during the initial purchase and they're authenticated, or they selected Apple Pay or Google Pay.

If these don't happen, then the upsell checkout experience presented to customers can vary. Customer data is still reused and the [skip button](#background-upsell-controls) is still shown.

#### Same session

You can open checkouts for upsells immediately after a transaction completes, or later - for example, from an email campaign or when customers return to your app.

Paddle determines whether an upsell occurs within the "same session" by checking if the original transaction was completed within the last 5 minutes.

If so, the checkout flow is streamlined to hide elements that were already presented during the initial purchase, like the Merchant of Record disclosure. If not, those elements are shown.

When a checkout is opened for an upsell, the session remains fixed. This means the flow won't change for customers during purchase and upsell transactions still process successfully after the 5 minute window has passed.

#### Reusing payment methods

When the upsell occurs within the [same session](#background-same-session) and payment methods are reusable, customers can complete their purchase with one click.

There's two approaches to enable payment method reuse, depending on the payment method used in the initial transaction:

- **Saved payment methods**  
   When you turn on the option to let customers [save their payment methods](https://developer.paddle.com/build/checkout/saved-payment-methods.md), they can opt to save their [card](https://developer.paddle.com/concepts/payment-methods/credit-debit-card.md) or [PayPal](https://developer.paddle.com/concepts/payment-methods/paypal.md) account during checkout. The saved payment method is automatically selected for one-click purchasing if the customer is authenticated with a [customer authentication token](https://developer.paddle.com/build/checkout/saved-payment-methods#present-payment-methods-generate.md).
- **Wallet payment methods**  
   When a customer uses [Apple Pay](https://developer.paddle.com/concepts/payment-methods/apple-pay.md) or [Google Pay](https://developer.paddle.com/concepts/payment-methods/google-pay.md) in the initial transaction, you can [pass that same method](#open-upsell-checkout-display-payment-methods) when opening the upsell checkout within `allowedPaymentMethods`. This restricts the checkout to that specific payment method, enabling one-click purchasing without requiring a customer authentication token.

{% callout type="info" %}
For card and PayPal payment methods, customers must opt to save. If consent wasn't given during the initial purchase, customers must reenter their payment details for the upsell.
{% /callout %}

### Customize the upsell flow

You can pass settings to Paddle.js to [configure the upsell experience](#open-upsell-checkout-customize-flow).

By default, the checkout shows a "No thanks" button so customers can decline the upsell. If they decline, you must [handle what happens](#handle-canceled-upsells) depending on your purchase flow.

To hide the skip button, pass `upsell.settings.showSkipButton: false`. Do this when customers can navigate away from the checkout themselves, and you don't want to handle what happens next.

{% callout type="info" %}
Paddle Checkout takes no action when the skip button is clicked. Hide the button or listen for the [`checkout.upsell.canceled`](https://developer.paddle.com/paddlejs/upsell/checkout-upsell-canceled.md) event to take action yourself.
{% /callout %}

### Checkout events

[Checkout events](https://developer.paddle.com/paddlejs/events/overview.md) include an `upsell` object when the checkout is opened as an upsell flow.

The object contains the original transaction ID and a `sameSession` boolean. If `true`, the upsell occurred within the same session as the previous transaction.

Paddle.js emits a [`checkout.loaded`](https://developer.paddle.com/paddlejs/general/checkout-loaded.md) event when the checkout is opened. You can use this to track whether an upsell flow was shown to a customer by reading `event.data.upsell`. If `sameSession` is `true`, the streamlined upsell flow was shown.

If the checkout isn't for an upsell, or you attempted to open a checkout for an upsell but an upsell-specific flow wasn't shown to the customer, the value of `upsell` is `null`. This can happen because:

- The previous transaction isn't `completed`.
- You attempt to open an overlay or multi-step checkout for an upsell.

## Before you begin

### Set up an initial checkout

Upsells are intended to follow directly after a previously completed transaction at checkout. To open a new checkout for an upsell, you need to have a previous transaction that's `completed`.

Build an [inline](https://developer.paddle.com/build/checkout/build-branded-inline-checkout.md) or [overlay](https://developer.paddle.com/build/checkout/build-overlay-checkout.md) checkout to let customers purchase initially. This allows you to [capture the ID of that transaction](#open-upsell-checkout-grab-transaction-id) and pass it to [open a checkout for the upsell](#open-upsell-checkout) later.

{% callout type="info" %}
While the initial checkout can use an overlay checkout, upsells must use inline checkouts. It's recommended to use an inline checkout throughout for ease of implementation.
{% /callout %}

### Create products and prices to upsell

You'll need to [create any new products and at least one related price](https://developer.paddle.com/build/products/create-products-prices.md) to pass as items to upsell at checkout.

### Turn on saved payment methods

For the best upsell experience where customers can purchase in one-click by reusing their previous payment method, you should [turn on saved payment methods](https://developer.paddle.com/build/checkout/saved-payment-methods.md).

Without this, customers can only use wallet payment methods like [Apple Pay](https://developer.paddle.com/concepts/payment-methods/apple-pay.md) or [Google Pay](https://developer.paddle.com/concepts/payment-methods/google-pay.md) for one-click purchasing, or would need to enter their payment details again for the upsell transaction.

## Open a checkout for an upsell

Open a checkout for an upsell in four steps:

1. [**Grab the previous transaction ID**](#open-upsell-checkout-grab-transaction-id)  
   Handle checkout completed events for the previous transaction.
2. [**Capture the payment method type**](#open-upsell-checkout-capture-payment-method) {% badge label="Optional" variant="outline" /%}  
   Capture the payment method type if the customer used Apple Pay or Google Pay.
3. [**Verify the transaction is completed**](#open-upsell-checkout-verify-transaction-completed) {% badge label="Optional" variant="outline" /%}  
   Confirm that the transaction is `completed` before opening the upsell flow.
4. [**Open the checkout with Paddle.js**](#open-upsell-checkout-open)  
   Pass `upsell` to Paddle.js with the previous transaction ID, along with the items to upsell and any other settings to customize the flow.

### Grab the previous transaction ID {% step=true %}

Upsell checkouts require a customer to have completed a previous transaction through checkout. You'll need to provide the transaction ID for that previous transaction when opening the new checkout.

Since upsell checkouts are intended to be opened [as soon as possible](#background-same-session) after a customer has completed their purchase, we recommend grabbing the transaction ID on the client-side using Paddle.js.

When initializing Paddle.js, you can pass `eventCallback` as a configuration option to run a function when a specific event occurs. Extract the transaction ID from the [`checkout.completed` event](https://developer.paddle.com/paddlejs/general/checkout-completed.md) when a customer completes checkout.

{% tabs sync="paddlejs-install-preference" %}
{% tab-item title="Using a JavaScript package manager" %}

Import Paddle.js events if using TypeScript, then update where you initialize Paddle.js to include an `eventCallback` function:

```typescript {% highlightLines="9-14" %}
import { initializePaddle } from '@paddle/paddle-js';
import { CheckoutEventNames, PaddleEventData } from '@paddle/paddle-js';

// Variable to store the transaction ID for the upsell
let previousTransactionId: string | null = null;

const paddle = await initializePaddle({
  token: 'CLIENT_SIDE_TOKEN',
  eventCallback: (event: PaddleEventData) => {
    if (event.name === CheckoutEventNames.CHECKOUT_COMPLETED) {
      // Grab the transaction ID from the completed checkout
      previousTransactionId = event.data.transaction_id;
    }
  }
});
```

{% /tab-item %}
{% tab-item title="Using script tag" %}

Update where you initialize Paddle.js to include an `eventCallback` function:

```html {% highlightLines="8-13" %}
<script src="https://cdn.paddle.com/paddle/v2/paddle.js"></script>
<script type="text/javascript">
  // Variable to store the transaction ID for the upsell
  var previousTransactionId = null;
  
  Paddle.Initialize({ 
    token: "CLIENT_SIDE_TOKEN",
    eventCallback: function(event) {
      if (event.name === 'checkout.completed') {
        // Grab the transaction ID from the completed checkout
        previousTransactionId = event.data.transaction_id;
      }
    }
  });
</script>
```

{% /tab-item %}
{% /tabs %}

### Capture the payment method type {% badge="Optional" step=true %}

If the customer might use Apple Pay or Google Pay in the initial transaction, you should capture the payment method type alongside the transaction ID. This lets you [present the one-click payment experience](#background-one-click-purchase) when [opening the upsell checkout](#open-upsell-checkout-wallet-payment-methods) by passing the wallet method to `allowedPaymentMethods`.

Extract the payment method type from the [`checkout.completed`](https://developer.paddle.com/paddlejs/general/checkout-completed.md) event by reading `event.data.payment.method_details.type`. Look for whether it's `apple-pay` or `google-pay`.

{% tabs sync="paddlejs-install-preference" %}
{% tab-item title="Using a JavaScript package manager" %}

Update your `eventCallback` function to also capture the payment method type:

```typescript {% highlightLines="11-18" %}
import { initializePaddle } from '@paddle/paddle-js';
import { CheckoutEventNames, PaddleEventData } from '@paddle/paddle-js';

// Variables to store data for the upsell
let previousTransactionId: string | null = null;
let paymentMethodType: string | null = null;

const paddle = await initializePaddle({
  token: 'CLIENT_SIDE_TOKEN',
  eventCallback: (event: PaddleEventData) => {
    if (event.name === CheckoutEventNames.CHECKOUT_COMPLETED) {
      // Grab the transaction ID and payment method type
      previousTransactionId = event.data.transaction_id;
      const methodType = event.data.payment?.method_details?.type;
      if (methodType === 'apple-pay' || methodType === 'google-pay') {
        paymentMethodType = methodType;
      }
    }
  }
});
```

{% /tab-item %}
{% tab-item title="Using script tag" %}

Update your `eventCallback` function to also capture the payment method type:

```html {% highlightLines="10-15" %}
<script src="https://cdn.paddle.com/paddle/v2/paddle.js"></script>
<script type="text/javascript">
  // Variables to store data for the upsell
  var previousTransactionId = null;
  var paymentMethodType = null;
  
  Paddle.Initialize({ 
    token: "CLIENT_SIDE_TOKEN",
    eventCallback: function(event) {
      if (event.name === 'checkout.completed') {
        // Grab the transaction ID and payment method type
        previousTransactionId = event.data.transaction_id;
        var methodType = event.data.payment && event.data.payment.method_details && event.data.payment.method_details.type;
        if (methodType === 'apple-pay' || methodType === 'google-pay') {
          paymentMethodType = methodType;
        }
      }
    }
  });
</script>
```

{% /tab-item %}
{% /tabs %}

### Verify the transaction is completed {% badge="Optional" step=true %}

When a checkout completes, the related transaction moves to `paid` while completed transaction processing takes place, then `completed`. This typically takes less than a second.

For most use cases, you can open the upsell checkout immediately after receiving the `checkout.completed` event. If the transaction isn't in a `completed` state yet, customers see the standard checkout flow instead.

If you want to ensure the streamlined upsell flow is shown, you have two options:

- **Make a single API verification call**  
   Fetch the transaction [through the Paddle API](https://developer.paddle.com/api-reference/transactions/get-transaction.md) and verify the `status` field is `completed` before opening the upsell checkout.
- **Using webhook data (if you store transactions)**  
   If you already listen for `transaction.completed` or `transaction.updated` webhooks to [handle fulfillment](https://developer.paddle.com/build/subscriptions/provision-access-webhooks.md), you can check your stored transaction data to verify completion status.

{% callout type="warning" %}
Don't continuously poll the Paddle API to check transaction status. Make at most one verification call, then proceed with opening the checkout regardless of the result.
{% /callout %}

### Open the checkout with Paddle.js {% step=true %}

You use the Paddle.js [`Paddle.Checkout.open()`](https://developer.paddle.com/paddlejs/methods/paddle-checkout-open.md) method to open a checkout for an upsell. You can [build an inline checkout](https://developer.paddle.com/build/checkout/build-branded-inline-checkout.md) with all usual [checkout settings](https://developer.paddle.com/build/checkout/set-up-checkout-default-settings.md).

{% callout type="info" %}
The checkout must be inline and one-page. Pass `displayMode` with the value `inline`, and `variant` with the value `one-page` as [a checkout setting](https://developer.paddle.com/build/checkout/set-up-checkout-default-settings.md).
{% /callout %}

Pass either the [items](https://developer.paddle.com/build/checkout/pass-update-checkout-items#open-items.md) you want to upsell to the checkout, or the [transaction ID](https://developer.paddle.com/build/transactions/pass-transaction-checkout#pass-transaction.md) of an existing transaction that contains the items you want to upsell. You may also want to [create and apply a discount](https://developer.paddle.com/build/products/offer-discounts-promotions-coupons.md) to the upsell to incentivize the customer to purchase the additional items.

The `upsell` object tells Paddle.js that this is a checkout for an upsell. You must pass this to the `Paddle.Checkout.open()` method.

#### Provide the previous transaction ID

Take the `previousTransactionId` variable you stored from the `checkout.completed` event and pass it as `upsell.transactionId`.

There's no need to [pass customer, address, or business details](https://developer.paddle.com/build/checkout/prefill-checkout-properties.md) because they're automatically inherited from the previous transaction. If you do choose to pass a `customer_id`, the customer against the previous transaction must match.

{% callout type="info" %}
[`allowLogout`](https://developer.paddle.com/build/checkout/set-up-checkout-default-settings#allow-logout.md) is ignored if passed and defaults to `false` because the upsell is tied to a previous transaction and customer.
{% /callout %}

#### Customize the upsell flow

By default, the checkout for upsells shows a "No thanks" skip button. Once clicked, the checkout emits a [`checkout.upsell.canceled`](https://developer.paddle.com/paddlejs/upsell/checkout-upsell-canceled.md) event.

You can hide this by passing `upsell.settings.showSkipButton: false`. This prevents the upsell from being canceled and the checkout from being closed.

#### Present saved payment methods

To render the [one-click purchase experience](#background-one-click-purchase) or show previous saved payment methods when customers use card or PayPal, you must:

- [Turn on saved payment methods](https://developer.paddle.com/build/checkout/saved-payment-methods.md)
- Authenticate customers with a [customer authentication token](https://developer.paddle.com/api-reference/customers/generate-customer-authentication-token.md).

Generate a [customer authentication token](https://developer.paddle.com/build/checkout/saved-payment-methods#present-payment-methods-generate.md) using the Paddle API and [pass it to the checkout](https://developer.paddle.com/build/checkout/saved-payment-methods#present-payment-methods-paddle-js.md) as `customerAuthToken`.

{% callout type="info" %}
If customers can't complete their purchase in one click, check that the [customer has payment methods saved](https://developer.paddle.com/api-reference/payment-methods/list-payment-methods.md) and the checkout is in the [same session](#background-same-session) using `upsell.sameSession` in [checkout events](https://developer.paddle.com/paddlejs/events/overview.md).
{% /callout %}

#### Present wallet payment methods

To render the [one-click purchase experience](#background-one-click-purchase) when customers use Apple Pay or Google Pay, you must pass that specific wallet method to `allowedPaymentMethods`.

If you [captured the payment method type](#open-upsell-checkout-capture-payment-method) from the initial checkout, you can pass it directly to `allowedPaymentMethods` as the only value in the array.

#### Examples

{% accordion %}
{% accordion-item title="With saved payment methods" %}

This example shows opening an upsell checkout when the customer used card or PayPal in the initial transaction. The customer authentication token enables access to their saved payment method for one-click purchasing.

```javascript
var upsellItems = [
  {
    priceId: 'pri_01h1vjfevh5etwq3rb176h9d9w',
    quantity: 1
  }
];

// previousTransactionId was captured from the initial checkout.completed event
Paddle.Checkout.open({
  items: upsellItems,
  customerAuthToken: 'pca_01hwz42rfyaxw721bgkppp66gx_01h282ye3v2d9cmcm8dzpawrd0_otkqbvati3ryh2f6o7zdr6owjsdhkgmm',
  settings: {
    displayMode: "inline",
    frameTarget: "checkout-container",
    frameInitialHeight: "450",
    frameStyle: "width: 100%; min-width: 312px; background-color: transparent; border: none;",
  },
  upsell: {
    transactionId: previousTransactionId,
    settings: {
      showSkipButton: false
    }
  }
});
```

{% /accordion-item %}
{% accordion-item title="With wallet payment methods" %}

This example shows opening an upsell checkout when the customer used Apple Pay or Google Pay in the initial transaction. The `allowedPaymentMethods` array is set to the wallet method used in the initial checkout.

```javascript
var upsellItems = [
  {
    priceId: 'pri_01h1vjfevh5etwq3rb176h9d9w',
    quantity: 1
  }
];

// paymentMethodType and previousTransactionId were captured from the initial checkout.completed event
Paddle.Checkout.open({
  items: upsellItems,
  settings: {
    displayMode: "inline",
    frameTarget: "checkout-container",
    frameInitialHeight: "450",
    frameStyle: "width: 100%; min-width: 312px; background-color: transparent; border: none;",
    allowedPaymentMethods: [paymentMethodType]
  },
  upsell: {
    transactionId: previousTransactionId,
    settings: {
      showSkipButton: false
    }
  }
});
```

{% /accordion-item %}
{% /accordion %}

## Handle skipped and canceled upsells

By default, checkouts for upsells [display a "No thanks" skip button](#open-upsell-checkout-customize-flow). When a customer clicks the "No thanks" button, Paddle.js emits a [`checkout.upsell.canceled`](https://developer.paddle.com/paddlejs/upsell/checkout-upsell-canceled.md) event but doesn't perform any action.

You need to decide what happens next so customers continue their journey.

{% callout type="info" %}
If you don't want checkouts to display the skip button, pass `upsell.settings.showSkipButton: false`.
{% /callout %}

Common actions include redirecting customers to a different page, [closing the checkout](https://developer.paddle.com/paddlejs/methods/paddle-checkout-close.md), or closing and opening a new checkout with an improved upsell offer.

When initializing Paddle.js, you can pass `eventCallback` as a configuration option to run a function when a specific event occurs. This can be used to handle when the [`checkout.upsell.canceled`](https://developer.paddle.com/paddlejs/upsell/checkout-upsell-canceled.md) event is emitted.

This example redirects customers to a dashboard page when they skip the upsell.

{% tabs sync="paddlejs-install-preference" %}
{% tab-item title="Using a JavaScript package manager" %}

Import Paddle.js events if using TypeScript, then update where you initialize Paddle.js to include an `eventCallback` function:

```typescript {% highlightLines="6-14" %}
import { initializePaddle } from '@paddle/paddle-js';
import { CheckoutEventNames, PaddleEventData } from '@paddle/paddle-js';

const paddle = await initializePaddle({
  token: 'CLIENT_SIDE_TOKEN',
  eventCallback: (event: PaddleEventData) => {
    if (event.name === CheckoutEventNames.CHECKOUT_UPSELL_CANCELED) {

      setTimeout(() => {
        window.location.href = `https://app.aeroedit.com/settings?upsellSkipped=true`;
      }, 3000);
    }
  }
});
```

{% /tab-item %}
{% tab-item title="Using script tag" %}

Update where you initialize Paddle.js to include an `eventCallback` function:

```html {% highlightLines="5-12" %}
<script src="https://cdn.paddle.com/paddle/v2/paddle.js"></script>
<script type="text/javascript">
  Paddle.Initialize({ 
    token: "CLIENT_SIDE_TOKEN",
    eventCallback: function(event) {
      if (event.name === 'checkout.upsell.canceled') {
        setTimeout(function() {
          window.location.href = 'https://app.aeroedit.com/settings?upsellSkipped=true';
        }, 3000);
      }
    }
  });
</script>
```

{% /tab-item %}
{% /tabs %}

{% callout type="info" %}
If you close the checkout using `Paddle.Checkout.close()`, be aware that the checkout `iframe` is removed from the DOM. Consider updating your UI alongside this for a more seamless user experience.
{% /callout %}