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

# Analytics

> Track widget funnel events and analytics delivery diagnostics.

The `onEvent` callback delivers modal funnel events and analytics delivery diagnostics. Funnel events use `DepositAnalyticsEvent`, `WithdrawAnalyticsEvent`, or `ClaimAnalyticsEvent`; diagnostics use `AnalyticsIngestFailureEvent`.

`onEvent` is browser UI telemetry, not server-side settlement confirmation. It stops when the modal closes; use [webhooks](/deposits/headless/processing-and-tracking/webhooks) on your server for settlement work that must run regardless.

```tsx theme={null}
import type {
  AnalyticsIngestFailureEvent,
  ClaimAnalyticsEvent,
  DepositAnalyticsEvent,
} from "@rhinestone/deposit-modal";

onEvent={(event: DepositAnalyticsEvent | ClaimAnalyticsEvent | AnalyticsIngestFailureEvent) => {
  analytics.track(event.type, event);
}}
```

All three funnels use the same event taxonomy where it applies: open, step open and complete, friction, failure, retry, handoff, UI outcome, abandonment, and close. Each modal also has events and fields specific to its flow.

Every position-bearing event uses `step`. Abandonment carries the latest reached step. Close carries that step, or `step: null` only when the session closes before entering the funnel. A UI outcome reports what the widget observed, not backend or on-chain fulfillment truth.

### The session envelope

Every funnel event carries these fields in addition to its event-specific payload:

| Field                | Type                                 | Description                                       |
| -------------------- | ------------------------------------ | ------------------------------------------------- |
| `session_id`         | `string`                             | Widget session identifier                         |
| `modal`              | `"deposit" \| "withdraw" \| "claim"` | Modal that emitted the event                      |
| `widget_version`     | `string`                             | Widget package version                            |
| `timestamp`          | `string`                             | Event timestamp                                   |
| `session_properties` | Modal-specific object                | Configuration snapshot taken when the modal opens |

Deposit funnel events also carry top-level `funding_method`, which is `null` before method selection. `AnalyticsIngestFailureEvent` is a delivery diagnostic, not a funnel event, so it has no `session_properties`.

Session properties stay fixed for the session even if props change while the modal is open.

#### Deposit session properties

| Property                      | Type                                                                                  |
| ----------------------------- | ------------------------------------------------------------------------------------- |
| `enabled_funding_methods`     | `DepositFundingMethod[]`                                                              |
| `wallet_integration`          | `"modal_connected" \| "host_supplied" \| "none"`                                      |
| `fiat_methods`                | `{ source: "backend_resolved" }` or `{ source: "configured"; identifiers: string[] }` |
| `asset_migration_providers`   | `string[]`                                                                            |
| `initial_asset_migration`     | `string \| null`                                                                      |
| `gasless_wallet_flow_enabled` | `boolean`                                                                             |
| `presentation`                | `"inline" \| "overlay"`                                                               |
| `overlay_close_enabled`       | `boolean`                                                                             |
| `prefills`                    | `{ source_chain, source_token, amount, initial_asset_migration: boolean }`            |
| `target_chain`                | `string`, optional                                                                    |
| `target_token`                | `string`, optional                                                                    |

The target is the configured deposit destination.

#### Withdraw session properties

| Property                | Type                                                         |
| ----------------------- | ------------------------------------------------------------ |
| `presentation`          | `"inline" \| "overlay"`                                      |
| `overlay_close_enabled` | `boolean`                                                    |
| `prefills`              | `{ target_chain, target_token, recipient, amount: boolean }` |
| `target_chain`          | `string`, optional                                           |
| `target_token`          | `string`, optional                                           |

The target is the destination on which the form opened, including the source-chain and source-token fallback when no target was supplied. The prefill flags separately record whether your app supplied each value.

#### Claim session properties

| Property                | Type                                                |
| ----------------------- | --------------------------------------------------- |
| `presentation`          | `"inline" \| "overlay"`                             |
| `overlay_close_enabled` | `boolean`                                           |
| `prefills`              | `{ transaction_hash, refund_destination: boolean }` |

`overlay_close_enabled` reports the effective configuration. Backdrop closing is opt-in because `closeOnOverlayClick` defaults to `false`.

#### Target identities

`target_chain` uses CAIP-2: `eip155:<id>`, `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp`, or `hypercore:spot`. `target_token` is the on-chain token identity. EVM addresses and HyperCore hex token IDs are normalized to lowercase, Solana mints preserve case, and the literal `native` is allowed. Invalid chain and token values are omitted independently, so one invalid value does not remove the other or invalidate the session.

