Skip to main content

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.

This guide walks you through supporting crypto withdrawals using the REST API.

Prerequisites

If the destination network requires an additional reference (e.g., destination tag, memo), strongly encourage your users to provide it. Missing references often lead to loss of funds or lengthy recovery.

Walkthrough

Check available rails

Call List rails to confirm the asset and network the user wants to withdraw to, and verify the withdraw feature is enabled.
GET /core/rails?type=crypto&asset=BTC
A successful response lists all rails for the asset. Each rail includes a features array — only proceed with networks that include "withdraw". For assets supported by multiple networks, require an explicit network selection from the user. Use the network’s reference field to determine whether to show a destination tag or memo input in your UI.
{
  "rails": [
    {
      "type": "crypto",
      "network": "bitcoin",
      "method": "crypto-transaction",
      "asset": "BTC",
      "decimals": 8,
      "features": [
        "deposit",
        "withdraw"
      ]
    },
    {
      "type": "crypto",
      "network": "lightning",
      "method": "crypto-transaction",
      "asset": "BTC",
      "decimals": 8,
      "features": []
    }
  ]
}

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. Call List accounts to retrieve the user’s accounts and let them choose one with sufficient balance for the withdrawal.
GET /core/accounts?currency=BTC
{
  "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"
      }
    }
  ]
}

Create a quote

Create a quote for the withdrawal using the Create quote endpoint.
POST /core/transactions/quote
{
  "origin": {
    "type": "account",
    "id": "a00507fe-628c-4f27-ae81-e1c40b2a8fb8"
  },
  "destination": {
    "type": "crypto-address",
    "asset": "BTC",
    "network": "bitcoin",
    "address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
  },
  "denomination": {
    "asset": "GBP",
    "amount": "100.00",
    "target": "origin"
  }
}
A successful response returns a quote object with details about the withdrawal, including fees and expiration.
{
  "quote": {
    "id": "623000c8-9bdf-4a2b-aa3d-6a6b44a7f6a0",
    "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"
        }
      }
    },
    "denomination": {
      "amount": "100.00",
      "asset": "GBP",
      "target": "origin",
      "rate": "0.00001210225938333485"
    },
    "fees": [],
    "expiresAt": "2024-07-24T15:22:39Z"
  }
}
Quotes typically expire quickly. Prompt for user confirmation within the expiry window and regenerate if needed.

Handle quote requirements

When the quote is returned, check the requirements array. If non-empty, resolve each requirement before creating the transaction.
{
  "quote": {
    "id": "623000c8-9bdf-4a2b-aa3d-6a6b44a7f6a0",
    "requirements": [
      "travel-rule"
    ],
    "expiresAt": "2024-07-24T15:22:39Z"
  }
}

Travel Rule

Travel Rule is a regulatory requirement that mandates the collection and transmission of information about the originator and beneficiary of certain crypto transactions. Use the Travel Rule widget to collect the required information.

Create a travel rule session

Call Create session with "flow": "withdrawal-form" and the quote id.
POST /widgets/travel-rule/sessions
{
  "flow": "withdrawal-form",
  "data": {
    "quoteId": "623000c8-9bdf-4a2b-aa3d-6a6b44a7f6a0"
  }
}
A successful response returns a session object with a token and url to initialize the widget.
{
  "session": {
    "flow": "withdrawal-form",
    "url": "https://widgets.uphold.com/travel-rule/sessions/abc123",
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "data": {
      "provider": "notabene",
      "parameters": {}
    }
  }
}

Set up the widget

Initialize the widget for the withdrawal-form flow using the session data returned from the API.
import { TravelRuleWidget } from '@uphold/enterprise-travel-rule-widget-web-sdk';

const initializeTravelRuleWidget = async (session) => {
  const widget = new TravelRuleWidget<'withdrawal-form'>(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('travel-rule-container'));
};
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.

Handle the complete event

The complete event fires when the user successfully submits the Travel Rule form. Send the travelRule data to your backend to include when creating the transaction.
widget.on('complete', (event) => {
  const { value: travelRule } = event.detail;

  sendToBackend({ travelRule });
  widget.unmount();
});

Handle cancellations

The cancel event fires when the user closes the widget without completing the form.
widget.on('cancel', () => {
  widget.unmount();
  // Redirect back or show a cancellation message
});

Handle errors

The error event fires when an unrecoverable error occurs.
widget.on('error', (event) => {
  console.error('Widget error:', event.detail.error);
  widget.unmount();
  // Show a user-friendly error message
});

Create a transaction

Once the user confirms the quote, create the transaction using the Create transaction endpoint. If the Travel Rule widget emitted a complete event, include the travelRule data in params.
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.
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.
{
  "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
      • status: processing → initiated but not yet broadcast
    • core.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

Notify the user

Display an in-app confirmation when the transaction is completed, and send an email if applicable.
You now support crypto withdrawals with the Enterprise API Suite.