Skip to main content
By the end of this guide the Travel Rule Widget will be running in your app, mounted to a container, and emitting events you can react to.

Before you start

You’ll need:
  • Access to Widgets API to create widget sessions. Manage your access in Enterprise Portal.
  • A backend that can call the Widgets API to create sessions on behalf of your users.
  • A frontend — a web app, or a native app with a WebView — to embed the Widget.
  • A quote or transaction with the travel-rule requirement — the Widget resolves either a pending request for information on an on-hold deposit (deposit-form flow) or a travel-rule requirement surfaced on a withdrawal quote (withdrawal-form flow). See Travel Rule deposit via Widget and Travel Rule withdrawal via Widget for how each requirement arises.
The Widget runs from one of two hosts depending on environment:

Choose an integration approach

We recommend integrating without the SDK for native apps — it’s simpler to set up. For web-only integrations, the Web SDK is a solid default. There are two ways to embed the Widget:
  • Web SDK — Install @uphold/enterprise-travel-rule-widget-web-sdk for a better developer experience — typed events and a more streamlined integration on web. For native apps, the SDK must be bundled into the WebView’s HTML page.
  • Without the SDK — Listen for the Widget’s messages over the iframe or WebView and respond to them directly. This is also the simpler option for native apps: there’s no SDK to bundle into the WebView’s HTML page.
Both approaches use the same backend step — creating a session via the Widgets API — and emit the same four lifecycle outcomes (ready, complete, cancel, error).

Shared setup

These two steps are identical whichever approach you choose above — do them once, then jump to the matching section below.

1. Create a session on your backend

The Travel Rule Widget runs against a session — a short-lived, server-side authorization scoped to one flow and one user. Create it server-side using your OAuth credentials.
To create a session, you must have the Travel Rule Widget scope.
Never create session directly from the client. Your client secret must not leave your backend.
Call Create session with the flow that matches what you’re resolving and, optionally, the Widget options:
  • deposit-form — resolves a pending request for information on an on-hold deposit. Pass data.requestForInformationId.
  • withdrawal-form — resolves a travel-rule requirement on a withdrawal quote. Pass data.quoteId.
The example below creates a deposit-form session:
The options object accepts the same options as the SDK, except debug — see Configuration reference.
If you set theme or layout, also see Theme and layout setup.
The response wraps the session object:
Send response.session to your frontend, for example in your page response or through your own API endpoint. Keep session.url unmodified, including its query string. The only change allowed is adding theme_appearance when integrating without the SDK (see Theme and layout setup). Then:
  • With the Web SDK, pass response.session as the session argument to the TravelRuleWidget constructor — see Setup with Web SDK.
  • Without the SDK, load session.url directly into your iframe or WebView — see Setup without the SDK.

2. Allow the Widget domain in your CSP

If your web app embeds the Widget in an iframe and enforces a Content Security Policy, allow the Widget host for your environment(s) under frame-src.
If your app does not use CSP, skip this step.

Setup with Web SDK

Install the SDK

Install the SDK in the frontend that will host the Widget — your web app, or the JS bundle loaded by your native WebView.

Initialize and mount the Widget

On the frontend, instantiate TravelRuleWidget with the session from Create a session on your backend, then mount it into a container element. If you set theme or layout in Create session, also pass the same values to the constructor, to avoid a brief change in appearance (see Theme and layout setup).
The container must have explicit CSS width and height — the iframe fills its bounds. Minimum recommended size is 400px × 600px.
For type inference on the complete event, pass the flow as a generic: new TravelRuleWidget<'deposit-form'>(session) (or 'withdrawal-form'). See the SDK reference for full constructor details.

Handle Widget events

The Widget emits four events during its lifecycle. Wire up handlers before calling mountIframe.
The Widget does not unmount itself. You must call widget.unmount() from complete, cancel, and error handlers.
The Widget resolves the underlying request for information itself by calling Update request for information internally — you don’t need to forward event.detail.value anywhere. For withdrawal-form sessions, once complete fires, call Create transaction with the quote id. For deposit-form sessions, the transaction leaves on-hold on its own once the RFI clears. See Travel Rule deposit via Widget and Travel Rule withdrawal via Widget for full examples. The error.code property (e.g. entity_not_found, validation_failed) lets you distinguish an expired session from a form validation issue — see Events in the SDK reference for the full error shape.

Native apps with the Web SDK

Setup without the SDK

Instead of installing the SDK, you can load the Widget’s session url directly — as an iframe you create yourself on web, or as your WebView’s top-level page on native — and speak its underlying message protocol directly. This is useful for hosts that can’t ship a JS bundle to their WebView, or want a fully native shell around the Widget.
The Shared setup steps still apply here — you just don’t install anything. Only how you load the Widget and exchange messages changes.

