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

# Status tracking

> Track deposit progress and react to state changes via the onLifecycle callback.

The modal emits every state transition through a single `onLifecycle` callback.
You switch on `event.type` to update your UI, trigger backend processes, or log
analytics. New event variants can be added without changing the prop surface.

## Deposit lifecycle

```mermaid actions={false} theme={null}
sequenceDiagram
    participant App as Your app
    participant Modal as Deposit modal
    participant Chain as Blockchain

    Modal->>App: onReady
    Note over Modal: User picks a funding method
    Modal->>App: onLifecycle "connected" (wallet only)
    Note over Modal: User selects chain, token, amount
    Modal->>Chain: Submit deposit tx
    Modal->>App: onLifecycle "submitted"
    Note over Chain: Bridge in progress
    Chain-->>Modal: Funds arrive on target chain
    Modal->>App: onLifecycle "complete"
```

1. The modal initializes and fires `onReady`
2. If the user funds from a wallet, `"connected"` fires with the EOA `address` and
   the `smartAccount` the deposit lands on
3. The user selects a source chain, token, and amount, then confirms
4. The modal submits the transaction on the source chain and emits `"submitted"`
5. The bridge routes funds to the target chain. Once they arrive, the modal
   emits `"complete"`

If the bridge fails after submission, `"failed"` is emitted instead of
`"complete"`.

<Warning>
  `"connected"` fires only for wallet funding. QR transfer, fiat on-ramp and exchange
  connect involve no wallet, so it never fires — use `onReady` if you need a "flow
  started" signal.
</Warning>

## onLifecycle

`onLifecycle` receives a discriminated union — `DepositLifecycleEvent` on
`<DepositModal>`, `WithdrawLifecycleEvent` on `<WithdrawModal>`. The two are
similar but not identical; see [withdraw events](#withdraw-events) for the
differences.

```tsx theme={null}
import type { DepositLifecycleEvent } from "@rhinestone/deposit-modal";

<DepositModal
  // ...required props
  onLifecycle={(event: DepositLifecycleEvent) => {
    switch (event.type) {
      case "connected":
        console.log("smart account", event.smartAccount);
        break;
      case "submitted":
        console.log("source tx", event.txHash, "on", event.sourceChain);
        break;
      case "complete":
        console.log("done", event.destinationTxHash, event.amount);
        break;
      case "failed":
        console.error("failed", event.txHash, event.error);
        break;
      case "balance-changed":
        setBalance(event.totalUsd);
        break;
      case "smart-account-changed":
        setSmartAccount({ evm: event.evm, solana: event.solana });
        break;
    }
  }}
/>
```

### Deposit events

| `event.type`              | Fields                                                                                                                                                                                                                                   | Description                                       |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
| `"connected"`             | `address: Address`, `smartAccount: Address`                                                                                                                                                                                              | A wallet was connected as the funding source      |
| `"submitted"`             | `txHash: string`, `sourceChain: ChainId \| "unknown"`, `amount: string`, `sourceDecimals?: number`, `amountUsd?: string`                                                                                                                 | Deposit transaction submitted on the source chain |
| `"complete"`              | `txHash: string`, `destinationTxHash?: string`, `amount: string`, `sourceChain: ChainId \| "unknown"`, `sourceToken?: string`, `sourceDecimals?: number`, `amountUsd?: string`, `targetChain: number \| "solana"`, `targetToken: string` | Tokens arrived on the target chain                |
| `"failed"`                | `txHash: string`, `error?: string`                                                                                                                                                                                                       | Bridge or transfer failed after submission        |
| `"balance-changed"`       | `totalUsd: number`                                                                                                                                                                                                                       | The user's total portfolio balance (USD) changed  |
| `"smart-account-changed"` | `evm: Address \| null`, `solana: string \| null`                                                                                                                                                                                         | The resolved smart account addresses changed      |

`amount` is in the source token's base units — divide by `sourceDecimals` to display
it. `sourceDecimals` is omitted when the token isn't recognised, which happens for a
QR deposit of an unlisted token.

`amountUsd` is the USD value as entered in the modal. It is omitted for flows with no
amount input: QR transfer, fiat on-ramp, and exchange connect.

<Warning>
  `sourceChain: "unknown"` is deposit-only. When a webhook-detected deposit
  arrives without chain or token information, `sourceChain` is `"unknown"` and
  `sourceToken` is `undefined` — handle this branch so you don't pick the wrong
  explorer URL.
</Warning>

### Withdraw events

`WithdrawLifecycleEvent` carries the same `type` values minus `"balance-changed"`
and `"smart-account-changed"`. Its `txHash` is `Hex`, `sourceChain` is always a
`number`, `sourceToken` / `targetToken` are `Address`, and `"submitted"` adds an
`accountAddress: Address` field. `"connected"` means the deposit account for the
chosen target is registered and fundable, not that a wallet connected.

### Claim events

`ClaimLifecycleEvent` is a separate union — `lookup`, `refund_requested`, `complete`,
`failed`. See [claim modal](/deposits/widget/claim-modal#lifecycle-events).

## onReady

Fires once when the modal is initialized and ready for interaction. No payload.

```tsx theme={null}
onReady={() => console.log("modal ready")}
```

## onError

Fires on errors at any stage — wallet connection, transaction signing, bridge
setup — that prevent the deposit from being submitted. Distinct from the
`"failed"` lifecycle event, which covers failures after the source transaction
confirms.

```tsx theme={null}
onError={(data) => console.error(`[${data.code}] ${data.message}`)}
```

| Field     | Type                  | Description              |
| --------- | --------------------- | ------------------------ |
| `message` | `string`              | Error description        |
| `code`    | `string \| undefined` | Error code, if available |

### Codes

| `code`                              | Meaning                                                          |
| ----------------------------------- | ---------------------------------------------------------------- |
| `WITHDRAW_MISSING_SEND_TRANSACTION` | `<WithdrawModal>` opened without an `onSendTransaction` function |
| `WITHDRAW_REGISTER_FAILED`          | Registering the withdrawal's deposit account failed              |
| `WITHDRAW_FLOW_ERROR`               | The withdrawal failed after the form was submitted               |
| `CLAIM_LOOKUP_FAILED`               | `<ClaimModal>` could not look up the pasted transaction hash     |
| `CLAIM_FAILED`                      | The refund request failed                                        |
| `SWAPPED_CONNECT_EXCHANGES_FAILED`  | The exchange list could not be fetched                           |

Errors without a code carry only `message`. For bridge-level codes, see [deposit processing error codes](/deposits/api/deposit-processing#error-codes).

## Error handling

| Stage               | Signal                   | Typical causes                              |
| ------------------- | ------------------------ | ------------------------------------------- |
| Wallet connection   | `onError`                | User rejected connection, network error     |
| Transaction signing | `onError`                | User rejected transaction, insufficient gas |
| After submission    | `onLifecycle` `"failed"` | Bridge failure, timeout, price deviation    |
| Any stage           | `onError`                | Unexpected errors                           |

After the source chain transaction confirms, the deposit service may
[retry automatically](/deposits/api/deposit-processing#retries) before the
`"failed"` event fires.

## Analytics

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

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

onEvent={(event: DepositAnalyticsEvent | 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`                                                                          |

### Claim analytics

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

| 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`, and `failure_cancel`.

#### 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>
  These are not the claim [lifecycle events](#claim-events). `onLifecycle` reports where the claim itself got to; `onEvent` reports funnel behavior.
</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/widget/backend#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/widget/deposit-modal#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/widget/backend#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>
