> ## Documentation Index
> Fetch the complete documentation index at: https://docs.salesive.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Client events

> Listen for shopper actions on the storefront from an injected app script, without watching network calls.

Salesive publishes a small, semantic event bus on every storefront page. An app
that injects a script can subscribe to it and learn when a shopper adds to cart,
signs in, places an order and more, without bundling an SDK and without patching
`fetch`.

The runtime loads automatically. Nothing to install.

```html theme={null}
<script>
  window.SalesiveEvents?.on("cart:item_added", (event) => {
    console.log(event.data.productId, event.data.quantity);
  });
</script>
```

## Why not watch network calls

A response code does not tell you what happened. `GET /cart` answers 200 on
every page load. The event that matters most, a completed payment, does not
happen in the browser at all. URLs also carry personal data in query strings,
and patching `fetch` breaks when another library wraps it first.

The bus emits what happened, with identifiers only.

## The API

`window.SalesiveEvents` exposes:

| Method | What it does |
| - | - |
| `on(name, handler)` | Subscribe. Returns an unsubscribe function. |
| `once(name, handler)` | Subscribe for one event. |
| `off(name, handler)` | Remove a handler. |
| `onAny(handler)` | Every event, for a catch all integration. |
| `emit(name, data)` | Emit your own event, namespaced to your app. |
| `observeNetwork()` | Opt in network fallback, see below. |
| `catalog` | The event names and their payload fields. |
| `version` | Runtime version. |

### If your script loads before the runtime

Queue your handlers. The runtime drains and clears the queue when it starts.

```html theme={null}
<script>
  (window.SalesiveEventsQueue ||= []).push(["order:paid", (event) => {
    myApp.report(event.data.orderId);
  }]);
</script>
```

## Event shape

```json theme={null}
{
  "name": "cart:item_added",
  "id": "evt_01H...",
  "at": "2026-11-08T10:00:00.000Z",
  "source": "sdk",
  "data": { "productId": "65f...", "variantId": null, "quantity": 2 }
}
```

`source` is `sdk`, `theme`, `network` or `app`. Payloads contain identifiers
only. No email, phone number, address, token or cart contents are ever included.

## Events

| Event | Data |
| - | - |
| `auth:otp_requested` | `channel` |
| `auth:signed_in` | `userId` |
| `auth:signed_out` | none |
| `cart:item_added` | `productId`, `variantId`, `quantity`, `cartId` |
| `cart:item_removed` | `productId`, `variantId` |
| `cart:quantity_changed` | `productId`, `variantId`, `quantity` |
| `cart:cleared` | `cartId` |
| `cart:coupon_applied` | `code`, `cartId` |
| `cart:coupon_removed` | `code` |
| `cart:shipping_selected` | `optionId` |
| `checkout:started` | `cartId` |
| `checkout:address_saved` | `addressId` |
| `payment:initiated` | `orderId`, `provider` |
| `payment:completed` | `orderId` |
| `payment:failed` | `orderId`, `reasonCode` |
| `order:placed` | `orderId` |
| `order:paid` | `orderId` |
| `order:cancelled` | `orderId` |
| `product:viewed` | `productId` |
| `category:viewed` | `categoryId` |
| `search:performed` | `query`, `queryLength`, `resultCount` |
| `blog:post_viewed` | `postId` |
| `wishlist:added` | `productId` |
| `wishlist:removed` | `productId` |
| `wishlist:cleared` | none |
| `review:submitted` | `productId`, `rating` |
| `newsletter:subscribed` | none |
| `library:book_opened` | `productId` |
| `library:auto_renew_changed` | `productId`, `enabled` |

The search event carries the query text, trimmed and capped at 120 characters.
It is the only event allowed to carry free text. Treat it as shopper data:
someone can type anything into a search box, including an email address or a
phone number, so keep it only as long as your app needs it and never use it as
an identifier.

`category:viewed` and `blog:post_viewed` are emitted by the theme, since no API
call happens when a shopper opens a category or a post. `cart:coupon_removed`
is defined for completeness; the store API has no coupon removal route yet, so
it does not fire today.

## Network fallback

Pages that do not use the Salesive SDK emit fewer events. `observeNetwork()`
turns on a fallback that watches `fetch` and `XMLHttpRequest` and maps a small,
platform maintained route table to the same event names.

```js theme={null}
const stop = window.SalesiveEvents.observeNetwork();
// later
stop();
```

It emits only on 2xx responses, only for known routes, and only identifiers
pulled from the response. It never reports the URL, query string, status code,
headers or body. It is safe to call twice and it never throws into the page.

## Events are not the record of truth

Use client events for user experience, attribution and analytics. Use
[webhooks](https://docs.salesive.com/apps-api/webhooks.md) for anything that must be correct: `orders/paid`
from your app's server is the authoritative signal that a payment completed. A
browser event can be lost to a reload, a blocked script or a closed tab.

## Scope

The bus is visible to any script on the page, which is the same trust boundary
as the page itself. It never carries data the page does not already have.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.