---
id: upsell
title: Add upsell to your store
hide_title: true
description: "How to offer upsell after a completed purchase"
---

import Ingress from "../../../src/components/ingress/ingress.react";

<Ingress title="Upsell after a completed purchase">
  Offer extra products to the customer once their purchase is complete. Let
  Walley Checkout render the upsell inside the iframe, or build your own UI and
  call Reauthorize.
</Ingress>

:::info Important
Auto-activated invoices cannot be upsold. Custom upsell using Reauthorize works for orders with status `NotActivated` or `PartActivated`. For in-checkout upsell, see [Availability](#availability).
:::

## Choose your integration

| Option                                | Who renders the UI        | When to pick it                                                                  |
| ------------------------------------- | ------------------------- | -------------------------------------------------------------------------------- |
| [**In-checkout upsell**](#in-checkout-upsell) | Walley Checkout iframe    | Sweden, credit purchases and Swish on a Walley-managed Swish account.            |
| [**Custom upsell using Reauthorize**](#custom-upsell-using-reauthorize) | Your own post-checkout UI | Credit purchases. Use when you need full control or upsell is decided post-checkout. |

---

## In-checkout upsell

:::info
In-checkout upsell is only allowed for Checkout sessions that create an order, which is the default. If the session uses a [settings profile](/docs/checkout/optional/settingsprofiles) set up for another purpose, such as subscriptions or identification, sending upsell items is rejected with `400 Bad Request`.
:::

### How it works

1. Pass an `upsell.items` array on the [Initialize Checkout](/docs/checkout/optional/init-checkout-features) request. Max **10 items**.
2. The customer completes their purchase as usual.
3. On the purchase-completed view, Walley Checkout renders the items as cards (image, description, price, **Add** button) together with a countdown for the offer's [time limit](#upsell-properties).
4. When the customer taps **Add**, Walley appends the article to the order. For credit purchases the order is reauthorized automatically. For Swish, the customer approves the extra payment in the Swish app. No merchant call required.
5. Accepted items appear as extra rows with `type: "Upsell"` in the order. Read them from [Get Checkout Information](/docs/checkout/getcheckoutinformation) under `order.items` once the offer has expired.

{/* TODO: add screenshot of in-iframe upsell view */}

### Availability

- **Country**: Sweden (`SE`) only.
- **Payment method**: Walley credit purchases, and Swish when the purchase is made on a Walley-managed Swish account.
- **Order state**: not activated. If the order is auto-activated, no upsell is shown.

If any of the above is not met the `upsell` block is silently omitted from the completed view — the rest of the checkout is unaffected.

If you activate the order before the time limit has expired, the customer can no longer add items and the offer closes. Wait until the time limit has expired before activating the order, fully or partly.

### Before you integrate

- **Using `redirectPageUri`?** When it is set, the customer is redirected as soon as the purchase is complete, before the upsell view is shown. The redirect cannot be delayed. To show the upsell, render the checkout again on your redirect page, or leave out `redirectPageUri`.
- **No checkout notification is sent when an item is added.** Neither the [notificationUri callback](/docs/checkout/notificationcallback) nor any checkout event fires for upsell. If you subscribe to [webhooks](/docs/webhooks/orders#walleyorderreauthorized), a `walley:order:reauthorized` webhook is sent each time an item is added. The time limit is the reliable signal: once it has expired, the order can no longer change. Fetch the final order after that.
- **The `validationUri` is not called before an upsell item is added.** Stock and price are not re-validated, so only offer items you can deliver at the price you sent.

### Adding upsell items at init

Extend your existing [Initialize Checkout](/docs/checkout/optional/init-checkout-features) request with an `upsell` object:

```http
POST /checkouts HTTP/1.1
Host: api.uat.walleydev.com
Authorization: Bearer bXlVc2VybmFtZTpmN2E1ODA4MGQzZTk0M2VmNWYyMTZlMDE...
Content-Type: application/json

{
  "storeId": 123,
  "countryCode": "SE",
  "reference": "123456789",
  "merchantTermsUri": "https://example.com/terms",
  "notificationUri": "https://example.com/notifications",
  "cart": {
    "items": [
      /* primary cart items */
    ]
  },
  "upsell": {
    "timeLimitInSeconds": 300,
    "items": [
      {
        "id": "10101",
        "description": "Premium gift wrapping",
        "quantity": 1,
        "unitPrice": 49.0,
        "vat": 25.0,
        "sku": "GIFTWRAP-PREMIUM",
        "imageUrl": "https://cdn.example.com/img/giftwrap.jpg"
      },
      {
        "id": "10102",
        "description": "Extended 2-year warranty",
        "quantity": 1,
        "unitPrice": 199.0,
        "vat": 25.0,
        "imageUrl": "https://cdn.example.com/img/warranty.jpg"
      }
    ]
  }
}
```

### `upsell` properties

| Property               | Required | Explanation                                                                                                                                                                                                                            |
| ---------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **items**              | Yes      | The products to offer. See [`upsell.items[]` properties](#upsellitems-properties).                                                                                                                                                     |
| **timeLimitInSeconds** | No       | How long the offer stays open, counted from purchase completion. Range `60`–`300`, default `300`. The customer sees a countdown. Once it has expired, nothing more can be added. Not allowed when `items` is empty. |

### `upsell.items[]` properties

| Property        | Required | Explanation                                                                                    |
| --------------- | -------- | ---------------------------------------------------------------------------------------------- |
| **id**          | Yes      | Article id. Max 50 characters. Shown on the invoice.                                           |
| **description** | Yes      | Short product description shown on the upsell card and the invoice. Truncated to 50 characters. |
| **quantity**    | Yes      | Allowed values `1`–`99999999`.                                                                 |
| **unitPrice**   | Yes      | Unit price including VAT. Max 2 decimals. Range `0.01`–`999999.99`.                            |
| **vat**         | Yes      | VAT in percent. Max 2 decimals. Range `0`–`100`.                                               |
| **imageUrl**    | Yes      | Absolute **HTTPS** URL to the product image. Max 2048 characters. Rendered in the upsell card. |
| **sku**         | No       | Stock Keeping Unit. Max 1024 characters.                                                       |

A maximum of **10** upsell items can be sent per checkout.

### Updating upsell items

Upsell items can be changed with [Update Checkout](/docs/checkout/optional/updatecheckout) until the customer commits to payment. Omit `upsell`, or send `null`, to keep the current items. Send `"items": []` to clear them. An `upsell` object without `timeLimitInSeconds` resets the time limit to `300`.

### Reading back what the customer accepted

When the customer adds an upsell item, Walley appends it to the order. Once the time limit has expired, [Get Checkout Information](/docs/checkout/getcheckoutinformation) returns the final order under `order.items` — accepted upsell rows show up alongside the original cart rows with `type` set to `Upsell`:

```json
{
  "order": {
    "orderId": "...",
    "totalAmount": 248.0,
    "items": [
      /* original cart rows */
      {
        "id": "10101",
        "description": "Premium gift wrapping",
        "quantity": 1,
        "unitPrice": 49.0,
        "vat": 25.0,
        "sku": "GIFTWRAP-PREMIUM",
        "type": "Upsell"
      }
    ]
  }
}
```

- Each offered item can be bought once.
- Identical offered items (same `id`, `description`, `unitPrice`, `vat` and `sku`) are shown as separate upsell cards. If the customer adds more than one, they are merged into one row in `order.items` with the summed quantity.
- An upsell row is never merged with a cart row, even if they share the same `id`.

Accepted items are already on the Walley invoice — no further merchant call is required.

### When an upsell fails

- **Credit purchase denied**, for example by the credit check: the item is not added, the original order is unaffected and the offer closes for the rest of the checkout.
- **Swish declined or cancelled** in the Swish app: the item is not added and the customer is not charged.

No notification is sent in either case. An item missing from `order.items` is the only signal.

---

## Custom upsell using Reauthorize

It’s possible to include your own upsell functionality after the purchase is complete in the checkout. This is done with a call to [Reauthorize](/docs/manage/orders/reauthorize) with the added products of the customer's choice or a larger amount than the original.

<center><img
  src="/img/Post-Checkout-Upsell.png"
  alt="An example post-checkout upsell page offering additional products"
/></center>

### How to get started 🚀

1. When the purchase is completed and the [checkout information](/docs/checkout/getcheckoutinformation) is fetched, it will return an `orderId`.
2. Use the `orderId` for calling the [Reauthorize](/docs/manage/orders/reauthorize) endpoint, remember to send along all existing article rows with the new upsell articles if you want the old articles to show on the new invoice. If you only want to adjust the amount, sending in an amount is enough. This will return a `201` or `202` response depending on if a credit check is needed or not. The response headers will contain a `Location` header with a path to the order.
3. Use this url path to verify that the order has successfully been reauthorized. In the case where a credit check happens (201), you will need to poll on the endpoint you received in the `Location header` to await that the reauthorize finalizes.
