# Events

Register handlers with `cashier.on(CashierEmitEvent.X, handler)`.

> ⚠️ **Warning:** SDK events are for UI updates only. Credit or debit balances only after a [webhook](/guides/concepts/webhooks) or a GET on the transaction confirms the final status. Browser events can be lost (closed tab) or spoofed.


## Payment lifecycle

| Constant | String | Payload | Fires when |
|  --- | --- | --- | --- |
| `PAYMENT_SUCCESS` | `paymentSuccess` | `PaymentEmitEventData` | A payment succeeds. |
| `PAYMENT_FAILED` | `paymentFailed` | `PaymentEmitEventData` | A payment fails. |
| `PAYMENT_PENDING` | `paymentPending` | `PaymentEmitEventData` | A payment is pending. |
| `PAYMENT_CANCELED` | `paymentCanceled` | `PaymentEmitEventData` | The customer cancels the payment. |


`PaymentEmitEventData`:

```typescript
type PaymentEmitEventData = {
  transactionId: string;
  status: string;
  currency: string;
  orderId?: string;
  amount?: number;
  merchantTransactionId?: string;
  timestamp?: string;
};
```

## KYC

| Constant | String | Payload | Fires when |
|  --- | --- | --- | --- |
| `KYC_REQUIRED_FIELD_ERRORS` | `kycRequiredFieldErrors` | `KYCRequiredFieldErrorsData[]` | Required KYC fields are missing or invalid. |
| `KYC_REQUIRED_LEVEL_ERRORS` | `kycRequiredLevelErrors` | `KYCRequiredLevelErrorsData[]` | Required KYC level(s) are incompatible. |


```typescript
type KYCRequiredFieldErrorsData = {
  fieldName: string;
  issue: "MISSING" | "INVALID";
  errorMessage: string;
};
type KYCRequiredLevelErrorsData = {
  level: string;
  description: string;
};
```

## Analytics

| Constant | String | Payload | Fires when |
|  --- | --- | --- | --- |
| `ANALYTICS_EVENT` | `ANALYTICS_EVENT` | `PaymentEmitEventData` | Once per unique `transactionId`, deduplicated across retries, navigation and tabs. |


## Iframe lifecycle

| Constant | String | Payload | Fires when |
|  --- | --- | --- | --- |
| `IFRAME_OPEN_REQUESTED` | `iframeOpenRequested` | `void` | An open is initiated. |
| `IFRAME_OPENED` | `iframeOpened` | `{ sessionId }` | The iframe opened with a session. |
| `CASHIER_LOADED` | `cashierLoaded` | `void` | The Cashier UI finished loading. |
| `IFRAME_CLOSE_REQUESTED` | `iframeCloseRequested` | `void` | A close is initiated. |
| `IFRAME_CLOSED` | `iframeClosed` | `void` | The iframe closed. |
| `IFRAME_DESTROYED` | `iframeDestroyed` | `void` | The iframe was removed from the DOM. |


## Other

| Constant | String | Payload | Fires when |
|  --- | --- | --- | --- |
| `LIVE_CHAT_CLICKED` | `liveChatClicked` | `void` | The Live Chat button is clicked inside the Cashier. |
| `OVERLAY_CLICKED` | `overlayClicked` | `void` | The customer clicks outside the Cashier. |
| `UNKNOWN` | `unknown` | `{ type, data }` | An unrecognised message was received. |


## `ANALYTICS_EVENT` vs `PAYMENT_SUCCESS`

- Use **`PAYMENT_SUCCESS`** for immediate UI reactions (a success screen, a spinner).
- Use **`ANALYTICS_EVENT`** for analytics and conversion tracking — it fires exactly once per `transactionId`, so it won't double-count across retries, navigations or tabs.


```typescript
cashier.on(CashierEmitEvent.ANALYTICS_EVENT, (data) => {
  gtag("event", "purchase", {
    transaction_id: data.transactionId,
    currency: data.currency,
  });
});
```

> ℹ️ **Note:** Hosted Payment Fields emit their own field events (`FIELD_VALIDITY_CHANGE`, `FIELD_FOCUS`, `FIELD_BLUR`) — documented in the [HPF → Events](/hosted-payment-fields/events) page.


## Build it →

- [Errors](/cashier/sdk/errors) · [Methods](/cashier/sdk/methods)