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

# Travel Rule — deposit flow

> Step-by-step guide to resolving a Travel Rule request for information on an on-hold crypto deposit.

This guide walks you through resolving a Travel Rule request for information on an on-hold crypto deposit — from detecting the on-hold status to resolving the RFI and allowing the transaction to proceed.

## Prerequisites

* The user has [completed onboarding](/developer-guides/user-onboarding/overview) and has the required capabilities enabled.

## Walkthrough

```mermaid theme={null}
sequenceDiagram
  autonumber
  participant Usr as User
  participant U as Your App
  participant B as Your Backend
  participant W as Travel Rule Widget
  participant A as Uphold

  A-->>B: webhook: transaction.status-changed (on-hold)
  B-->>Usr: Notify the user (deposit on hold)
  B->>A: GET /core/transactions/{id}/requests-for-information
  A-->>B: { requestsForInformation }
  B->>A: POST /widgets/travel-rule/sessions
  A-->>B: { session }
  B-->>U: { session }
  U->>W: Initialize widget
  W-->>U: ready
  Usr->>W: Complete form
  W-->>U: complete { travelRule }
  U->>B: { travelRule }
  B->>A: PUT /core/transactions/{id}/requests-for-information/{rfiId}
  A-->>B: RFI resolved
  A-->>B: webhook: transaction.status-changed (completed/failed)
  B-->>Usr: Notify the user (outcome)
```

## Detect the on-hold transaction

When a crypto deposit is placed on hold due to a Travel Rule requirement, Uphold sends a `core.transaction.status-changed` webhook with `status: on-hold` and `statusDetails.reason: pending-requests-for-information`.

```json theme={null}
{
  "id": "<eventId>",
  "type": "core.transaction.status-changed",
  "createdAt": "2024-07-24T15:22:39Z",
  "data": {
    "transaction": {
      "id": "b97c7f64-1c34-4b6e-a8f2-3d5c4e9a1b72",
      "status": "on-hold",
      "statusDetails": {
        "reason": "pending-requests-for-information"
      }
    }
  }
}
```

<Note>Abbreviated — the transaction object includes additional fields.</Note>

<Info>If you are using polling instead of webhooks, check for `status: on-hold` and `statusDetails.reason: pending-requests-for-information` on the transaction object.</Info>

## Notify the user

Surface the on-hold status to the user out-of-band (email or push notification) — deposits can sit on-hold indefinitely until resolved.

## List transaction RFIs

Call [List requests for information](/rest-apis/core-api/transactions/rfis/list-requests-for-information) endpoint to retrieve all RFIs associated with the transaction, then filter the results to keep only entries where type is "travel-rule". From that filtered set, check whether any RFI has a status of "pending" — if so, the transaction is still awaiting resolution.

```http theme={null}
GET /core/transactions/{transactionId}/requests-for-information
```

```json theme={null}
{
  "requestsForInformation": [
    {
      "id": "3f6d0c1e-a1bf-4b25-9802-2a3ee492d3c8",
      "type": "travel-rule",
      "status": "pending",
      "data": {},
      "createdAt": "2024-07-24T15:22:39Z",
      "updatedAt": "2024-07-24T15:22:39Z"
    }
  ]
}
```

<Info> If all travel-rule RFIs have a status of "ok", the transaction may have already been moved out of on-hold status automatically. Make sure to re-fetch the transaction and check its current status before taking further action. </Info>

## Resolve the RFI

The Travel Rule Widget allows the user to resolve a travel rule RFI for a specific transaction.

### Create a widget session

Create a session tied to the RFI by calling [Create session](/rest-apis/widgets-api/travel-rule/create-session) with `flow: deposit-form` and the `data: requestForInformationId`. Each session is single-use and bound to a specific RFI.

```http theme={null}
POST /widgets/travel-rule/sessions
{
  "flow": "deposit-form",
  "data": {
    "requestForInformationId": "3f6d0c1e-a1bf-4b25-9802-2a3ee492d3c8"
  }
}
```

A successful response returns the session data needed to initialize the widget.

```json [expandable] theme={null}
{
  "session": {
    "flow": "deposit-form",
    "url": "https://travel-rule-widget.enterprise.uphold.com/",
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "data": {
      "provider": "notabene",
      "parameters": {
        "init": {
          "authToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
          "nodeUrl": "https://api.notabene.id"
        },
        "options": {},
        "transaction": {
          "amountDecimal": 0.05,
          "asset": "BTC",
          "source": ["bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh"],
          "customer": {
            "name": "John Doe",
            "type": "natural"
          }
        }
      }
    }
  }
}
```

<Note>Widget sessions expire after 2 minutes. If the session expires before the user opens the widget — or while they are mid-form — the widget emits an `error` event. Create a new session and re-mount to let the user retry.</Note>

### Set up the widget

Initialize the widget using the session returned from the API and mount it into your application. The widget does not unmount itself — always call `unmount()` after handling any event.

```javascript theme={null}
import { TravelRuleWidget } from '@uphold/enterprise-travel-rule-widget-web-sdk';

const widget = new TravelRuleWidget<'deposit-form'>(session, { debug: true });

widget.on('ready', () => {
  // Widget has loaded and is ready for user interaction
});

widget.on('complete', (event) => {
  // `event.detail.value` is the entire Travel Rule payload — forward it unchanged
  const { value: travelRule } = event.detail;
  sendToBackend({ travelRule });
  widget.unmount();
});

widget.on('cancel', () => {
  widget.unmount();
});

widget.on('error', (event) => {
  console.error('Travel Rule widget error:', event.detail.error);
  widget.unmount();
});

widget.mountIframe(document.getElementById('travel-rule-container'));
```

