> ## 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 Widget installation and setup

> Install the Uphold Travel Rule Widget SDK and set it up in web and native apps with sessions tied to a quote or transaction with the travel-rule requirement.

This guide walks you through installing the Travel Rule Widget SDK and integrating it into your web or native application.

## Prerequisites

* **Access to [Widgets API](/rest-apis/widgets-api/travel-rule/create-session)** to create widget sessions.
* A quote or transaction with the **travel-rule** requirement.

## Installation

Install the SDK via npm:

```bash theme={null}
npm install @uphold/enterprise-travel-rule-widget-web-sdk
```

## Quick start

The minimal flow has three steps: create a session on your backend, mount the widget on the page, and handle the `complete` event when the user finishes the form.

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

// 1. Create a session via the Widgets API on your backend
const session = await createTravelRuleWidgetSession();

// 2. Mount the widget into a container
const widget = new TravelRuleWidget(session);
widget.mountIframe(document.getElementById('travel-rule-container'));

// 3. Handle the result
widget.on('complete', (event) => {
  console.log('Travel Rule completed:', event.detail.value);
  widget.unmount();
});
```

<Note>The container that hosts the widget must have explicit dimensions in CSS — the widget fills the container bounds. Minimum recommended size is 400px × 600px.</Note>

For a more complete example with all event handlers, see [Web app integration](#web-app-integration) below. For the complete API, see the [SDK reference](./sdk-reference).

## Web app integration

Import and initialize the SDK in your application.

Make sure the container that will host the widget has explicit dimensions in CSS. The Widget will fill the container bounds. Minimum recommended size is 400px × 600px.

<Tabs>
  <Tab title="JavaScript/TypeScript">
    ```javascript [expandable] theme={null}
    import { TravelRuleWidget } from '@uphold/enterprise-travel-rule-widget-web-sdk';

    // Create the widget instance
    const widget = new TravelRuleWidget(session, { debug: true });

    // Set up event handlers
    widget.on('complete', (event) => {
      console.log('Travel Rule form completed:', event.detail.value);

      widget.unmount();
    });

    widget.on('cancel', () => {
      console.log('Travel Rule form cancelled by user');

      widget.unmount();
    });

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

      widget.unmount();
    });

    // Mount the widget's iframe to a DOM element
    widget.mountIframe(document.getElementById('tr-container'));
    ```
  </Tab>
</Tabs>

<Note>The `session` parameter is obtained from the [Create Session](/rest-apis/widgets-api/travel-rule/create-session) endpoint.</Note>

## Native app integration

Native mobile applications integrate the Travel Rule Widget using a WebView component that loads an HTML page containing the Travel Rule Widget SDK.

To handle Travel Rule Widget [events](./sdk-reference#events), your native application needs to implement a communication bridge between the WebView and native code. This bridge enables your native app to receive and respond to events from the Travel Rule Widget.

### WebView HTML template

Create an HTML file that includes the Travel Rule Widget SDK in your JS bundle:

```html [expandable] theme={null}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Travel Rule widget</title>
    <style>
      body { margin: 0; padding: 0; }
      #travel-rule-container { width: 100%; height: 100vh; }
    </style>
  </head>
  <body>
    <div id="travel-rule-container"></div>

    <!-- Include your JS bundle which contains the SDK -->
    <script src="your-bundle-with-sdk.js"></script>
    <script>
      // Initialize widget when page loads
      window.addEventListener('load', () => {
        const session = /* Obtain session from Create Session endpoint */;

        const widget = new TravelRuleWidget(session);

        widget.on('complete', (event) => {
          sendToNativeApp('complete', event.detail);
          widget.unmount();
        });

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

        widget.on('error', (event) => {
          sendToNativeApp('error', event.detail.error);
          widget.unmount();
        });

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

      // Helper function to send events to native app
      function sendToNativeApp(type, data = null) {
        const message = { type, data };

        if (window.webkit?.messageHandlers?.travelRuleWidgetMessage) {
          // iOS - send all events through single handler
          window.webkit.messageHandlers.travelRuleWidgetMessage.postMessage(message);
        } else if (window.TravelRuleBridge) {
          // Android - send events through JavaScript interface
          window.TravelRuleBridge.onMessage(JSON.stringify(message));
        } else if (window.ReactNativeWebView) {
          // React Native - send events through postMessage
          window.ReactNativeWebView.postMessage(JSON.stringify(message));
        }
      }
    </script>
  </body>
</html>
```

<Note>The Travel Rule Widget SDK must be included in your WebView bundle (for example, via your build pipeline). Loading the SDK directly from a CDN is not supported.</Note>

### Setting up the WebView

Configure the WebView and event bridge for your platform:

<Tabs>
  <Tab title="iOS (Swift)">
    ```swift [expandable] theme={null}
    import WebKit

    class TravelRuleViewController: UIViewController, WKScriptMessageHandler {
      @IBOutlet weak var webView: WKWebView!

      override func viewDidLoad() {
        super.viewDidLoad()

        // Set up single message handler for all widget events
        let contentController = webView.configuration.userContentController
        contentController.add(self, name: "travelRuleWidgetMessage")

        // Load your HTML file with the widget
        if let url = Bundle.main.url(forResource: "travel-rule-widget", withExtension: "html") {
          webView.loadFileURL(url, allowingReadAccessTo: url.deletingLastPathComponent())
        }
      }

      // Handle messages from the WebView
      func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if message.name == "travelRuleWidgetMessage" {
          guard let messageDict = message.body as? [String: Any],
              let type = messageDict["type"] as? String else {
            return
          }

          let data = messageDict["data"]

          switch type {
            case "complete":
              handleTravelRuleComplete(data: data)
            case "cancel":
              handleTravelRuleCancel()
            case "error":
              handleTravelRuleError(error: data)
            default:
              print("Unknown Travel Rule widget message type: \(type)")
          }
        }
      }

      private func handleTravelRuleComplete(data: Any?) {
        // Handle successful Travel Rule compliance completion
        print("Travel Rule form completed: \(data ?? "no data")")
        // Proceed with the crypto transaction
      }

      private func handleTravelRuleCancel() {
        // Handle Travel Rule process cancellation
        print("Travel Rule form cancelled by user")
        // Navigate back or show cancellation message
      }

      private func handleTravelRuleError(error: Any?) {
        // Handle Travel Rule error
        print("Travel Rule form error: \(error ?? "unknown error")")
        // Show error message to user
      }
    }
    ```
  </Tab>

  <Tab title="Android (Java/Kotlin)">
    ```java [expandable] theme={null}
    import android.webkit.WebView;
    import android.webkit.WebSettings;
    import android.webkit.JavascriptInterface;
    import android.util.Log;
    import org.json.JSONObject;

    // Set up WebView
    WebView webView = findViewById(R.id.webview);
    WebSettings webSettings = webView.getSettings();
    webSettings.setJavaScriptEnabled(true);

    // Add JavaScript interface to handle events from WebView
    webView.addJavascriptInterface(new TravelRuleBridge(), "TravelRuleBridge");

    // Load your HTML file with the widget
    webView.loadUrl("file:///android_asset/travel-rule-widget.html");

    // JavaScript interface class to handle WebView events
    public class TravelRuleBridge {
      @JavascriptInterface
      public void onMessage(String messageJson) {
        try {
          JSONObject message = new JSONObject(messageJson);
          String type = message.getString("type");
          Object data = message.opt("data");

          runOnUiThread(() -> {
            switch (type) {
              case "complete":
                handleTravelRuleComplete(data != null ? data.toString() : null);
                break;
              case "cancel":
                handleTravelRuleCancel();
                break;
              case "error":
                handleTravelRuleError(data != null ? data.toString() : null);
                break;
              default:
                Log.w("TravelRule", "Unknown Travel Rule widget message type: " + type);
            }
          });
        } catch (Exception e) {
          Log.e("TravelRule", "Error parsing Travel Rule widget message", e);
        }
      }

      private void handleTravelRuleComplete(String data) {
        // Handle successful Travel Rule compliance completion
        Log.d("TravelRule", "Travel Rule form completed: " + data);
        // Proceed with the crypto transaction
      }

      private void handleTravelRuleCancel() {
        // Handle Travel Rule process cancellation
        Log.d("TravelRule", "Travel Rule form cancelled by user");
        // Navigate back or show cancellation message
      }

      private void handleTravelRuleError(String error) {
        // Handle Travel Rule error
        Log.e("TravelRule", "Travel Rule form error: " + error);
        // Show error message to user
      }
    }
    ```

    <Note>If you load local assets, ensure the WebView allows file access according to your security requirements (e.g., `setAllowFileAccess(true)` when needed).</Note>
  </Tab>

  <Tab title="React Native">
    ```javascript [expandable] theme={null}
    import { WebView } from 'react-native-webview';
    import { Alert } from 'react-native';

    const TravelRuleScreen = () => {
      const handleMessage = (event) => {
        try {
          const message = JSON.parse(event.nativeEvent.data);

          switch (message.type) {
            case 'complete':
              handleTravelRuleComplete(message.data);
              break;
            case 'cancel':
              handleTravelRuleCancel();
              break;
            case 'error':
              handleTravelRuleError(message.data);
              break;
            default:
              console.log('Unknown message type:', message.type);
          }
        } catch (error) {
          console.error('Error parsing WebView message:', error);
        }
      };

      const handleTravelRuleComplete = (data) => {
        // Handle successful Travel Rule compliance completion
        console.log('Travel Rule form completed:', data);
        Alert.alert('Success', 'Travel Rule compliance completed!');
        // Proceed with the crypto transaction
      };

      const handleTravelRuleCancel = () => {
        // Handle Travel Rule process cancellation
        console.log('Travel Rule form cancelled by user');
        Alert.alert('Cancelled', 'Travel Rule process was cancelled');
        // Navigate back
      };

      const handleTravelRuleError = (error) => {
        // Handle Travel Rule error
        console.error('Travel Rule form error:', error);
        Alert.alert('Error', 'An error occurred during Travel Rule compliance');
        // Show error message or retry option
      };

      return (
        <WebView
          source={{ uri: 'file:///path/to/travel-rule-widget.html' }}
          javaScriptEnabled={true}
          domStorageEnabled={true}
          onMessage={handleMessage}
        />
      );
    };
    ```

    <Note>For iOS, `file://` URLs may be restricted. Consider using `source={{ html: '<html>...</html>' }}` or loading a bundled asset and adjusting the URI per platform.</Note>
  </Tab>
</Tabs>

## Configuration reference

The most common SDK options. See the [SDK reference](./sdk-reference#options) for the full schema and all event types.

| Option  | Purpose                                                                                                                                                                                                                       |
| ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `theme` | Customize the Widget's appearance: force `light` or `dark` mode, set brand colors, typography, and per-component border radii. See [WidgetThemeOption](/widgets/travel-rule/sdk-reference#widgetthemeoption) for all options. |
| `debug` | Verbose console logging. Use during development.                                                                                                                                                                              |

## Troubleshooting

**Widget not displaying**

The widget loads in an iframe, which requires proper Content Security Policy (CSP) configuration. Add the following domains to your CSP directives:

* **Sandbox**: `https://travel-rule-widget.enterprise.sandbox.uphold.com`
* **Production**: `https://travel-rule-widget.enterprise.uphold.com`

Example CSP configuration:

```html theme={null}
<meta 
  http-equiv="Content-Security-Policy" 
  content="frame-src 'self' https://travel-rule-widget.enterprise.sandbox.uphold.com https://travel-rule-widget.enterprise.uphold.com;"
>
```

**Events not firing in native apps**

Verify that:

* JavaScript is enabled in the WebView.
* The message bridge is properly registered before the HTML page loads.
* Event handlers match the platform-specific bridge implementation.

## Next steps

* Review the complete [SDK Reference](./sdk-reference) for all available methods and events.
* See [Handle quote requirements](/developer-guides/crypto-transfers/withdrawal/via-rest-api#handle-quote-requirements) and [Handle on-hold transactions](/developer-guides/crypto-transfers/deposit/via-rest-api#handle-on-hold-transactions) for practical examples of using the Travel Rule Widget in transactions.
