Skip to content

Upselling on the confirmation page

Show product recommendations after the customer has paid and let them add items to the existing order in one tap — enabling it, feeding it recommendations and the two ways to create the order in your own system.

Upsell puts product recommendations on the confirmation page, after the customer has paid. Because the payment is already authorized, adding an item takes one tap — no payment details, no second checkout, no second order. Kustom owns the widget, the authorization increase and the order update. You decide what is recommended and, more consequentially, when your own system creates the order. That last decision is the real work on this page.

An integration has three parts, and all three are needed before you go live: turn upsell on, choose where recommendations come from and build your backend to accept an order that can still change after the customer lands on your confirmation page.

Upsell must be enabled on your merchant account. Contact Kustom. Until it is, nothing on this page has any effect.

You also need to understand one consequence before you write any code: when upsell is active, the confirmation push is delayed until the upsell window closes. Anything your system does on the push — creating the order, reserving stock, emailing the customer — happens later than it used to: ten minutes by default, plus a minute’s buffer. Plan for that before you enable it.

The delay applies only when upsell is active for the order and the payment method the customer used allows it — Kustom checks that for you and only loads the widget when it does. If either is false, the push fires immediately and nothing about your existing flow changes.

Per order, which is what you want for a staged rollout or an A/B test:

{
"options": {
"confirmation_page_upsell": true
}
}

The flag can still be changed on an update, right up until the order reaches checkout_complete.

Two other things also switch upsell on for an order, and it’s worth knowing both before you wonder why a push arrived late:

  • Setting merchant_urls.upsell. An order carrying an upsell URL has upsell on, with or without the option.
  • Account-level enablement. Kustom can turn upsell on for every eligible order on your merchant ID. Once that’s set, confirmation_page_upsell: false doesn’t opt an order out — ask Kustom to change the account setting instead.
Curation in Kustom PortalYour own endpoint
EffortNo developmentBuild and host an HTTP endpoint
How it worksKustom imports your Google product feed and you pick the products to recommendKustom calls your endpoint and you return the products
ControlA fixed selectionPer order, dynamic, personalized
Best forGetting started, a small set of evergreen add-onsAn existing recommendation engine or catalog-driven logic

Curation in Portal needs nothing built. Connect your Google product feed under Upsell in Kustom Portal and choose the products to recommend. Kustom still filters the selection for each order: a product is dropped when its currency doesn’t match the order’s, when it’s out of stock, when it costs more than the authorization headroom or when the customer already has it in the order. Beyond that the selection is fixed — it doesn’t depend on what else is in the basket or on the chosen shipping option. If you need that, build an endpoint.

Your own endpoint. Set merchant_urls.upsell and Kustom calls it before the confirmation page loads:

{
"merchant_urls": {
"upsell": "https://www.example.com/api/upsell"
}
}

Curated products win. If your account has Portal-curated products that survive the filtering above, Kustom shows those and doesn’t call your endpoint. Your endpoint is called only when there is no curated selection for the order. With neither, Kustom asks its own recommendation service instead.

Aim to respond in under 3 seconds. The hard limit is 10 seconds, the same deadline every merchant callback gets, and there’s no retry, so one slow answer is one lost widget. But the customer is waiting on a confirmation page while you think, and three seconds is about as long as that stays comfortable. Treat 3 s as your budget and 10 s as the cliff.

A slow or failed response means no widget — the customer sees an ordinary confirmation page. The push is still delayed for the full window, though: that delay is decided at completion from the upsell settings and the payment method, before your endpoint is ever called, and nothing about the call’s outcome shortens it.

Kustom POSTs an upsell_request describing the order and the headroom available, and expects a list of lines back. The full contract is a page of its own. Four fields in the request are what make a recommendation good rather than merely valid:

  • max_upsell_amount — the authorization headroom, in minor units. Filter to what fits. Anything above it will be blocked when the customer taps.
  • purchase_currency and locale — price and describe correctly.
  • billing_address and shipping_address — apply the right tax rate.
  • selected_shipping_option — drop anything that would break the delivery method already chosen.

A minimal, valid response:

{
"upsell_lines": [
{
"name": "Baseball Cap",
"reference": "CAP-SAND-001",
"quantity": 1,
"unit_price": 40000,
"tax_rate": 2500,
"total_amount": 40000,
"total_tax_amount": 8000,
"max_allowed_quantity": 3,
"image_url": "https://www.example.com/images/cap-sand.jpg",
"product_url": "https://www.example.com/products/cap-sand"
}
],
"last_upsell_time": "2024-01-01T12:05:00Z"
}

Your endpoint is called more than once. Kustom calls it before the confirmation page loads and again after every item the customer adds, so the widget refreshes as the cart grows. Make the handler idempotent, and exclude what is already in the order from what you recommend.

Return upsell_lines: [] to show no widget at all. You’ll still be called when upsell_possible is false. An empty array is the correct answer then.

4. Decide when your system creates the order

Section titled “4. Decide when your system creates the order”

This is the decision that shapes your backend. Without upsell, the checkout order is final when the customer reaches your confirmation page. With upsell, it can still change until the window closes. There are two defensible ways to handle that.

