Capabilities — card deposits require the cards capability and the fiat → crypto leg requires trades (UK retail users have a 24-hour post-onboarding cooldown before trading). Bank deposit 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.
Call List assets to retrieve the assets available on the platform. Filter to assets that include buy in their features array to show only purchasable assets.
The chosen asset (BTC in this example) becomes the destination.asset for the buy.Call List accounts to find the user’s existing Uphold accounts for that asset.
GET /core/accounts
If the user does not yet have an account for the chosen asset, create one with Create account.
POST /core/accounts{ "label": "My BTC Account", "asset": "BTC"}
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.
Bank:For bank accounts, the linking flow varies by rail. See the per-rail guides:
Show the user a preview of the buy 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.
A fiat buy runs in two sequenced legs: a fiat deposit into the user’s Uphold fiat account, followed by a fiat-to-crypto trade. Create a quote for each leg.Deposit quote — card:
Trade quote (fiat → crypto):After presenting the deposit quote, also create the trade quote so the user can see the full buy preview — the deposit amount and the crypto amount they will receive — before confirming.
For bank deposits, this trade quote is for preview only. Bank deposits settle asynchronously (hours to days), so this quote will have expired long before the deposit lands. Re-quote at the time you confirm the trade leg, after the deposit status: "completed" webhook fires.
Confirm the deposit leg first, then the trade leg after the deposit settles.Deposit — card (requires 3DS):Always include a returnUrl — the card issuer may require 3DS authorization. You may embed stateful data in the returnUrl query parameters to resume the flow after redirect.Include X-Uphold-User-Ip and X-Uphold-User-Agent headers — they are used by the platform for 3DS risk evaluation.
If origin.node.confirmationUrl is present, redirect the user to that URL to complete the 3DS challenge. After completion, the issuer redirects the user back to your returnUrl. Wait for the core.transaction.status-changed webhook with status: "completed" before proceeding to the trade leg.Deposit — bank:Bank deposits do not require a returnUrl or 3DS.
POST /core/transactions{ "quoteId": "b2c3d4e5-f6a7-4b8c-9d0e-f1a2b3c4d5e6"}
Bank deposits settle asynchronously — instantly for FPS / FedNow, hours to days for ACH / SEPA / wire. See the per-rail bank-transfer deposit guides for timing details.
Wait for the core.transaction.status-changed webhook with status: "completed" before proceeding to the trade leg.Trade (fiat → crypto):Once the deposit settles, confirm the trade quote.
POST /core/transactions{ "quoteId": "c3d4e5f6-a7b8-4c9d-0e1f-a2b3c4d5e6f7"}
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 an in-app confirmation when each core.transaction.status-changed webhook fires with status: "completed". Show a final confirmation once the last leg settles — the crypto is now credited to the user’s Uphold account.
A buy via a fiat external account produces two chained transactions — a fiat deposit and a fiat-to-crypto trade. Each follows the same lifecycle and fires its own core.transaction.status-changed webhook on completion.
Card declined or 3DS failed (charge never captured)
Nothing moved; no fees charged
Deposit (card)
Transaction
Quote expired before commit
No transaction created; create a new quote
Deposit (card)
Transaction
Card captured but later reversed by issuer
Fiat balance reduced by reversal; Uphold absorbs processing fees
Deposit (card)
Transaction
cards capability not enabled
No transaction; prompt KYC
Deposit (bank)
Transaction
Insufficient funds or mandate not authorised
No deposit received; surface error to user
Deposit (bank)
Transaction
Settlement timeout (rail-dependent)
No deposit received; retry or contact support
If the deposit leg completes but the fiat → crypto conversion fails, the user’s fiat funds remain in their Uphold fiat account. Detect this via the core.transaction.status-changed webhook and either retry the conversion or surface the fiat balance to the user.
The core.transaction.status-changed webhook fires with status: "failed" for each failed transaction.