<Info>The example above is for web applications. For native apps using a WebView, see [Native app integration](/widgets/travel-rule/installation-and-setup#native-app-integration).</Info>

### Handle the complete event

Once the widget emits `complete`, send the payload to your backend. The request body's `data` field is the full `event.detail.value` object from the complete event, passed through unchanged. See [complete event](/widgets/travel-rule/sdk-reference#complete) for the event reference.

```http theme={null}
PUT /core/transactions/{transactionId}/requests-for-information/{requestForInformationId}
```

```json [expandable] theme={null}
{
  "data": {
    "txUpdate": {
      "transactionAsset": { "caip19": "xrpl:0/slip44:144" },
      "transactionAmount": "30000000",
      "originatorEqualsBeneficiary": false,
      "beneficiaryVASPdid": "did:ethr:0x…",
      "originatorVASPdid": "did:ethr:0x…",
      "beneficiary": {},
      "originator": {
        "originatorPersons": [
          {
            "naturalPerson": {
              "name": {
                "nameIdentifier": [
                  { "primaryIdentifier": "Doe", "secondaryIdentifier": "John", "nameIdentifierType": "LEGL" }
                ]
              }
            }
          }
        ]
      }
    },
    "errors": [],
    "status": "pending",
    "valid": true,
    "value": {
      "amountDecimal": 30,
      "asset": "XRP",
      "customer": { "name": "John Doe", "type": "natural" },
      "source": ["rExampleXRPLAddress0000000000000"],
      "agent": {
        "type": "VASP",
        "did": "did:ethr:0x…",
        "name": "Example VASP",
        "verified": true
      },
      "counterparty": { "type": "natural", "name": "John Doe", "verified": false },
      "account": {
        "caip10": "xrpl:0:rExampleXRPLAddress0000000000000",
        "blockchainAddress": "rExampleXRPLAddress0000000000000",
        "chain": "xrpl:0",
        "did": "did:pkh:xrpl:0:rExampleXRPLAddress0000000000000",
        "valid": true
      }
    },
    "ivms101": {
      "originator": {
        "originatorPersons": [
          {
            "naturalPerson": {
              "name": {
                "nameIdentifier": [
                  { "primaryIdentifier": "Doe", "secondaryIdentifier": "John", "nameIdentifierType": "LEGL" }
                ]
              }
            }
          }
        ]
      }
    }
  }
}
```

A successful response returns the updated RFI with `status: ok`. The transaction resumes processing automatically.

```json [expandable] theme={null}
{
  "requestForInformation": {
    "id": "3f6d0c1e-a1bf-4b25-9802-2a3ee492d3c8",
    "type": "travel-rule",
    "status": "ok",
    "createdAt": "2024-07-24T15:22:39Z",
    "updatedAt": "2024-07-24T15:25:12Z",
    "data": {
      "originator": {
        "originatorPersons": [
          {
            "naturalPerson": {
              "name": {
                "nameIdentifier": [
                  {
                    "primaryIdentifier": "Doe",
                    "secondaryIdentifier": "John",
                    "nameIdentifierType": "LEGL"
                  }
                ]
              }
            }
          }
        ]
      },
      "beneficiary": {},
      "originatorProof": {
        "type": "self-declaration",
        "status": "verified",
        "confirmed": true
      },
      "transactionAsset": {
        "caip19": "xrpl:0/slip44:144"
      },
      "transactionAmount": "4000000",
      "originatorEqualsBeneficiary": true
    }
  }
}
```

### Handle cancellations

The `cancel` event fires when the user closes the widget without completing the form. The transaction remains `on-hold` until the RFI is resolved — create a new widget session for the same RFI to let the user retry. See [cancel event](/widgets/travel-rule/sdk-reference#cancel) for the event reference.

```javascript theme={null}
widget.on('cancel', () => {
  widget.unmount();
  // Redirect back or show a cancellation message
});
```

### Handle errors

The `error` event fires when an unrecoverable error occurs. See [error event](/widgets/travel-rule/sdk-reference#error) for the full error shape and available properties.

```javascript theme={null}
widget.on('error', (event) => {
  console.error('Widget error:', event.detail.error);
  widget.unmount();
  // Show a user-friendly error message
});
```

## Testing

To trigger a Travel Rule RFI on a deposit, use a GB user account and send 30 XRP from an unhosted (self-custodial) wallet to the user's Uphold deposit address.

Verify the following:

1. A `core.transaction.status-changed` webhook is received with `status: on-hold` and `statusDetails.reason: pending-requests-for-information`.
2. [List requests for information](/rest-apis/core-api/transactions/rfis/list-requests-for-information) returns an RFI with `type: travel-rule` and `status: pending`.
3. After completing the widget flow and calling [Update request for information](/rest-apis/core-api/transactions/rfis/update-request-for-information), the RFI status changes to `ok` and the transaction moves back to `processing`.
4. The transaction status updates from `processing` to `completed` within a few minutes, assuming no other blockers.

For the equivalent withdrawal guide, see [Travel Rule — withdrawal flow](/developer-guides/travel-rule/withdrawal).
