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

# Card withdrawal via the Payment Widget

> Send card withdrawals with the Uphold Payment Widget for card selection, then create the quote and transaction directly via the REST API to complete payout.

The Payment Widget handles card selection for withdrawals via the **Select for Withdrawal flow**. Your backend then creates the quote and the transaction directly via the REST API.

<Info>
  The Payment Widget handles card selection only. Your backend must create the transaction via the REST API.
</Info>

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

  Usr->>U: Start card 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 card account
  P-->>U: complete { via, selection }
  Usr->>U: Choose amount
  U->>B: Request quote
  B->>A: Create quote (account → card)
  A-->>B: { quote }
  B-->>U: { quote }
  Usr->>U: Confirm
  U->>B: Create transaction
  B->>A: Create transaction
  A-->>B: { transaction }
  A-->>B: webhook: transaction.created (processing)
  A-->>B: webhook: transaction.status-changed (completed/failed)
  B-->>Usr: Notify the user
```

***

## Select source account

Card withdrawals can be sourced from any account. If the selected account is not in the card's currency, 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 pick the one to withdraw from.

```http theme={null}
GET /core/accounts
```

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

***

## Link or select a card account

The widget session lets the user link a new card or select an existing one.

### Create a widget session

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

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

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

Pass `response.session` to your frontend to initialize the widget.

### Set up the widget

```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('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>
  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 `complete` event fires after the user selects a card. The event payload includes the selected card external account.

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

  if (via === 'external-account') {
    // selection is the selected card external account
    // use selection.id as the destination in the quote
    handleCardSelected(selection);
  }

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

Once you have the selected card, prompt the user to select a source account, then create a quote.

### Handle cancellations

```javascript theme={null}
widget.on('cancel', () => {
  widget.unmount();
  // Return the user to the previous screen
});
```

### Handle errors

The `error` event fires for critical unrecoverable errors. Card-specific errors (duplicate card, country mismatch, card limits) are handled by the widget internally.

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

<Warning>The Payment Widget handles most errors internally. For unrecoverable errors, the widget fires an `error` event. It is the host application's responsibility to handle these events, present an error message to the user, and unmount the widget.</Warning>

***

## Create a quote

Call [Create quote](/rest-apis/core-api/transactions/create-quote) with the origin account and the selected card external account as destination.

```http theme={null}
POST /core/transactions/quote
{
  "origin": {
    "type": "account",
    "id": "a00507fe-628c-4f27-ae81-e1c40b2a8fb8"
  },
  "destination": {
    "type": "external-account",
    "id": "7d5928c5-8ac4-4b0d-8b45-f332ba6a9de7"
  },
  "denomination": {
    "asset": "GBP",
    "amount": "250.00",
    "target": "origin"
  }
}
```

A successful response includes the quote details. Present the quote to the user for confirmation before proceeding.

```json [expandable] theme={null}
{
  "quote": {
    "id": "a91f3c72-1e4b-4c8a-b3e9-9f2d8e4b7c1a",
    "origin": {
      "amount": "250.00",
      "asset": "GBP",
      "node": {
        "type": "account",
        "id": "a00507fe-628c-4f27-ae81-e1c40b2a8fb8",
        "ownerId": "e4ce04dc-67b7-4e9f-af91-482cb6f9fc4a"
      },
      "rate": "1"
    },
    "destination": {
      "amount": "250.00",
      "asset": "GBP",
      "node": {
        "type": "external-account",
        "id": "7d5928c5-8ac4-4b0d-8b45-f332ba6a9de7",
        "ownerId": "e4ce04dc-67b7-4e9f-af91-482cb6f9fc4a"
      },
      "rate": "1"
    },
    "denomination": {
      "asset": "GBP",
      "amount": "250.00",
      "target": "origin",
      "rate": "1"
    },
    "fees": [],
    "expiresAt": "2024-07-24T15:22:39Z"
  }
}
```

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

***

## Confirm and create transaction

Once the user confirms, call [Create transaction](/rest-apis/core-api/transactions/create-transaction) with the quote ID.

```http theme={null}
POST /core/transactions
{
  "quoteId": "a91f3c72-1e4b-4c8a-b3e9-9f2d8e4b7c1a"
}
```

In a successful card withdrawal, the origin is the user's `account` and the destination is the `external-account` representing the card. The transaction status is initially `processing` and updates to `completed` once the transfer settles.

```json [expandable] theme={null}
{
  "transaction": {
    "id": "a1b2c3d4-5e6f-4a8b-9c0d-1e2f3a4b5c6d",
    "origin": {
      "asset": "GBP",
      "amount": "250.00",
      "node": {
        "type": "account",
        "id": "a00507fe-628c-4f27-ae81-e1c40b2a8fb8",
        "ownerId": "e4ce04dc-67b7-4e9f-af91-482cb6f9fc4a"
      }
    },
    "destination": {
      "asset": "GBP",
      "amount": "250.00",
      "node": {
        "type": "external-account",
        "id": "7d5928c5-8ac4-4b0d-8b45-f332ba6a9de7",
        "ownerId": "e4ce04dc-67b7-4e9f-af91-482cb6f9fc4a"
      }
    },
    "status": "completed",
    "quotedAt": "2025-01-10T14:22:15Z",
    "createdAt": "2025-01-10T14:22:45Z",
    "updatedAt": "2025-01-10T14:22:45Z",
    "denomination": {
      "asset": "GBP",
      "amount": "250.00",
      "target": "origin"
    }
  }
}
```

***

## Monitor for settlement

Card withdrawal transactions may remain in `processing` while the payment settles. Monitor until the transaction reaches a terminal state.

* **Webhook events** (recommended):
  * [core.transaction.created](/rest-apis/core-api/transactions/webhooks/transaction-created) — `status: processing` → transaction created, pending settlement
  * [core.transaction.status-changed](/rest-apis/core-api/transactions/webhooks/transaction-status-changed) — `status: completed` → funds settled; `status: failed` → irrecoverable error
* **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 card withdrawals via the Payment Widget.</Check>