### Friction, failure, and retry

* **Friction** means progress is blocked without an attempted operation failing. Its payload carries `step` and `reason`.
* **Failure** means an attempted operation failed or the flow reached a terminal condition. Its payload carries `step`, `reason`, and `retryable`.
* **Retry** is a subsequent explicit attempt after a reported reason. Its payload carries `step` and `reason`.

`retryable` means the widget judged another attempt safe or offered that affordance. It appears only on failure events.

Reasons used by friction, failure, and retry form bounded vocabularies. Each starts with one declared `AnalyticsReasonFamily`: `account_setup`, `amount`, `exchange`, `lookup`, `migration`, `modal`, `processor`, `provider`, `quote`, `recipient`, `recovery`, `refund`, `regional_methods`, `registration`, `route`, `signature`, `submission`, `swapped`, `transfer`, or `wallet`.

Route reasons by matching this declared list longest-prefix first. Do not split at the first underscore: `account_setup_*` and `regional_methods_*` are multiword families. Abandonment reasons, close sources, and ingest-delivery reasons are separate vocabularies and do not follow this convention.

### Deposit analytics

Funding methods are `wallet`, `transfer`, `fiat_onramp`, `exchange_connect`, and `asset_migration`.

#### Deposit steps

| Flow            | Steps                                                                                                                                                                       |
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Shared          | `account_setup`, `funding_method_home`                                                                                                                                      |
| Wallet          | `wallet_connect`, `wallet_source_asset`, `wallet_source_token`, `wallet_amount`, `wallet_review`, `wallet_submit`, `wallet_processing`                                      |
| Transfer        | `transfer_source_selection`, `transfer_address_shown`, `transfer_copy`, `transfer_fee_amount`, `transfer_fee_review`, `transfer_tracking`                                   |
| Fiat            | `fiat_regional_payment_method`, `fiat_provider_mint`, `fiat_provider_handoff`, `fiat_tracking`, `fiat_receipt`                                                              |
| Exchange        | `exchange_selection`, `exchange_fee_acknowledgement`, `exchange_provider_mint`, `exchange_provider_handoff`, `exchange_tracking`, `exchange_finalising`, `exchange_receipt` |
| Asset migration | `migration_boot_resolve`, `migration_provider_selection`, `migration_asset_selection`, `migration_amount`, `migration_review`, `migration_submit`, `migration_processing`   |

#### Deposit events

All payloads below also carry the envelope and top-level `funding_method`.

| Event                               | Payload beyond the envelope                                      |
| ----------------------------------- | ---------------------------------------------------------------- |
| `deposit_modal_open`                | No event-specific fields; `funding_method` is `null`             |
| `deposit_modal_method_selected`     | `funding_method`, `entry_source`, method-dependent `identifiers` |
| `deposit_modal_step_open`           | `step`                                                           |
| `deposit_modal_step_complete`       | `step`                                                           |
| `deposit_modal_friction`            | `step`, `reason`                                                 |
| `deposit_modal_failure`             | `step`, `reason`, `retryable`                                    |
| `deposit_modal_retry`               | `step`, `reason`                                                 |
| `deposit_modal_handoff`             | `step`, `correlator`; transfer only: `identifiers`               |
| `deposit_modal_correlator_observed` | `step`, `correlator`                                             |
| `deposit_modal_ui_outcome`          | `step`, `outcome` (`completed` \| `failed` \| `cancelled`)       |
| `deposit_modal_method_abandoned`    | `step`, `reason`                                                 |
| `deposit_modal_close`               | `source`, nullable `step`, `after_handoff`                       |

`entry_source` is `user` or `initial_config`. Method-selection identifiers are discriminated by `funding_method`:

| Funding method     | `identifiers`                                                                                             |
| ------------------ | --------------------------------------------------------------------------------------------------------- |
| `wallet`           | Required `network` (`evm` \| `solana`) and `integration` (`modal_connected` \| `host_supplied` \| `none`) |
| `transfer`         | Not present                                                                                               |
| `fiat_onramp`      | Required `payment_method` and `source` (`personalized` \| `fallback` \| `configured`)                     |
| `exchange_connect` | Optional, and when present contains exactly `exchange`                                                    |
| `asset_migration`  | Optional, and when present contains exactly `provider`                                                    |