The message protocol

These are the message types the Widget speaks. The transport carrying them differs per platform — see the tabs below. From the Widget to your host: From your host to the Widget: The Widget loads its session on its own, using the options set when creating the session, so the init reply is optional. The init payload carries an options object with the same shape as the SDK’s TravelRuleWidgetOptions, except debug — omit it or pass {} to use defaults. For now, include the same theme and layout you set in Create session, to avoid a brief change in appearance (see Theme and layout setup). If you send init, its options are merged with the ones set when creating the session, and Create session values take precedence for any value set in both.
Sending the full session object in init ({ ...session, options }) still works but is deprecated. The Widget ignores it if it has already loaded its session, and logs a console warning when it’s used. Send { options } only.

Platform implementation

Mount the session url in an iframe you create yourself, and exchange messages over the standard window.postMessage API. Make sure the iframe’s container has explicit CSS width and height (minimum recommended size is 400px × 600px).

Theme and layout setup

Set theme and layout in the Create session options to customize the Widget’s appearance. See WidgetThemeOption and WidgetLayout for all options. The Widget applies the options set in Create session once it has loaded the session. Until then, it renders its defaults: the browser or OS color scheme and the boxed layout. If you set theme or layout, the Widget may briefly show those defaults before switching. Until a future release removes the need for it, pass the same theme and layout from your frontend as well. Use the same values in both places, since Create session values replace the frontend ones once the session loads.

With the Web SDK

Pass the same theme and layout in the constructor options:

Without the SDK

  1. Before loading session.url, append a theme_appearance query parameter with the value you want (dark or light). The Widget renders before it receives any options, so this makes its first render use the right light or dark mode instead of the browser or OS color scheme:
    This works the same whether you load the result into a web iframe or as your native WebView’s top-level page.
    Add theme_appearance through URL/URLSearchParams, as above, to preserve the sessionToken query parameter the Widget needs to load its session. Don’t rebuild the query string by hand.
  2. When the Widget sends its load message, reply with an init message carrying the same theme and layout:

Test in Sandbox

With your Sandbox credentials and the Sandbox Widget host configured, run through this checklist — it applies whichever approach you integrated with:
  • The Widget mounts and ready fires.
  • Completing the compliance form fires complete with the expected value for your flow — event.detail.value with the Web SDK, or the value payload of the complete message without it.
  • Closing or dismissing the Widget fires cancel.
  • If your app uses a CSP (see Shared setup), the browser console shows no violations (look for “Refused to frame”).
Once Sandbox is green, swap your OAuth credentials to Production. The Widget host is selected automatically by the session url returned from your backend — no client-side environment switching is needed.

Configuration reference

The most common Widget options. See the SDK reference for the full schema and all event types. If you’re integrating without the SDK, these map directly to the options object you send in the Create session request, or in your init reply, except debug, which only applies to the SDK.

Troubleshooting

Widget not displaying Confirm the Widget host for your environment is in the frame-src directive of your CSP (see Shared setup). Open DevTools → Console and look for Refused to frame violations. Container is empty after mount The iframe fills its container — the container must have explicit CSS width and height. Minimum recommended size is 400px × 600px. Widget briefly shows the wrong theme or layout The Widget renders its defaults (the browser or OS color scheme and the boxed layout) until it loads the session, then switches to the theme and layout set in Create session. Verify that:
  • You pass the same theme and layout from your frontend, with the same values as in Create session.
  • Without the SDK, you append theme_appearance to session.url before loading it, and reply to load with init as soon as you receive it.
See Theme and layout setup. Events not firing in native apps (with the Web SDK) Verify that:
  • JavaScript is enabled in the WebView.
  • The message bridge is registered before the HTML page loads.
  • Event handler names match the platform-specific bridge contract used in sendToNativeApp.
Events not firing (without the SDK) Verify that:
  • Your message-handler / listener name is exactly uphdTravelRuleWidget — the name the Widget checks for on both iOS and Android — and is registered before the WebView loads the session url.
  • On web, you’re filtering incoming message events by origin (event.origin === sessionOrigin) — messages from other origins should be ignored, not treated as Widget events.
Widget stays in a loading state and sends error The Widget couldn’t load its session. Usually, the sessionToken in session.url is expired, invalid, or already used, or session.url was modified or rebuilt without its query string. Create a new session and load its session.url unmodified. Widget never unmounts. The Web SDK does not auto-unmount, and neither does the Widget itself when integrating without it. Call widget.unmount() (Web SDK) or tear down your iframe/WebView (without the SDK) from each terminal message (complete, cancel, error).

Next steps