The user has an internal Uphold crypto account with sufficient balance.
Capabilities — card payouts require the card-withdrawals capability and a destination card with octSupport: "supported"; the crypto → fiat leg of a bank sell requires trades. Bank withdrawal capability varies by rail — see the per-rail guides.
The integration uses Uphold’s rails. Partner-owned rails (omnibus) follow a different flow not covered here.
The user receives the proceeds in a linked card or bank account.
Apple Pay and Google Pay are not supported as sell destinations — the platforms do not expose a payout API. Card OCT is the only card-rail payout supported.
Call List Rails to verify the payout rail you plan to use is available. The withdraw feature must be present for the rail to be usable as a sell destination.Card:
This disclaimer is required to comply with FCA regulations. Display it to users based in GB, the first time they link a credit/debit card or generate bank deposit details — it only needs to be shown once:
Uphold Europe Limited is an EMD Agent of Optimus Cards UK Limited (FRN: 902034). All received funds are held in a designated safekeeping account with a regulated bank and kept separate from Uphold’s own assets. These funds are not protected by the UK Financial Services Compensation Scheme.
A freshly linked card returns "octSupport": "unknown" while Uphold verifies OCT eligibility. Poll GET /core/external-accounts/{id} until details.octSupport is "supported" before using the card as a sell destination.
Bank:Bank account linking varies by rail. See the per-rail guides:
Once the user has a linked bank external account, retrieve it with List external accounts. Filter for type: "bank", status: "ok", and "withdraw" in features.
Also ensure the user has a fiat Uphold account in the bank’s currency to receive the trade proceeds. If one does not exist, create it with Create account.
POST /core/accounts{ "label": "My EUR Account", "asset": "EUR"}
Show the user a preview of the sell before committing. The preview is a quote — a price-locked offer valid until expiresAt. The amount the user specifies is denomination.amount; set denomination.asset to indicate whether the amount is expressed in the source or destination asset.
Quotes expire quickly. Capture user confirmation before expiresAt and re-quote if the user hesitates.
Card (OCT):A card sell settles in a single transaction — the crypto account is debited, converted at the locked rate, and fiat is paid out to the card via OCT.
Bank:A bank sell runs in two sequenced legs: a crypto-to-fiat trade lands fiat in the user’s Uphold fiat account, followed by a fiat withdrawal to the linked bank account. Create a quote for each leg.Trade quote (crypto → fiat):
Withdrawal quote (fiat → bank):After presenting the trade quote, also create the withdrawal quote so the user sees the full sell preview before confirming. The example below uses SEPA (EUR). For rail-specific quote shapes, see the per-rail bank-transfer withdrawal guides.
UK retail users may encounter a 409 user_capability_failure with restrictions: ["financial-promotion-cooldown-running"] for 24 hours after onboarding. See the trade guide for cooldown details.
Wait for the core.transaction.status-changed webhook with status: "completed" before proceeding to the withdrawal leg.Bank — withdrawal leg (fiat → bank):Once the trade settles and fiat lands in the user’s Uphold fiat account, confirm the withdrawal quote.
POST /core/transactions{ "quoteId": "623000c8-9bdf-4a2b-aa3d-6a6b44a7f6a0"}
Bank withdrawals settle asynchronously — instantly for FPS / FedNow, hours to days for ACH / SEPA. See the per-rail bank-transfer withdrawal guides for timing details.
Wait for the core.transaction.status-changed webhook with status: "completed" before notifying the user.
Monitor each transaction in the chain by listening for core.transaction.status-changed webhooks or polling. Each leg fires its own event — wait for status: "completed" on one leg before submitting the next.
GET /core/transactions/{transactionId} returns the current state. Use polling as a fallback when webhook delivery cannot be guaranteed. Terminal statuses are completed and failed.
GET /core/transactions/a0b1c2d3-e4f5-4a6b-7c8d-e9f0a1b2c3d4
Display a confirmation when the final core.transaction.status-changed webhook fires with status: "completed". For card payouts, include the payout amount and currency so the user knows the card credit to expect. For bank withdrawals, confirm the amount and destination account once the withdrawal settles.
A sell to a card debits the user’s internal crypto account, converts at the locked quote rate, and pays out fiat to the linked card via OCT. The transaction fires a single core.transaction.status-changed webhook on completion.
Quote creation succeeds but returns non-empty requirements; transaction creation blocked until resolved
Prompt user to complete the outstanding KYC step, then re-quote
Destination card fails octSupport check
Transaction fails before any debit
Re-verify octSupport on the card external account; relink if needed
Card OCT declined by issuer at payout
Crypto is automatically credited back to the user’s internal Uphold account
core.transaction.status-changed fires with status: "failed"; notify the user
Rate slippage — quote expired between quote and transaction
Transaction creation returns a quote-expired error; no debit
Re-quote and retry
Every terminal state — completed or failed — fires a core.transaction.status-changed webhook. Listen for all three to keep your UI in sync.
If the card OCT is declined after the crypto has been debited, the crypto is returned to the user’s internal account — not to an external wallet. Make sure your UI communicates this clearly.