A transfer handoff requires `correlator: { type: "deposit_address", value }` and `identifiers: { source_chain, source_token }`. Those identities describe the payment source, not the session target. Non-transfer handoffs cannot use `deposit_address` and have no identifier bag.

The transfer picker can show a temporary default while its source options load. The modal waits until the picker settles on the source presented to the user before emitting a handoff, so it never publishes that temporary source. If no source identity resolves, the step events remain but no transfer handoff is emitted. A later selection of a distinct source can emit another handoff.

For same-route deposits, the processing step opens before the completed outcome. A transfer emits `deposit_modal_step_open` at `transfer_tracking`, followed by `deposit_modal_ui_outcome` with `outcome: "completed"` and `step: "transfer_tracking"`. A wallet deposit emits the same events in that order at `wallet_processing`. These outcomes report what the widget observed, not backend or on-chain fulfillment.

Correlator types are `deposit_address`, `transaction_hash`, `deposit_id`, and `swapped_external_customer_id`.

Abandonment reasons are `back`, `modal_close`, `provider_back`, `wallet_disconnect`, `method_replacement`, and `initial_migration_fallback`. Close sources are `header_button`, `escape`, `overlay`, `host_controlled`, `provider_back`, `native_dismiss_request`, `success_done`, `failure_cancel`, and `new_deposit_reset`.

#### Deposit reasons

| Family             | Reasons                                                                                                                                                                                                                                                                                                                                     |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `account_setup`    | `account_setup_pending`, `account_setup_stale_response`, `account_setup_failed`                                                                                                                                                                                                                                                             |
| `wallet`           | `wallet_connection_requested`, `wallet_connection_rejected`, `wallet_connection_failed`, `wallet_disconnected`, `wallet_portfolio_load_failed`, `wallet_no_supported_assets`, `wallet_no_funded_assets`, `wallet_chain_switch_rejected`, `wallet_chain_switch_failed`, `wallet_permit_preparation_unavailable`, `wallet_signature_rejected` |
| `amount`           | `amount_invalid`, `amount_insufficient_balance`, `amount_below_minimum`, `amount_above_maximum`                                                                                                                                                                                                                                             |
| `quote`            | `quote_source_price_unavailable`, `quote_target_price_unavailable`, `quote_unavailable`                                                                                                                                                                                                                                                     |
| `submission`       | `submission_rejected`, `submission_failed`, `submission_missing_hash`, `submission_uncertain`                                                                                                                                                                                                                                               |
| `transfer`         | `transfer_source_unavailable`, `transfer_address_unavailable`, `transfer_clipboard_failed`                                                                                                                                                                                                                                                  |
| `processor`        | `processor_status_poll_failed`, `processor_failed`                                                                                                                                                                                                                                                                                          |
| `regional_methods` | `regional_methods_timeout`, `regional_methods_failed`, `regional_methods_fallback`, `regional_methods_empty`                                                                                                                                                                                                                                |
| `exchange`         | `exchange_list_failed`, `exchange_list_empty`, `exchange_setup_unavailable`, `exchange_fee_changed`, `exchange_fee_not_acknowledged`                                                                                                                                                                                                        |
| `swapped`          | `swapped_mint_failed`, `swapped_url_untrusted`, `swapped_iframe_timeout`, `swapped_browser_open_failed`                                                                                                                                                                                                                                     |
| `provider`         | `provider_cancelled`, `provider_failed`                                                                                                                                                                                                                                                                                                     |
| `migration`        | `migration_wallet_required`, `migration_availability_loading`, `migration_availability_failed`, `migration_unavailable`, `migration_no_balance`, `migration_partial_availability`, `migration_coming_soon`                                                                                                                                  |

### Withdraw analytics

Steps are `form`, `review`, `submit`, and `processing`. `submit` is the asynchronous boundary opened by the review CTA, not a separate screen.

| Event                          | Payload beyond the envelope                                          |
| ------------------------------ | -------------------------------------------------------------------- |
| `withdraw_modal_open`          | —                                                                    |
| `withdraw_modal_step_open`     | `step`, optional `same_route`                                        |
| `withdraw_modal_step_complete` | `step`, optional `same_route`                                        |
| `withdraw_modal_friction`      | `step`, `reason`, optional `same_route`                              |
| `withdraw_modal_failure`       | `step`, `reason`, `retryable`, optional `same_route`                 |
| `withdraw_modal_retry`         | `step`, `reason`, optional `same_route`                              |
| `withdraw_modal_handoff`       | `step`, `transaction_hash`, `managed_account`, required `same_route` |
| `withdraw_modal_ui_outcome`    | `step`, `outcome` (`completed` \| `failed`), optional `same_route`   |
| `withdraw_modal_abandoned`     | `step`, `reason` (`back` \| `modal_close`), optional `same_route`    |
| `withdraw_modal_close`         | `source`, nullable `step`, `after_handoff`, optional `same_route`    |

