Skip to main content
This guide walks you through handling a Travel Rule requirement when creating a crypto withdrawal using the Travel Rule Widget — from detecting the requirement on a quote to letting the widget collect and submit proof.
Want to collect and submit proof yourself instead of using a managed UI? See Travel Rule withdrawal via the REST API — it also documents the RFI’s context and proofOptions fields in detail.

Prerequisites

Walkthrough

Detect the requirement

When a quote is returned, check the requirements array. If it contains travel-rule, the requirement must be resolved before the transaction can be created. If requirements is empty, proceed directly to creating the transaction.

Resolve the Travel Rule RFI

The Travel Rule Widget allows the user to resolve a Travel Rule RFI for a specific quote.

Create a widget session

Create a session tied to the quote by calling Create session with flow: withdrawal-form and the data property containing the referenceId — the quote’s id. Each session is single-use and bound to a specific quote.
A successful response returns the session data needed to initialize the widget.
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. If the quote has also expired, create a new quote first before creating a new session.

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.
Both examples above are for web applications — either creating the iframe yourself or letting the SDK do it. For native apps using a WebView, see Native apps with the SDK for the SDK’s native-bundling pattern, or Setup with JavaScript for the no-SDK approach that loads the session url directly as the WebView’s top-level page.

Handle complete event

Once the user finishes the form, the widget automatically resolves the RFI by calling Update request for information internally with the collected proof — you don’t need to forward any data yourself.
Calling Update request for information yourself is only needed if you’re not using the widget and are resolving the Travel Rule requirement directly — not part of this widget-based flow.
When complete fires, unmount the widget and confirm the RFI is resolved before proceeding — whether it is immediately ok or still pending depends on the proof type used:
  • Synchronous proofs (self-declaration, message signing) — the RFI is fully resolved as soon as complete fires.
  • Asynchronous proofs (Satoshi test, a small on-chain test transaction) — complete firing does not mean the RFI is resolved yet; resolution depends on the microtransfer settling on-chain and confirmed by Uphold.
Use Get request for information to check which proof type was used and confirm the RFI’s status. Once the RFI is resolved (status: "ok"), create a new quote if the original one has expired, and then proceed to create the transaction. If the RFI resolution fails (status: "failed") or expires, retry the process with a new quote and resolve the Travel Rule requirement again.

Handle cancellations

The cancel event fires when the user closes the widget without completing the form. The quote is not affected — it remains valid until it expires, so a new widget session can be created for the same quote to let the user retry. See cancel event for the event reference.

Handle errors

The error event fires when an unrecoverable error occurs. See error event for the full error shape and available properties.

Create the transaction

Once the RFI is resolved (status: "ok"), create the transaction using the quote ID. If the original quote expired during the RFI process, create a new quote and proceed — no additional Travel Rule data needs to be passed, as the resolved RFI is tied to the destination address rather than a specific quote.
Unlike a deposit hold, a withdrawal transaction is created right away, so failures surface after creation — either when the Travel Rule payload is rejected at creation time, or when the counterparty VASP later rejects the data. After the transaction is created, the counterparty VASP has a window to review the Travel Rule data. If the data is rejected, Uphold emits a core.transaction.status-changed webhook with status: failed and statusDetails.reason: travel-rule-verification-failed. Common causes are the transaction being created before the RFI was fully resolved, or the beneficiary VASP being unrecognized or invalid. Notify the user and ask them to retry with a different destination.

Testing

To trigger a Travel Rule requirement on a withdrawal, use a GB user account and create an XRP withdrawal quote to an external address for 30 XRP. Verify the following:
  1. The quote response includes "travel-rule" in the requirements array.
  2. After creating a widget session, complete the form — verify via Get request for information that the RFI status is ok.
  3. Call Create transaction with the quoteId; the transaction is created successfully.
  4. A core.transaction.status-changed webhook is received with status: completed (or failed if the counterparty rejects the data).
  5. The transaction status updates to completed within a few minutes, assuming no other blockers.
For the equivalent deposit guide, see Travel Rule deposit via the Travel Rule Widget.