Skip to content

Checkout JavaScript events

The events the Checkout iframe emits to the page around it — load, customer type, address and shipping changes, order total, checkboxes, network errors — with the payload each one carries, plus suspend and resume, and how to forward events to an analytics tool.

The Checkout iframe talks to the page it’s embedded in. It emits an event whenever the customer does something the surrounding page might care about — enters an address, picks a shipping option, ticks a checkbox — and it accepts two commands, suspend and resume, so your page can freeze the iframe while it updates the order behind the scenes. This page is the catalogue of those events and their payloads. Treat everything here as presentational: use it to keep your own cart panel in sync or to feed analytics, and use the Checkout API as the source of truth for anything you act on.

The script inside html_snippet installs a global accessor on your page once the iframe has rendered. You pass it a function and it hands back the checkout API object. Render Checkout snippet documents that accessor and its current name. Every example below assumes you already hold the resulting api object.

Register listeners with api.on(), passing an object whose keys are event names:

api.on({
load: function (data) {
// data is the payload documented below
},
order_total_change: function (data) {
updateMerchantPanelTotal(data.order_total);
},
});

Suspending and resuming around an order update

Section titled “Suspending and resuming around an order update”

When your own page changes the cart — the customer edits a quantity, applies a coupon, removes a line — the iframe doesn’t know about it until you push the change to the Checkout API. suspend locks the iframe so the customer can’t interact with a stale total. resume unlocks it and makes it refresh from the updated order.

api.suspend();
// POST the update to the order location URI, then:
api.resume();

A suspend resumes itself automatically after 10 seconds. Disable that if your update can legitimately take longer, and make sure every code path still calls resume:

api.suspend({ autoResume: { enabled: false } });

Between suspend and resume, update the order with a POST to the order location URI you already hold. Never create a new order to reflect a cart change — that issues a new order id and orphans the one the customer is looking at.

EventFires whenPayload
loadThe checkout iframe has renderedCustomer type and the address fields known so far
user_interactedThe customer first touches the iframetype — mousedown or keydown, or click when the first touch is an express checkout button
customerThe checkout detects or changes customer typetype — person or organization
changeBilling details change — postal code, country, emailThe changed fields. In AT and DE, narrowed to country, postal_code and city
billing_address_changeA valid billing address is detectedpostal_code, country
shipping_address_changeA valid shipping address is detectedpostal_code, country
shipping_option_changeThe customer selects a shipping optionThe full option — price and tax_amount in minor units, tax_rate a percentage
shipping_address_update_errorUpdating shipping failedEmpty object
order_total_changeThe order total changesorder_total in minor units
checkbox_changeA checkbox is ticked or untickedkey, checked
can_not_complete_orderThe customer needs an alternative payment option, for example after a declined credit checkEmpty object
network_errorThe iframe detects a network problemEmpty object
redirect_initiatedThe customer is about to be redirected to your confirmation pagetrue
load_confirmationThe confirmation snippet has renderedEmpty object
validation_callbackThe customer clicked Pay order, before the server requestEmpty object, plus a callback

load reports what the checkout already knows, so it’s the event to seed your panel from. shipping_address.country is an alpha-3 code.

{
"customer": { "type": "person" },
"shipping_address": { "country": "swe", "postal_code": "16972" }
}

change carries whichever billing fields the customer has supplied. Values may be prefilled or obfuscated, and the object is often partial.

{
"email": "shopper@example.com",
"postal_code": "12345",
"country": "deu"
}

shipping_option_change carries the whole option. price and tax_amount are in minor units; tax_rate is a percentage with two implicit decimals, so 2500 is 25%.

{
"id": "express",
"name": "Express delivery",
"description": "Delivered the next working day",
"promo": "Free over 500 SEK",
"price": 9900,
"tax_amount": 1980,
"tax_rate": 2500
}

checkbox_change identifies the checkbox by the id you gave it in options.additional_checkboxes. The older single-checkbox option has no id of its own, so it reports the fixed key additional_checkbox_from_merchant:

{
"key": "additional_checkbox_from_merchant",
"checked": true
}

Running client-side validation before the purchase

Section titled “Running client-side validation before the purchase”

validation_callback fires when the customer clicks Pay order, before Kustom makes any server request. It’s the only event with a second argument, and you have 10 seconds to call that callback with a should_proceed value.

Answering late is the same as allowing it. If the 10 seconds pass with no answer, Kustom proceeds with the purchase rather than blocking it. A handler that waits on a slow service, or throws before reaching the callback, doesn’t stop anything — it just delays the customer by ten seconds and then lets the purchase through. Keep the handler fast and synchronous where you can, and never rely on it failing closed.

api.on({
validation_callback: function (data, callback) {
callback({
should_proceed: false,
message: 'Sorry, we cannot ship to that address.',
});
},
});

data is always an empty object here — it exists only to keep the handler signature consistent. Enable the event by setting options.require_client_validation to true when you create the order.

This is a customer-experience check, not a control. It runs in the browser and can be bypassed. Anything that must hold — stock, price, delivery restrictions — belongs in the order validation callback on your server.

The event stream maps cleanly onto a purchase funnel, so it’s worth piping into whatever analytics tool you already run. Register one subscriber and forward everything through it rather than writing a handler per event:

const forward = (eventName, data) =>
gtag('event', `checkout_${eventName}`, { ...data });
api.on({
load: (data) => forward('load', data),
user_interacted: (data) => forward('user_interacted', data),
billing_address_change: (data) => forward('billing_address_change', data),
shipping_address_change: (data) => forward('shipping_address_change', data),
redirect_initiated: (data) => forward('redirect_initiated', data),
});

Those five events make a usable funnel on their own: iframe rendered, customer engaged, billing entered, shipping entered, redirected to confirmation. Drop-off between any two steps is a real signal about the checkout.

Two cautions. Several events also fire on load to report default or prefilled data, so a naive count will overstate the first funnel step — deduplicate per session. And event payloads can contain personal data such as email and postal code, so check what your analytics tool is allowed to store before you spread the payload into it.

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