# 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.

## Getting the checkout API handle

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](/checkout/integrate/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:

```js
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

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.

```js
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`:

```js
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.

## The event catalogue

| Event | Fires when | Payload |
| --- | --- | --- |
| `load` | The checkout iframe has rendered | Customer type and the address fields known so far |
| `user_interacted` | The customer first touches the iframe | `type` — `mousedown` or `keydown`, or `click` when the first touch is an express checkout button |
| `customer` | The checkout detects or changes customer type | `type` — `person` or `organization` |
| `change` | Billing details change — postal code, country, email | The changed fields. In AT and DE, narrowed to `country`, `postal_code` and `city` |
| `billing_address_change` | A valid billing address is detected | `postal_code`, `country` |
| `shipping_address_change` | A valid shipping address is detected | `postal_code`, `country` |
| `shipping_option_change` | The customer selects a shipping option | The full option — `price` and `tax_amount` in minor units, `tax_rate` a percentage |
| `shipping_address_update_error` | Updating shipping failed | Empty object |
| `order_total_change` | The order total changes | `order_total` in minor units |
| `checkbox_change` | A checkbox is ticked or unticked | `key`, `checked` |
| `can_not_complete_order` | The customer needs an alternative payment option, for example after a declined credit check | Empty object |
| `network_error` | The iframe detects a network problem | Empty object |
| `redirect_initiated` | The customer is about to be redirected to your confirmation page | `true` |
| `load_confirmation` | The confirmation snippet has rendered | Empty object |
| `validation_callback` | The customer clicked **Pay order**, before the server request | Empty object, plus a callback |

### Payload shapes

`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.

```json
{
  "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.

```json
{
  "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%.

```json
{
  "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`:

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

## 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.

```js
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](/checkout/integrate/validation-callback/) on your server.

## Forwarding events to an analytics tool

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:

```js
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.

## Related

- [Render Checkout snippet](/checkout/integrate/render-checkout-snippet/) — embed the snippet and
  get hold of the checkout API object.
- [Server-side callbacks](/checkout/callbacks/) — the server half, for anything you
  need to be authoritative.
- [Answer the callbacks](/checkout/integrate/validation-callback/) — block a purchase from
  your server rather than the browser.
- [Troubleshooting Checkout](/checkout/troubleshooting/) — what breaks the iframe
  and how to spot it.
