> ## 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.

# Crypto withdrawal via the Payment Widget

> Send crypto withdrawals with the Uphold Payment Widget for asset, network and address collection, then create the quote and transaction via the REST API.

The Payment Widget handles crypto asset, network and address collection for withdrawals. Your backend creates the session, then continues with the REST API to create a quote and transaction once the user has provided the destination details.

<Warning>
  The Payment Widget does not create any transaction. Transaction creation must be handled by your backend via the REST API.
</Warning>

## Prerequisites

* The user has [completed onboarding](/developer-guides/user-onboarding/overview) and has the required capabilities enabled.
* The Payment Widget SDK is installed in your frontend. See [Installation and setup](/widgets/payment/installation-and-setup).

## Walkthrough

```mermaid theme={null}
sequenceDiagram
  autonumber
  participant Usr as User
  participant U as Your App
  participant B as Your Backend
  participant P as Payment Widget
  participant A as Uphold
  participant N as Blockchain Network

  Usr->>U: Start crypto withdrawal
  U->>B: List accounts
  B->>A: GET /core/accounts
  A-->>B: { accounts }
  B-->>U: { accounts }
  Usr->>U: Choose source account
  U->>B: Create widget session
  B->>A: Create widget session (select-for-withdrawal)
  A-->>B: { session }
  B-->>U: { session }
  U->>P: Initialize widget
  Usr->>P: Select asset, network and enter address
  P-->>U: complete { via: "crypto-network", selection }
  U->>B: Request quote
  B->>A: Create quote
  A-->>B: { quote }
  B-->>U: { quote }
  Usr->>U: Confirm quote
  U->>B: Create transaction
  B->>A: Create transaction
  A-->>B: { transaction }
  A-->>B: webhook: transaction.created (processing)
  A->>N: Broadcast withdrawal
  N-->>A: Confirmations reached
  A-->>B: webhook: transaction.status-changed (completed/failed)
  B-->>Usr: Notify the user
```

***

## Select source account

Crypto withdrawals can be sourced from any account. If the selected account is not in the withdrawal asset, the balance will be converted at the time of the transaction using Uphold's prevailing rate. Make sure the origin asset has the necessary [features enabled](/rest-apis/core-api/assets/introduction#features-and-deposits-/-withdrawals).

Call [List accounts](/rest-apis/core-api/accounts/list-accounts) to retrieve the user's accounts and let them choose one with sufficient balance for the withdrawal.

```http theme={null}
GET /core/accounts?currency=BTC
```

```json theme={null}
{
  "accounts": [
    {
      "id": "a00507fe-628c-4f27-ae81-e1c40b2a8fb8",
      "ownerId": "e4ce04dc-67b7-4e9f-af91-482cb6f9fc4a",
      "label": "My BTC account",
      "asset": "BTC",
      "balance": {
        "total": "0.05",
        "available": "0.05"
      }
    }
  ]
}
```

***

## Set a crypto destination

The widget lets the user select a crypto asset, choose a network and enter the destination address.

### Create a widget session

Call the [Create widget session](/rest-apis/widgets-api/payment/create-session) endpoint to create a session for the `select-for-withdrawal` flow.

```http theme={null}
POST /widgets/payment/sessions
{
  "flow": "select-for-withdrawal"
}
```

A successful response contains a `session` object. Pass `response.session` to your frontend to initialize the widget.

```json theme={null}
{
  "session": {
    "flow": "select-for-withdrawal",
    "url": "https://payment.enterprise.uphold.com/",
    "token": "GEbRxBN...edjnXbL"
  }
}
```

### Set up the widget

Initialize the widget for the `select-for-withdrawal` flow using the session data returned from the API.

```javascript theme={null}
import { PaymentWidget } from '@uphold/enterprise-payment-widget-web-sdk';

const initializeWithdrawalWidget = async (session) => {
  const widget = new PaymentWidget<'select-for-withdrawal'>(session, { debug: true });

  widget.on('ready', () => {
    console.log('Ready');
  });

  widget.on('complete', (event) => {
    console.log('Complete', JSON.stringify(event.detail.value));
  });

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

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

  widget.mountIframe(document.getElementById('payment-container'));
};
```

<Info>
  The example above is for web applications. For native apps using a WebView, you'll need a bridge for events as outlined in [Installation & setup](/widgets/payment/installation-and-setup#native-application-integration).
</Info>

### Handle the complete event

The widget lets the user select a crypto asset, choose a network and enter a destination address. If the network requires a destination tag or memo, the widget prompts for it and warns the user if it's missing. When the user completes the selection, the `complete` event fires with `via: "crypto-network"`.

```javascript theme={null}
widget.on('complete', (event) => {
  const { via, selection } = event.detail.value;

  if (via === 'crypto-network') {
    handleCryptoWithdrawal(selection);
  }

  widget.unmount();
});
```

The event payload:

* `via` — set to `crypto-network` when the user provides a crypto withdrawal address.
* `selection.asset` — the selected crypto asset code (e.g. `BTC`, `XRP`).
* `selection.network` — the selected blockchain network (e.g. `bitcoin`, `xrp-ledger`).
* `selection.address` — the destination wallet address.
* `selection.reference` — the destination tag or memo, if required by the network.

```json theme={null}
{
  "via": "crypto-network",
  "selection": {
    "asset": "XRP",
    "network": "xrp-ledger",
    "address": "rPjTZfLP3Qxwwd2xvXSALJzEFmmf7bEYgh",
    "reference": "12345678"
  }
}
```