Create on push — recommended, and simpler. Don’t create the order until the confirmation push arrives. It carries the final order_lines, upsold items included, so your system never modifies an order mid-flight.

  1. The payment is authorized and Kustom marks the checkout order checkout_complete.
  2. Kustom redirects the customer to your confirmation page.
  3. Your backend reads the checkout order but doesn’t create an order yet.
  4. You render the Kustom confirmation snippet.
  5. Kustom fetches recommendations and shows the widget.
  6. The customer adds items. Kustom increases the authorization and updates the checkout order.
  7. The window closes and Kustom sends the confirmation push with the final order_lines.
  8. Your backend creates the order once, from the push.

Create on redirect. Create the order in your system as soon as the customer lands on the confirmation page, on hold, then take updates as items are added. Choose this only when you genuinely need the order visible immediately — high-value goods where stock must be reserved the instant payment clears.

  1. The payment is authorized and Kustom marks the checkout order checkout_complete.
  2. Kustom redirects the customer to your confirmation page.
  3. Your backend reads the checkout order and creates it in your system, on hold.
  4. You render the Kustom confirmation snippet.
  5. Kustom fetches recommendations and shows the widget.
  6. The customer adds an item. Kustom runs your validation callback if you set one, increases the authorization and updates the order, then notifies you — at merchant_urls.upsell_notification if it’s set, otherwise at the notification_uri your upsell response returned. Re-read the order through the Order Management API.
  7. The window closes and Kustom sends the confirmation push.
  8. Your backend reads the final order and releases the hold for fulfillment.

If you do choose it: the order must stay updatable for the whole upsell window. Don’t send it to the warehouse and don’t send a confirmation email until the push arrives, or you’ll ship an order that is about to change.

Either way, you can tell whether to expect a delayed push from options.confirmation_page_upsell on the order, or from upsell_possible in the upsell request.

In Playground, with upsell enabled on the account:

  1. Create an order with options.confirmation_page_upsell: true and complete a purchase.
  2. Your merchant_urls.upsell endpoint is called before the confirmation page renders, and the request carries upsell_possible: true and a non-zero max_upsell_amount.
  3. The widget renders your recommendations, with images.
  4. Adding an item doesn’t ask for payment details, the order’s order_lines grow and your endpoint is called again.
  5. The confirmation push arrives after the window closes, carrying the final lines.
  6. For a purchase where upsell_possible is false, no widget appears and the push arrives immediately.

To find what was upsold, diff the order_lines on the original checkout order against the order_lines in the confirmation push. For a signal per item as it’s added, set feedback_url on each upsell line.

Validate each add. Set merchant_urls.upsell_validation and Kustom asks before processing an addition. The body is the ordinary order model plus upsell_order_lines, the lines being requested. Return 2xx to allow. Anything else blocks it.

{
"merchant_urls": {
"upsell_validation": "https://www.example.com/api/upsell-validation"
}
}

Do not treat that response as final. The authorization increase can still fail after you approve. The confirmation push remains the source of truth.

Close the widget early. Return last_upsell_time in your upsell response and the widget stops accepting additions at that moment. It doesn’t bring the push forward: the push still waits for the window configured on your account.

Re-rating shipping after an add. If your carrier pricing depends on the cart, either pre-filter in your upsell endpoint so every candidate keeps the current shipping method valid — the recommended route — or re-rate afterwards and handle a method change by your own policy, optionally via the shipping_option_update callback.

A/B testing. Swap the merchant_urls.upsell endpoint whenever you like, or split traffic with options.confirmation_page_upsell. Remember that curated Portal products take precedence over an endpoint, so clear the curation to test an endpoint against it.

Minimal confirmation. Upsell is compatible with the minimal_confirmation GUI option and overrides it automatically while the widget is shown.

The widget never appears. Either the order didn’t qualify, for example because of the payment method — check upsell_possible — or there was nothing to show: your endpoint was too slow or returned no lines, or every curated product was filtered out. All of these fail silently by design.

Your endpoint is never called. Your account has Portal-curated products, and they take precedence. Remove the curation if you want the endpoint to decide.

Additions are blocked. They exceed the authorization. Pre-filter on max_upsell_amount. Recommending what will be refused costs conversion.

selected_shipping_option is missing. It’s present whenever the order has a selected shipping option — set from the order’s own shipping_options (the preselected one, else the first) or by the Shipping Assistant. An order that carries no shipping options at all has nothing to report. Filter on your own shipping knowledge instead.

The push is late. That’s the feature. The window defaults to ten minutes, is set per merchant account by Kustom and can’t be changed through the API. The push follows it by about a minute. last_upsell_time closes the widget sooner but doesn’t move the push.

The push is late and you never enabled upsell. Check whether the order carries merchant_urls.upsell, or whether Kustom enabled upsell for your whole account. Either one turns it on without the option.

You want to show a free-shipping threshold in the widget. Not supported. Apply threshold logic on your side when you process the final order.

This page is also available as Markdown — append .md to its URL — and is indexed in llms.txt.