`same_route` is unknown before submit, pinned on handoff, and repeated on later events. A same-route withdrawal creates no backend bridge row, so its UI outcome remains a client observation. Close sources are `header_button`, `escape`, `overlay`, `host_controlled`, `success_done`, and `failure_cancel`.

#### Withdraw reasons

| Family         | Reasons                                                                                                                     |
| -------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `submission`   | `submission_handler_missing`, `submission_rejected`, `submission_failed`, `submission_missing_hash`, `submission_uncertain` |
| `wallet`       | `wallet_balance_unavailable`                                                                                                |
| `route`        | `route_target_tokens_unavailable`                                                                                           |
| `recipient`    | `recipient_invalid`, `recipient_not_allowed`                                                                                |
| `amount`       | `amount_invalid`, `amount_insufficient_balance`                                                                             |
| `registration` | `registration_pending`, `registration_stale`, `registration_failed`, `registration_target_changed`                          |
| `processor`    | `processor_status_poll_failed`, `processor_failed`                                                                          |

### History analytics

History navigation stays in the current deposit or withdraw session. It does not open or advance a host funnel step. Each suffix below has both a `deposit_modal_history_*` and `withdraw_modal_history_*` event name.

| Event suffix     | Payload beyond the envelope                                           |
| ---------------- | --------------------------------------------------------------------- |
| `open`           | `history_visit_id`, nullable host `step`, `badge`, `badge_count`      |
| `row_view`       | `history_visit_id`, `deposit_id`, `status`, `recoverable`, `position` |
| `load_more`      | `history_visit_id`, `page`, `loaded_rows`                             |
| `fallback_open`  | `history_visit_id`, `claim_attempt_id`                                |
| `recovery_start` | `history_visit_id`, `claim_attempt_id`, `entry_source`, `deposit_id`  |

For example, deposit history emits `deposit_modal_history_open`, `deposit_modal_history_row_view`, `deposit_modal_history_load_more`, `deposit_modal_history_fallback_open`, and `deposit_modal_history_recovery_start`. Withdraw uses the same suffixes after `withdraw_modal_`. Withdraw history events may also carry the optional `same_route` classification used by the rest of the withdraw funnel. Deposit history events carry the normal top-level `funding_method`.

| Field          | Values or semantics                                                   |
| -------------- | --------------------------------------------------------------------- |
| `badge`        | `needs_you`, `progress`, or `none`                                    |
| `status`       | `pending`, `processing`, `completed`, `failed`, `refunded`, or `spam` |
| `entry_source` | `history_row` or `history_fallback`                                   |
| `position`     | Zero-based row position                                               |
| `page`         | One-based load-more press count                                       |
| `loaded_rows`  | Rows loaded before the press                                          |

Opening the panel creates a history visit. Closing and reopening creates a new `history_visit_id`. A row emits at most one view event per visit. Opening fallback lookup immediately creates a `claim_attempt_id`, even if the hash is malformed or resolves to no deposit, so lookup failures remain measurable. `history_recovery_start` is later: it fires only when an eligible deposit is selected for recovery.

Recovery runs as a claim session rather than as host funnel steps. Claim events from history carry `entry_source`, `source_session_id`, `history_visit_id`, and `claim_attempt_id`; `deposit_id` joins the selected deposit after lookup. These correlation fields are event payload fields, not `session_properties`. They link `history_fallback_open` or `history_recovery_start` to the claim session.

The host modal delivers those `ClaimAnalyticsEvent` values through `onEvent`. It does not deliver claim progress through deposit or withdraw `onLifecycle`.

### Claim analytics

Steps are `lookup`, `select`, `review`, and `submit`. `submit` is the asynchronous boundary opened by the review CTA, not a separate screen.