### Handle cancellations

The `cancel` event fires when the user closes the widget without completing the selection.

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

### Handle errors

The `error` event fires when an error occurs during the flow.

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

## Create a quote

Use the `selection` data from the widget to create a quote via the [Create quote](/rest-apis/core-api/transactions/create-quote) endpoint.

```http theme={null}
POST /core/transactions/quote
{
  "origin": {
    "type": "account",
    "id": "a00507fe-628c-4f27-ae81-e1c40b2a8fb8"
  },
  "destination": {
    "type": "crypto-address",
    "asset": "XRP",
    "network": "xrp-ledger",
    "address": "rPjTZfLP3Qxwwd2xvXSALJzEFmmf7bEYgh",
    "reference": "12345678"
  },
  "denomination": {
    "asset": "XRP",
    "amount": "10.00",
    "target": "origin"
  }
}
```

<Info>Quotes typically **expire** quickly. Prompt for user confirmation within the expiry window and requote if needed.</Info>

## Handle quote requirements

When the quote is returned, check the `requirements` array. If non-empty, resolve each requirement before creating the transaction.

```json theme={null}
{
  "quote": {
    "id": "623000c8-9bdf-4a2b-aa3d-6a6b44a7f6a0",
    "requirements": [
      "travel-rule"
    ],
    "expiresAt": "2024-07-24T15:22:39Z"
  }
}
```

### Travel Rule

If the `requirements` array contains `travel-rule`, you must collect the required originator and beneficiary information before creating the transaction. For the full step-by-step implementation, see the [Travel Rule withdrawal](/developer-guides/travel-rule/withdrawal) guide.

## Create a transaction

Once the user confirms the quote, create the transaction using the [Create transaction](/rest-apis/core-api/transactions/create-transaction) endpoint. If the Travel Rule widget emitted a `complete` event, include the `travelRule` data in `params`.

<Warning>
  If Travel Rule was required, the original quote may have expired while the user was completing the form. If so, create a new quote before proceeding — the Travel Rule data remains valid and will automatically apply to the new quote.
</Warning>

```http theme={null}
POST /core/transactions
{
  "quoteId": "623000c8-9bdf-4a2b-aa3d-6a6b44a7f6a0",
  "params": {
    "travelRule": {
      // Travel Rule data from widget complete event — omit if not required
    }
  }
}
```

In a successful crypto withdrawal, the origin is the source account and the destination is a `crypto-address` node reflecting the recipient's on-chain address.

```json [expandable] theme={null}
{
  "transaction": {
    "id": "223c24c5-76c6-4553-91bc-5af519441f03",
    "origin": {
      "amount": "0.00121023",
      "asset": "BTC",
      "rate": "0.00002629253259492961",
      "node": {
        "type": "account",
        "id": "a00507fe-628c-4f27-ae81-e1c40b2a8fb8",
        "ownerId": "e4ce04dc-67b7-4e9f-af91-482cb6f9fc4a"
      }
    },
    "destination": {
      "amount": "0.00121023",
      "asset": "BTC",
      "rate": "1",
      "node": {
        "type": "crypto-address",
        "address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
        "network": "bitcoin",
        "execution": {
          "mode": "onchain"
        }
      }
    },
    "fees": [],
    "status": "processing",
    "quotedAt": "2024-07-24T15:02:39Z",
    "createdAt": "2024-07-24T15:22:39Z",
    "updatedAt": "2024-07-24T15:22:39Z",
    "denomination": {
      "amount": "100.00",
      "asset": "GBP",
      "target": "origin",
      "rate": "0.00001210225938333485"
    }
  }
}
```

### Execution modes

Crypto withdrawals support different execution modes depending on the destination and environment:

* **On-chain execution**: The transaction is processed directly on the blockchain network. The `destination.node.execution.mode` will be `onchain` and include blockchain-specific details such as transaction hashes upon completion.
* **Off-chain execution**: When both the sender and recipient are Uphold users, the transaction is processed internally within Uphold's infrastructure. This eliminates network fees and is faster than onchain processing. The `destination.node.execution.mode` will be `offchain`. For offchain transactions, the `execution` object includes additional properties: `accountOwnerId` (the recipient user ID) and `accountId` (the recipient account ID).
* **Simulated execution**: Used in development environments for testing purposes. The transaction appears processed but does not affect actual blockchain state (user balances will be affected though). The `destination.node.execution.mode` will be `simulated`.

## Monitor for settlement

Prefer **webhooks** for real-time updates, or fall back to **polling** if webhooks are not feasible.

* Webhook events (recommended):
  * [core.transaction.created](/rest-apis/core-api/transactions/webhooks/transaction-created)
    * `status: processing` → initiated but not yet broadcast
  * [core.transaction.status-changed](/rest-apis/core-api/transactions/webhooks/transaction-status-changed)
    * `status: completed` → broadcast and confirmed
    * `status: on-hold` → transaction checks paused (e.g., pending RFIs)
    * `status: failed` → transaction failed, check `statusDetails` for more info
* Polling (fallback): [Get transaction](/rest-apis/core-api/transactions/get-transaction)

## Notify the user

Display an in-app confirmation when the transaction is `completed`, and send an email if applicable.

<Check>You now support crypto withdrawals via the Payment Widget.</Check>
