> ## 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 status via polling or webhooks — two approaches for monitoring the deposit lifecycle.

There are two ways to track a deposit through the processing pipeline:

* **Polling** — query the `GET /deposits` endpoint filtered by transaction hash. Simple and stateless.
* **Webhooks** — receive push notifications as deposits move through each stage. Real-time and event-driven.

Use whichever fits your architecture. Many integrations combine both: webhooks for real-time updates, polling as a fallback or for on-demand status checks.

## Polling

Query the `GET /deposits` endpoint with the `txHash` parameter to look up a deposit by its source transaction hash.

```ts theme={null}
const DEPOSIT_SERVICE_URL =
    "https://v1.orchestrator.rhinestone.dev/deposit-processor";
const API_KEY = "YOUR_RHINESTONE_API_KEY";

const txHash = "0xabc123...";

const response = await fetch(
    `${DEPOSIT_SERVICE_URL}/deposits?txHash=${txHash}`,
    {
        headers: { "x-api-key": API_KEY },
    },
);

const { deposits } = await response.json();
```

### Response

Each item in the `deposits` array has the following shape:

| Field               | Type             | Description                                                                                                                     |
| ------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `id`                | `string`         | Unique deposit identifier                                                                                                       |
| `chain`             | `string`         | Source chain (CAIP-2)                                                                                                           |
| `txHash`            | `string`         | Source transaction hash                                                                                                         |
| `token`             | `string`         | Deposit token address                                                                                                           |
| `amount`            | `string`         | Deposit amount (raw token units)                                                                                                |
| `sender`            | `string`         | Sender address                                                                                                                  |
| `account`           | `string`         | Registered account address                                                                                                      |
| `targetChain`       | `string`         | Destination chain (CAIP-2)                                                                                                      |
| `targetToken`       | `string`         | Destination token address                                                                                                       |
| `status`            | `string`         | In-progress: `"processing"`, `"expecting_refund"`. Terminal: `"completed"`, `"failed"`, `"rejected"`, `"refunded"`, `"ignored"` |
| `sourceTxHash`      | `string \| null` | Bridge source transaction hash                                                                                                  |
| `destinationTxHash` | `string \| null` | Bridge destination transaction hash                                                                                             |
| `sourceAmount`      | `string \| null` | Bridge source amount                                                                                                            |
| `destinationAmount` | `string \| null` | Bridge destination amount                                                                                                       |
| `errorCode`         | `string \| null` | [Error code](/deposits/api/deposit-processing#error-codes) if the deposit failed                                                |
| `createdAt`         | `string`         | ISO 8601 timestamp of when the deposit was detected                                                                             |
| `completedAt`       | `string \| null` | ISO 8601 timestamp of when the deposit completed                                                                                |
| `retryable`         | `boolean`        | Whether a failed deposit can be retried — `false` for non-retryable errors and policy rejections                                |
| `isSpam`            | `boolean`        | Flagged as spam (token has no known price); omitted from results unless you pass `includeSpam=true`                             |

### Polling loop

Poll until the deposit reaches a terminal status (`completed`, `failed`, `rejected`, `refunded`, or `ignored`):

```ts theme={null}
const TERMINAL = new Set(["completed", "failed", "rejected", "refunded", "ignored"]);

async function waitForDeposit(txHash: string): Promise<void> {
    const url = `${DEPOSIT_SERVICE_URL}/deposits?txHash=${txHash}`;
    const headers = { "x-api-key": API_KEY };

    while (true) {
        const response = await fetch(url, { headers });
        const { deposits } = await response.json();
        const deposit = deposits[0];

        if (!deposit) {
            // Deposit not yet indexed — wait and retry
            await new Promise((r) => setTimeout(r, 1_000));
            continue;
        }

        if (TERMINAL.has(deposit.status)) {
            // `rejected` carries an errorCode (whitelist/minimum); see deposit-rejected
            console.log("Deposit settled:", deposit.status, deposit.errorCode ?? deposit.destinationTxHash);
            return;
        }

        // Still in progress (`processing` / `expecting_refund`) — poll again
        await new Promise((r) => setTimeout(r, 1_000));
    }
}
```

<Tip>
  A 1-second interval works well for most use cases. Most deposits complete
  within seconds.
</Tip>

## Webhooks

The deposit service sends webhook notifications to your configured endpoint as deposits move through the processing pipeline. All webhooks are `POST` requests with `Content-Type: application/json`.

Configure your webhook URL and optional secret via the [`POST /setup`](/deposits/api/initial-setup#configure-a-webhook) endpoint.

### Payload envelope

Every webhook request body follows the same envelope structure:

```json theme={null}
{
  "version": "1.0",
  "type": "<event-type>",
  "eventId": "12345",
  "time": "2025-01-15T12:00:00.000Z",
  "data": { ... }
}
```

| Field     | Type                   | Description                                                             |
| --------- | ---------------------- | ----------------------------------------------------------------------- |
| `version` | `string`               | Protocol version. Currently `"1.0"`                                     |
| `type`    | `string`               | Event type identifier                                                   |
| `eventId` | `string`               | Stable identifier for this delivery. Use it as the canonical dedupe key |
| `time`    | `string`               | ISO 8601 timestamp of when the event was sent                           |
| `test`    | `boolean \| undefined` | Present and `true` only for events dispatched via the test endpoint     |
| `data`    | `object`               | Event-specific payload (see below)                                      |

### Event types

| Type                                    | Trigger                                                                       |
| --------------------------------------- | ----------------------------------------------------------------------------- |
| [`deposit-received`](#deposit-received) | Token transfer detected on a registered account                               |
| [`deposit-rejected`](#deposit-rejected) | Detected deposit will not be bridged — deposit whitelist or minimum violation |
| [`bridge-started`](#bridge-started)     | Bridging intent created and submitted to the Orchestrator                     |
| [`bridge-complete`](#bridge-complete)   | Tokens arrived on the target chain                                            |
| [`bridge-delayed`](#bridge-delayed)     | Bridge provider did not fill within the expected window; a refund is expected |
| [`bridge-failed`](#bridge-failed)       | Bridging failed                                                               |
| [`deposit-refunded`](#deposit-refunded) | Deposit funds returned to a recipient on the source chain                     |
| [`error`](#error)                       | Unexpected error while processing a deposit (the settlement catch-all)        |

#### `deposit-received`

Sent when an incoming token transfer is detected on a registered account.

| Field             | Type     | Description                                 |
| ----------------- | -------- | ------------------------------------------- |
| `chain`           | `string` | Source chain (CAIP-2, e.g. `"eip155:8453"`) |
| `token`           | `string` | Token address                               |
| `amount`          | `string` | Deposit amount in raw token units           |
| `account`         | `string` | Account address                             |
| `transactionHash` | `string` | Deposit transaction hash                    |
| `sender`          | `string` | Sender address                              |

```json theme={null}
{
    "version": "1.0",
    "type": "deposit-received",
    "eventId": "12345",
    "time": "2025-01-15T12:00:00.000Z",
    "data": {
        "chain": "eip155:8453",
        "token": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
        "amount": "1000000",
        "account": "0x1234567890abcdef1234567890abcdef12345678",
        "transactionHash": "0xabc123...",
        "sender": "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
    }
}
```

#### `deposit-rejected`

Sent when a detected deposit will **not** be bridged because it violates the account's [deposit whitelist](/deposits/api/initial-setup#restrict-accepted-deposits) — the token isn't allowed, or the amount is outside the configured minimum/maximum. It always follows a [`deposit-received`](#deposit-received) event for the same deposit and is terminal: no bridging is attempted and there is no retry. This is a deliberate rejection, not a processing failure — use it to record the outcome on your side. See [error codes](/deposits/api/deposit-processing#error-codes) for the full list.

| Field                     | Type                  | Description                                                                                                                                                                 |
| ------------------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `errorCode`               | `string`              | Reason — `"BALANCE-4"` (below minimum), `"BALANCE-3"` (above maximum), or `"TOKEN-3"` (token not allowed)                                                                   |
| `account`                 | `string`              | Account address                                                                                                                                                             |
| `message`                 | `string \| undefined` | Human-readable explanation                                                                                                                                                  |
| `limits.minAmount`        | `string \| undefined` | Configured minimum in raw token units — present for `BALANCE-4` when the rejection is a per-token whitelist minimum                                                         |
| `limits.minAmountUsd`     | `string \| undefined` | Configured minimum as a USD value — present for `BALANCE-4` when the rejection is your per-client USD floor (a `BALANCE-4` carries `minAmount` or `minAmountUsd`, not both) |
| `limits.maxAmount`        | `string \| undefined` | Configured maximum in raw token units — present for `BALANCE-3`                                                                                                             |
| `deposit.transactionHash` | `string`              | Deposit transaction hash                                                                                                                                                    |
| `deposit.chain`           | `string`              | Deposit chain (CAIP-2)                                                                                                                                                      |
| `deposit.token`           | `string`              | Deposit token address                                                                                                                                                       |
| `deposit.amount`          | `string`              | Actual deposited amount in raw token units                                                                                                                                  |
| `deposit.sender`          | `string`              | Sender address                                                                                                                                                              |

Compare `deposit.amount` (what was deposited) against `limits` (the configured bound) to surface the shortfall or overage to your users.

```json theme={null}
{
    "version": "1.0",
    "type": "deposit-rejected",
    "eventId": "12347",
    "time": "2025-01-15T12:00:01.000Z",
    "data": {
        "errorCode": "BALANCE-4",
        "account": "0x1234567890abcdef1234567890abcdef12345678",
        "message": "Amount 500000 is below configured minimum 1000000",
        "limits": {
            "minAmount": "1000000"
        },
        "deposit": {
            "transactionHash": "0xabc123...",
            "chain": "eip155:8453",
            "token": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
            "amount": "500000",
            "sender": "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
        }
    }
}
```

#### `bridge-started`

Sent when a bridging intent is created and submitted to the Orchestrator.

| Field                       | Type                  | Description                                                     |
| --------------------------- | --------------------- | --------------------------------------------------------------- |
| `source.chain`              | `string`              | Source chain (CAIP-2)                                           |
| `source.asset`              | `string`              | Source asset address                                            |
| `source.amount`             | `string`              | Source amount                                                   |
| `destination.chain`         | `string`              | Destination chain (CAIP-2)                                      |
| `destination.asset`         | `string`              | Destination asset address                                       |
| `destination.amount`        | `string`              | Destination amount                                              |
| `account`                   | `string`              | Account address                                                 |
| `deposit.transactionHash`   | `string`              | Original deposit transaction hash                               |
| `deposit.chain`             | `string`              | Deposit chain (CAIP-2)                                          |
| `deposit.asset`             | `string`              | Deposit asset address                                           |
| `deposit.amount`            | `string`              | Deposit amount                                                  |
| `deposit.sender`            | `string`              | Sender address                                                  |
| `settlementLayer`           | `string`              | Settlement layer used (e.g. `"layerzero"`)                      |
| `estimatedFillTime.seconds` | `number \| undefined` | Estimated time to fill, in seconds, when the route provides one |

#### `bridge-complete`

Sent when tokens have arrived on the target chain.

| Field                         | Type                  | Description                                           |
| ----------------------------- | --------------------- | ----------------------------------------------------- |
| `deposit.transactionHash`     | `string`              | Original deposit transaction hash                     |
| `deposit.chain`               | `string`              | Deposit chain (CAIP-2)                                |
| `deposit.asset`               | `string`              | Deposit asset address                                 |
| `deposit.amount`              | `string`              | Deposit amount                                        |
| `deposit.sender`              | `string`              | Sender address                                        |
| `source.transactionHash`      | `string`              | Source chain claim transaction hash                   |
| `source.chain`                | `string`              | Source chain (CAIP-2)                                 |
| `source.amount`               | `string`              | Source amount                                         |
| `source.asset`                | `string`              | Source asset address                                  |
| `destination.transactionHash` | `string`              | Destination chain transaction hash                    |
| `destination.chain`           | `string`              | Destination chain (CAIP-2)                            |
| `destination.amount`          | `string`              | Destination amount                                    |
| `destination.asset`           | `string`              | Destination asset address                             |
| `destination.logIndex`        | `number \| undefined` | Log index of the destination transfer, when available |
| `account`                     | `string`              | Account address                                       |
| `settlementLayer`             | `string \| undefined` | Settlement layer used                                 |

```json theme={null}
{
    "version": "1.0",
    "type": "bridge-complete",
    "eventId": "12346",
    "time": "2025-01-15T12:01:30.000Z",
    "data": {
        "deposit": {
            "transactionHash": "0xabc123...",
            "chain": "eip155:8453",
            "asset": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
            "amount": "1000000",
            "sender": "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
        },
        "source": {
            "transactionHash": "0xdef456...",
            "chain": "eip155:8453",
            "amount": "1000000",
            "asset": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"
        },
        "destination": {
            "transactionHash": "0x789ghi...",
            "chain": "eip155:42161",
            "amount": "990000",
            "asset": "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
            "logIndex": 5
        },
        "account": "0x1234567890abcdef1234567890abcdef12345678"
    }
}
```

#### `bridge-delayed`

Sent when the bridge provider has not filled the intent within the expected window. A refund is expected on the source chain; the subsequent [`deposit-refunded`](#deposit-refunded) event confirms the funds returned.

| Field                        | Type                  | Description                                                    |
| ---------------------------- | --------------------- | -------------------------------------------------------------- |
| `reason`                     | `string`              | Human-readable explanation of the delay                        |
| `estimatedDelayTime.seconds` | `number \| undefined` | Additional time the refund is expected to take, when available |
| `account`                    | `string`              | Account address                                                |
| `deposit.transactionHash`    | `string`              | Deposit transaction hash                                       |
| `deposit.chain`              | `string`              | Deposit chain (CAIP-2)                                         |
| `deposit.token`              | `string`              | Deposit token address                                          |
| `deposit.amount`             | `string`              | Deposit amount                                                 |
| `deposit.sender`             | `string`              | Sender address                                                 |

#### `bridge-failed`

Sent when a bridging operation fails. See [error codes](/deposits/api/deposit-processing#error-codes) for the full list and retry behavior.

| Field                     | Type                  | Description                      |
| ------------------------- | --------------------- | -------------------------------- |
| `errorCode`               | `string`              | Error code (e.g. `"BRIDGE-1"`)   |
| `account`                 | `string`              | Account address                  |
| `message`                 | `string \| undefined` | Human-readable error description |
| `deposit.transactionHash` | `string`              | Deposit transaction hash         |
| `deposit.chain`           | `string`              | Deposit chain (CAIP-2)           |
| `deposit.token`           | `string`              | Deposit token address            |
| `deposit.amount`          | `string`              | Deposit amount                   |
| `deposit.sender`          | `string`              | Sender address                   |

#### `deposit-refunded`

Sent when funds from a deposit are returned to a recipient on the source chain. Typically follows a [`bridge-delayed`](#bridge-delayed) event.

| Field                     | Type     | Description                       |
| ------------------------- | -------- | --------------------------------- |
| `account`                 | `string` | Account address                   |
| `deposit.transactionHash` | `string` | Original deposit transaction hash |
| `deposit.chain`           | `string` | Deposit chain (CAIP-2)            |
| `deposit.asset`           | `string` | Deposit asset address             |
| `deposit.amount`          | `string` | Deposit amount                    |
| `deposit.sender`          | `string` | Sender address                    |
| `refund.transactionHash`  | `string` | Refund transaction hash           |
| `refund.chain`            | `string` | Refund chain (CAIP-2)             |
| `refund.asset`            | `string` | Refund asset address              |
| `refund.amount`           | `string` | Refund amount                     |
| `refund.recipient`        | `string` | Address that received the refund  |

#### `error`

Sent when an unexpected, unhandled error occurs while processing a deposit — the catch-all in the settlement pipeline. It is not part of the normal lifecycle ordering and may arrive at any point. It carries whatever deposit / account / intent context was available at the point of failure; the optional fields are present only when that context was known.

| Field                     | Type                   | Description                                                                                |
| ------------------------- | ---------------------- | ------------------------------------------------------------------------------------------ |
| `error.message`           | `string`               | Error message                                                                              |
| `error.stack`             | `string \| undefined`  | Stack trace, when available                                                                |
| `error.cause`             | `unknown \| undefined` | Underlying cause, when available                                                           |
| `intentId`                | `string \| undefined`  | Bridging intent id, when the failure occurred after the intent was created                 |
| `account`                 | `string \| undefined`  | Account address, when known                                                                |
| `deposit.transactionHash` | `string`               | Deposit transaction hash (the `deposit` object is present only when the deposit was known) |
| `deposit.chain`           | `string`               | Deposit chain (CAIP-2)                                                                     |
| `deposit.token`           | `string`               | Deposit token address                                                                      |
| `deposit.amount`          | `string`               | Deposit amount in raw token units                                                          |
| `deposit.sender`          | `string`               | Sender address                                                                             |

Treat `error` as a signal that a deposit needs manual attention. Correlate on `deposit.transactionHash` / `intentId` and reconcile by polling `GET /deposits`.

### Signature verification

If you provided a `webhookSecret` during [setup](/deposits/api/initial-setup#configure-a-webhook), every webhook request includes an `X-Webhook-Signature` header:

```
X-Webhook-Signature: sha256=<hex-encoded-signature>
```

The signature is an HMAC-SHA256 hash computed over the raw JSON request body using your secret.

To verify:

1. Read the raw request body as a string (before JSON parsing)
2. Compute the HMAC-SHA256 of the raw body using your webhook secret
3. Compare the result with the value in the `X-Webhook-Signature` header (strip the `sha256=` prefix)
4. Use a constant-time comparison to prevent timing attacks

```ts theme={null}
import { createHmac, timingSafeEqual } from "node:crypto";

function verifyWebhookSignature(
    rawBody: string,
    signatureHeader: string,
    secret: string,
): boolean {
    const expected =
        "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
    return (
        signatureHeader.length === expected.length &&
        timingSafeEqual(Buffer.from(signatureHeader), Buffer.from(expected))
    );
}
```

<Warning>
  Always verify against the **raw request body** string, not a re-serialized
  version of the parsed JSON. Re-serialization may change key order or
  whitespace, which will produce a different signature.
</Warning>

### Delivery behavior

* **Retries** — failed deliveries are retried multiple times before the event is marked `failed`. Events that exhaust their retries can still be replayed on demand via [`POST /webhooks/events/{id}/resend`](/api-reference/deposit-service/webhooks/re-attempt-delivery-of-a-stored-webhook-event), which reuses the original `eventId`.
* **Ordering** — events for a single deposit are sent in lifecycle order: `deposit-received` → `bridge-started` → `bridge-complete` when it proceeds, or `deposit-received` → `deposit-rejected` when it won't be bridged. There is no global ordering guarantee across deposits.
* **URL validation** — the webhook URL must use HTTPS and must not target internal or private network addresses.
* **Idempotency** — use `eventId` from the envelope as the canonical dedupe key.

### Backfilling missed events

If your receiver was offline or rejected events, replay them through the events API. Every dispatched webhook is persisted regardless of delivery outcome.

List events delivered (or attempted) to your URL, newest first:

```ts theme={null}
const response = await fetch(
    `${DEPOSIT_SERVICE_URL}/webhooks/events?since=2025-01-15T00:00:00Z`,
    { headers: { "x-api-key": API_KEY } },
);
const { events, nextCursor } = await response.json();
```

Re-send a stored event to your URL — the original `eventId` is reused so dedupe still holds:

```ts theme={null}
await fetch(`${DEPOSIT_SERVICE_URL}/webhooks/events/${eventId}/resend`, {
    method: "POST",
    headers: { "x-api-key": API_KEY },
});
```

For more details, see the API reference for [`GET /webhooks/events`](/api-reference/deposit-service/webhooks/list-persisted-webhook-events-for-the-calling-client) and [`POST /webhooks/events/{id}/resend`](/api-reference/deposit-service/webhooks/re-attempt-delivery-of-a-stored-webhook-event).

## Conventions

* EVM addresses and token addresses are **lowercase**. Non-EVM addresses (Solana, Tron) preserve their original case.
* All amounts are **strings** (raw token units, not human-readable).
* Chains use [CAIP-2](https://chainagnostic.org/CAIPs/caip-2) identifiers (e.g. `"eip155:8453"` for Base).
