How to test onramp flows in the sandbox environment
Prerequisite: Your organization must be approved before it can quote. See KYC Overview for what verification requires, and KYC WebSDK for the hosted flow.
An onramp moves fiat from a bank account to a wallet. In v2 both sides are payment accounts: you register the bank account and the wallet, quote between them, create the order, and fund it.Every call below runs for one organization, set as $ORGANIZATION_ID in the examples. Use your own organization’s id from GET /me, or a customer organization’s id when you ramp for that customer. Register the accounts, quote and order under the same id. See Organizations in the route.
Either of these endpoints gives you the asset identifiers for a quote:
GET /supported_assets requires auth. It returns every market and token a quote can use. Pass paymentAccountId to see what one of your accounts can use, or chain for one chain.
GET /lookup/stablebonds is public. It returns every stablebond on every chain, with pricing and supply data.
Use a token’s asset from the first, or the identifier from the second, as destinationAsset in the quote.
Asset identifier format differs by chain:
Solana: Base58 mint address (for example AvvetPGuuB5FD5m86fpw3LtDKyQoUFT1mG9WarNQLW4q)
Stellar:CODE:ISSUER (for example CETES:GC3CW7...)
EVM (Base, Polygon, Monad, Avalanche): the raw contract address only (for example 0xcC77c598d42f2f78Beb42C91d12B9d4041a5cE29)
Register the bank account and the wallet: POST /organization/{organizationId}/payment_account
Show Details
Register the bank account the customer pays from and the wallet that receives the crypto. Each call returns the account’s id and its rails. See Register payment account for the full schema.
You can quote a direction only when it is ready. For an onramp, the bank account needs source ready on its fiat rail (spei), and the wallet needs destination ready on its chain rail. Read the account again with GET /organization/{organizationId}/payment_account/{id}, or listen for paymentAccountUpdated.
A bank account route needs holder details such as accountHolderName and taxId. We fill them from the verified identity of the organization in the route, so pass the customer’s organization id to register a customer’s bank account. If a field is still missing, we return 422registration-info-required with the fields in missing. Send them under registrationInfo and retry. Use the dummy RFC XAXX010101000 in sandbox.
3
Create a quote: POST /organization/{organizationId}/quote
Show Details
Quote from the bank account to the wallet. Both accounts must belong to the organization in the route. Set sourceAsset to the fiat currency (MXN or BRL) and destinationAsset to the asset identifier. Quotes expire 2 minutes after they are created (expiresAt). See Create quote for the full schema.
The response has the quoteId, sourceAmount, destinationAmount, both rails, rateBeforeFees, rateAfterFees, the total fee (feeBps, feeAmount) and the fee lines in fees[].
Partner fees: We apply your organization’s partner fee automatically. To set it for one quote, add "partnerFeeBpsOverride": 100. We cap values above 500. See Partner fees.
In sandbox, onramp quotes are limited to a sourceAmount of 500. Larger amounts return 400sandbox-limit-exceeded.
4
Create the order: POST /organization/{organizationId}/order
Show Details
Send the quoteId. The quote carries the accounts, rails, assets and amounts, so you do not repeat them. The response is 201 with the full order, and the order’s id equals the quoteId. See Create order for the full schema.
memo is optional, up to 255 characters. Sending the same quoteId again returns the existing order with 200, so a retry never creates a second order. See Idempotency.fundingInstructions tells the customer how to pay:
On spei, destination gives the CLABE to pay. On pix, it gives a brCode. destination is absent until it is available; you receive it in an orderUpdated event, or you can read the order again.
5
Simulate the fiat deposit: POST /organization/{organizationId}/order/{id}/fiat_received
Show Details
In production, the customer pays the funding destination before fundingDeadline. In sandbox, simulate the deposit with Simulate fiat received. Send the order’s source.amount.
We return 200 with an empty body, and the order continues as it would after a real deposit.
This endpoint works only in sandbox. In production it returns 403forbidden. It returns 422unsupported when the order’s source is not a spei or pix bank account.
The customer must send the exact order amount. Pay the source.amount from the order to the funding destination.
6
Track the order: GET /organization/{organizationId}/order/{id}
The status moves created → funded → completed. A Stellar onramp can stop at claimable first; see Stellar: wallets without a trustline. An order can also end failed (see failure), canceled or refunded.
Show Chain-specific quote examples
The source is always the bank account. Only the wallet and destinationAsset change.Solana: use the Base58 mint address:
We issue stablebonds on Solana and bridge them to the wallet’s chain, so a stablebond onramp to another chain can include a bridge fee line. For an avalanche wallet, read the available assets from GET /supported_assets?chain=avalanche.
Show Common errors
Error
Cause
Fix
409organization-not-approved
Your organization is not approved
Complete verification, then quote again
409payment-account-not-ready
The bank account is not ready as a source, or the wallet is not ready as a destination
Check each account’s rails and wait for the direction to become ready
422registration-info-required
The bank account route needs holder fields we do not hold
Send the fields in missing under registrationInfo
400sandbox-limit-exceeded
The sandbox onramp amount is above 500
Use a smaller amount
400quote-expired
The quote is older than 2 minutes, or does not exist
Create a new quote
400amount-out-of-range
The amount is outside a rail’s minAmount or maxAmount
Change the amount
422quote-unavailable
We cannot price the route right now
Retry with backoff
Quote rejected for a stablecoin on Base or Polygon
We quote stablecoins only on Solana, Stellar and Monad
Quote a stablebond, or use a wallet on one of those chains
A Stellar wallet needs an on-chain account and a trustline before it can hold an asset. When the destination wallet has neither, we still deliver: we send the asset as a claimable balance, and the customer claims it.
Quote as usual. The wallet is the quote’s destination, so we check it on-chain. If it needs setup, the quote’s feeBps and feeAmount include a one-time cost to cover the XLM for the account and the trustline reserve.
Create and fund the order as usual.
The order becomes claimable. We deliver the claimable balance, and the order’s claimInstructions describes it:
stellarTransaction: a self-held wallet signs and submits xdr. The transaction adds the trustline and claims the balance in one step.
authorization: an embedded wallet gets an authorizationId. The order’s authorization with that id holds the message for the key holder to sign.
Sign and submit. A self-held wallet submits the signed xdr. For an embedded wallet, the key holder signs authorization.method.message and you send the signature to POST /organization/{organizationId}/order/{id}/authorization; see Order authorization. After the claim lands, the wallet holds the trustline and the asset, and the order moves to completed.
If the wallet already has an account and the trustline, we deliver directly. The order goes from funded to completed, and claimInstructions stays null.
The claim transaction goes stale. It uses the wallet’s current sequence number, so any other transaction the wallet submits first breaks it. We build a new transaction on every call and never store one, so call the endpoint again right before the customer signs.
Assistant
Responses are generated using AI and may contain mistakes.