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

# Handle lifecycle events

> Subscribe to Onramp widget lifecycle events to drive your UI, handle session expiry, and tear down cleanly.

The Onramp widget reports its progress back to your page through a `postMessage`
channel. The kit wraps that channel and surfaces it as strongly typed event
subscriptions on the widget controller returned by `mountIframe` or
`openWindow`.

## Events

Every event the widget dispatches has the shape:

```typescript TypeScript theme={null}
type OnrampEvent = {
  event: OnrampEventType; // "INITIALIZATION_SUCCESS" | "DEPOSIT_SETTLED" | ...
  code: OnrampEventCode; // "INVALID_SESSION_TOKEN" | "CANCELED_BY_CUSTOMER" | ...
  payload?: Record<string, unknown>;
};
```

* `event` is the high-level effect. For example, `INITIALIZATION_ERROR`
  indicates that the widget couldn't start.
* `code` narrows the cause. For example, an `INITIALIZATION_ERROR` with code
  `INVALID_SESSION_TOKEN` means the session token was missing or invalid.
* `payload` carries event-specific metadata such as `amount`, `tokenSymbol`,
  `paymentMethod`, `orderId`, and `transactionHash`. Errors include
  `errorMessage` and a boolean `canRetry`.

The full list of event types and codes is exported from the App Kit SDK as
`ONRAMP_EVENT_TYPES` and `ONRAMP_EVENT_CODES`.

<Note>
  The widget controller does not validate event envelopes against a closed schema.
  A widget release that adds a new event, code, or payload field is delivered to
  your wildcard handler verbatim instead of being dropped. Known events still get
  type-safe callbacks for autocomplete.
</Note>

### Event types

| Event                    | Description                                                   |
| ------------------------ | ------------------------------------------------------------- |
| `INITIALIZATION_SUCCESS` | Onramp loaded successfully and is waiting for user input.     |
| `INITIALIZATION_ERROR`   | Onramp could not start. See `code` for the cause.             |
| `DEPOSIT_SUBMITTED`      | The customer submitted a deposit request.                     |
| `DEPOSIT_SETTLED`        | A previously submitted deposit settled onchain.               |
| `DEPOSIT_NOT_COMPLETED`  | The deposit flow ended without a settled deposit. See `code`. |

### INITIALIZATION\_ERROR codes

| Code                    | Description                                                                            |
| ----------------------- | -------------------------------------------------------------------------------------- |
| `PAGE_NOT_LOADED`       | The iframe or popup failed to load, was blocked, or did not initialize before timeout. |
| `INVALID_SESSION_TOKEN` | The Onramp app rejected the launch because the session token was missing or invalid.   |

### DEPOSIT\_NOT\_COMPLETED codes

| Code                      | Description                                                             |
| ------------------------- | ----------------------------------------------------------------------- |
| `SESSION_TIMEOUT`         | The Onramp session expired before the customer completed a deposit.     |
| `CANCELED_BY_CUSTOMER`    | The customer chose not to complete the deposit.                         |
| `NO_PAYMENT_OPTIONS`      | No eligible payment options were available for the customer or region.  |
| `CUSTOMER_PENDING_REVIEW` | The customer is still under review and cannot complete a deposit yet.   |
| `CUSTOMER_REJECTED`       | The customer failed KYC or provider review.                             |
| `PAYMENT_PROVIDER_ERROR`  | A payment provider or integration failure prevented deposit completion. |

## Subscribe with typed callbacks

The simplest way to handle events is to pass callback options to `mountIframe`
or `openWindow`. Each option corresponds to one event:

```typescript TypeScript theme={null}
kit.onramp.mountIframe({
  session,
  container,
  onInitializationSuccess: () => {
    console.log("widget ready");
  },
  onDepositSubmitted: ({ payload }) => {
    showSubmittedToast(payload.amount, payload.tokenSymbol);
  },
  onDepositSettled: ({ payload }) => {
    refreshBalance();
  },
  onDepositNotCompleted: ({ code, payload }) => {
    if (code === "CANCELED_BY_CUSTOMER") return;
    showRetryDialog(payload.errorMessage);
  },
  onInitializationError: ({ code }) => {
    if (code === "INVALID_SESSION_TOKEN") return refreshSession();
    if (code === "PAGE_NOT_LOADED") return showLoadFailureToast();
  },
});
```

## Subscribe after the widget is mounted

The widget controller returned by `mountIframe` and `openWindow` has `on` and
`off` methods. Use them when you need to add listeners after construction or
manage subscriptions dynamically:

```typescript TypeScript theme={null}
const widget = kit.onramp.mountIframe({ session, container });

widget.on("DEPOSIT_SETTLED", ({ payload }) => {
  refreshBalance();
});

const handler = ({ code }: { code: string }) => {
  if (code === "INVALID_SESSION_TOKEN") refreshSession();
};
widget.on("INITIALIZATION_ERROR", handler);

// Later:
widget.off("INITIALIZATION_ERROR", handler);
```

## Subscribe to every event

Pass `'*'` to receive every envelope as it is dispatched. This pattern works
well for analytics:

```typescript TypeScript theme={null}
widget.on("*", (envelope) => {
  analytics.track("onramp", {
    event: envelope.event,
    code: envelope.code,
    ...envelope.payload,
  });
});
```

Wildcard subscriptions receive future event types the widget may add, with no
SDK upgrade required.

## Handle session expiry

A session can expire while the widget is open if the user idles long enough.
When that happens, the widget emits `DEPOSIT_NOT_COMPLETED` with code
`SESSION_TIMEOUT`. The App Kit SDK surfaces this and the `INVALID_SESSION_TOKEN`
initialization error through a dedicated `onSessionExpired` callback so you do
not have to branch on codes by hand:

```typescript TypeScript theme={null}
let widget: { close: () => void } | undefined;

widget = kit.onramp.mountIframe({
  session,
  container,
  onSessionExpired: async () => {
    const fresh = await kit.onramp.fetchSession({
      url: "/api/onramp/sessions",
      body: { appUserId, destinationAddress },
    });
    widget?.close();
    widget = kit.onramp.mountIframe({ session: fresh, container });
  },
});
```

`onSessionExpired` fires alongside `onDepositNotCompleted` or
`onInitializationError`, so existing handlers continue to run. The dedicated
callback is your "mint a new session and re-launch" signal.

## Close unused widgets

Each `mountIframe()` call, and each successful `openWindow()` call, returns a
widget controller. Keep that controller and call `widget.close()` before
starting over or when the user leaves the view.

```typescript TypeScript theme={null}
let widget: { close: () => void } | undefined;

function mountCurrentSession() {
  widget?.close();
  widget = kit.onramp.mountIframe({ session, container });
}

function stopOnramp() {
  widget?.close();
  widget = undefined;
}
```

For iframe mode, the kit auto-disposes the widget if the container is removed
from the DOM without a `close()` call. Call `widget.close()` when you know the
widget is no longer needed.

## Webhooks remain the source of truth

`DEPOSIT_SETTLED` is a UX signal, not a deposit completion record. A user can
close the tab between `DEPOSIT_SUBMITTED` and `DEPOSIT_SETTLED` and never
receive the settlement event, even though the deposit succeeds. Use lifecycle
events to update the UI; reconcile final deposit state from webhook events
delivered to your backend.