Every claim event identifies how the attempt started with `entry_source`: `history_row` or `history_fallback`. Both carry the correlation fields described in [history analytics](#history-analytics).

| Event                          | Payload beyond the envelope                                                         |
| ------------------------------ | ----------------------------------------------------------------------------------- |
| `claim_modal_open`             | —                                                                                   |
| `claim_modal_step_open`        | `step`                                                                              |
| `claim_modal_step_complete`    | `step`                                                                              |
| `claim_modal_lookup_result`    | `matches`, `eligible`                                                               |
| `claim_modal_deposit_selected` | `deposit_id`, `transaction_hash: string \| null`, `auto_selected`                   |
| `claim_modal_friction`         | `step`, `reason`                                                                    |
| `claim_modal_failure`          | `step`, `reason`, `retryable`                                                       |
| `claim_modal_retry`            | `step`, `reason`                                                                    |
| `claim_modal_handoff`          | `step`, `deposit_id`, `transaction_hash: string \| null`, `refund_transaction_hash` |
| `claim_modal_ui_outcome`       | `step`, `outcome` (`completed` \| `failed`)                                         |
| `claim_modal_abandoned`        | `step`, `reason` (`back` \| `modal_close`)                                          |
| `claim_modal_close`            | `source`, nullable `step`, `after_handoff`                                          |

Most events arrive at most once per attempt. `claim_modal_lookup_result` arrives once per submitted search. The searched hash, refund destination, and deposit amount are not sent. Identities enter the stream only after a lookup matches a backend deposit row.

`transaction_hash` is the deposit's source transaction hash; `refund_transaction_hash` is the submitted refund transaction. A missing source hash is `null`, never an empty string, and `deposit_id` is the authoritative join key.

A claim UI outcome is what the widget observed at submit, not on-chain settlement. Close sources are `header_button`, `escape`, `overlay`, `host_controlled`, `success_done`, `failure_cancel`, and `history_back`. The last value returns a history-originated claim to its history panel.

#### Claim reasons

| Family      | Reasons                                                                                                       |
| ----------- | ------------------------------------------------------------------------------------------------------------- |
| `lookup`    | `lookup_transaction_hash_invalid`, `lookup_failed`, `lookup_no_deposits_found`, `lookup_no_eligible_deposits` |
| `refund`    | `refund_destination_invalid`, `refund_reconciliation_required`, `refund_failed`, `refund_service_unreachable` |
| `recovery`  | `recovery_deposit_data_incomplete`, `recovery_deposit_not_recoverable`, `recovery_unsupported`                |
| `signature` | `signature_rejected`, `signature_invalid`, `signature_verification_unavailable`                               |
| `modal`     | `modal_close_refused_in_flight`                                                                               |

<Note>
  Recovery from history is analytics-only for the host and arrives through `onEvent`. There is no claim `onLifecycle`.
</Note>

### Opting out

`enableAnalyticsIngest={false}` on any modal turns Rhinestone collection off entirely. `onEvent` receives the same events either way, so your own pipeline is unaffected.

Collection and attribution are separate: leaving the [`POST /analytics/ingest-token`](/deposits/overview/widget/backend-setup#the-analytics-token-route) route off your proxy leaves sessions unattributed but does not stop collection.

### Ingest failures

When delivery to Rhinestone fails, `onEvent` receives an `AnalyticsIngestFailureEvent`. It carries `type`, `session_id`, `modal`, `widget_version`, `timestamp`, `reason`, `dropped_events`, and `status` when an HTTP response exists. It has no `session_properties` and is never sent to Rhinestone.

| `reason`            | Meaning                                                                                                                                                            |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `network`           | The request did not complete, including a [`connect-src` CSP block](/deposits/overview/widget/widget-configuration#content-security-policy)                        |
| `unauthorized`      | Ingest refused the credential after refresh and unattributed retry                                                                                                 |
| `token_unavailable` | The proxy lacks or refused [`POST /analytics/ingest-token`](/deposits/overview/widget/backend-setup#required-routes); attribution is lost but collection continues |
| `throttled`         | Rate limiting persisted after retries                                                                                                                              |
| `unavailable`       | Ingest remained unavailable after retries                                                                                                                          |
| `conflict`          | The batch conflicted with events already stored for the session                                                                                                    |
| `rejected`          | Ingest refused the batch; it is not retried                                                                                                                        |
| `unsupported`       | The environment has no usable `fetch` or proxy origin                                                                                                              |

It fires at most once per distinct reason per session, with a running `dropped_events` count.

<Note>
  Analytics never blocks or interrupts a deposit, withdrawal, or claim. Ignoring a delivery diagnostic costs analytics only.
</Note>
