Prerequisites
- The user has completed onboarding.
- Capabilities — card deposits require the
cardscapability and the fiat → crypto leg requirestrades(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.
Walkthrough
Select a crypto to buy
Call List assets to retrieve the assets available on the platform. Filter to assets that includebuy in their features array to show only purchasable assets.
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.
Define the source
The source determines how the buy is funded. Choose the path that matches the user’s intent:Fiat external account
The user funds the buy from a linked card or bank account.Check available rails
Call List Rails to verify the deposit rail you plan to use is available. Card:Link or select the external account
Let the user link a new external account or select an existing one. Card: Call List external accounts to fetch the user’s saved cards.status: "ok" and in features.
status: "processing" while the card is validated.
ok and the full details are available.
Monitor the external account status via Get external account or the
external-account.status-changed webhook until status is ok before proceeding.EMD disclaimer
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: Once the user has a linked bank external account, retrieve it with List external accounts.
Transaction preview
Show the user a preview of the buy before committing. The preview is a quote — a price-locked offer valid untilexpiresAt. 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.Fiat external account
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: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 quote
Commit the buy by calling Create transaction with the quote ID.Fiat external account
Confirm the deposit leg first, then the trade leg after the deposit settles. Deposit — card (requires 3DS): Always include theauthorize:3ds requirement — 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.
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.
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.
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.
core.transaction.status-changed webhook with status: "completed" before notifying the user.
Monitor for settlement
Monitor each transaction in the chain by listening forcore.transaction.status-changed webhooks or polling. Each leg fires its own event — wait for status: "completed" on one leg before submitting the next.
Via webhooks
Subscribe tocore.transaction.status-changed. The event fires on every status transition; filter on data.transaction.status to detect terminal states:
completed— transaction settled successfullyfailed— transaction failed; inspectdata.transaction.errorsfor details
Via polling
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.
Notify the user
Display an in-app confirmation when eachcore.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.
You now support crypto buy via the REST API.
Sample transactions
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 owncore.transaction.status-changed webhook on completion.
Card deposit
Bank deposit (SEPA)
Fiat-to-crypto trade
Failure handling
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.