How crypto exchanges add local-currency withdrawals with an off-ramp API

How exchanges, brokers, and wallets let users withdraw to Pix, SPEI, ACH, or SEPA: the flow, the quote screen, the compliance split, and refunds.

An exchange adds local-currency withdrawals by converting the user's stablecoin balance through an off-ramp API and paying their own bank account over Pix, SPEI, ACH, SEPA, or another local rail. The user sees a quote with the exact amount they'll receive, confirms, and the payout runs from the exchange's treasury wallet. Users get KYC once; refunds come back as stablecoins.

This is the first-party withdrawal case: the person cashing out is the account holder, and the money goes to an account in their own name. It's a different job from paying suppliers or contractors, and it has its own rules about names, limits, and refunds.

Key takeaways

  • The exchange keeps the user ledger. The off-ramp converts and pays out from a treasury wallet.
  • Each withdrawing user is an onboarded customer, with their own bank account registered as first party.
  • Request the quote on the confirm screen and show the amount received before the user taps confirm.
  • Instant rails (Pix, SPEI, Transfers 3.0) set the user's expectations. Bank-day rails need a clear "arrives by" date.
  • Refunds return stablecoins. Credit the user from the webhook, never from a timeout.

Why do exchanges add local-currency withdrawals?

Because "send USDC to some other app to cash out" is where users leave. A user who can sell and withdraw to Pix in one screen stays. A user who has to move funds to a second platform often moves for good.

The other reason is control. An exchange that sends users to a third-party widget hands over the last screen of its own product. With an API, the exchange owns the flow and the off-ramp provider handles conversion, rails, and payout compliance. Types of crypto off-ramps compares that API model with widgets, OTC desks, and off-ramp wallets.

How does a withdrawal flow work, step by step?

Five steps, with the exchange's ledger wrapped around them.

  1. Onboard the user. Create a customer and complete KYC. On BlindPay, KYC Standard is automated and takes about 60 seconds (cut-off times).
  2. Add their bank account as first party. Set recipient_relationship to first_party. Anything else skips the name-match check, which is the opposite of what a withdrawal wants (bank accounts).
  3. Quote on the confirm screen. Create a payout quote for the withdrawal amount. It locks the exchange rate and fee and expires 5 minutes after creation (payout quotes).
  4. Execute. On confirm, debit the user's ledger balance and execute the payout against the quote from your treasury wallet. A quote backs exactly one payout, and an idempotency key makes a retry safe (idempotency).
  5. Close the loop. payout.update and payout.complete events tell you when the withdrawal moved, finished, failed, or was refunded (payouts).

Why onboard each user? Because paying a party the provider can't see is nesting. BlindPay's rule is that you may not use your account to move payments for any party "whose identity or transaction activity is not visible to BlindPay" (nested payments). Withdrawals to verified users in their own name are the clean version of this flow. First-party vs third-party payouts explains why the relationship field matters.

Which rails can a withdrawal land on?

The user's local rail, chosen by the bank account type. BlindPay's payout rails, with settlement as a rule of thumb (payment methods):

CountryRailRecipient identifierTypical settlement
BrazilPixPix key: CPF, CNPJ, phone, email, or random keyMinutes
BrazilPIX SafeCPF or CNPJ, bank code, branch, and accountMinutes
BrazilTEDCPF or CNPJ, bank code, branch, and account1 to 2 business days
MexicoSPEICLABEMinutes
ArgentinaTransfers 3.0CVU, CBU, or aliasMinutes
ColombiaACH ColombiaBank account and document ID1 to 2 business days
United StatesACH, domestic wire, RTPRouting and account numberRTP instant; ACH and wire 1 to 2 business days
GlobalSWIFT (POBO/COBO)BIC and IBAN or account numberUp to 5 business days, tracked by UETR with MT103 confirmations
SEPA zoneSEPAIBAN1 to 2 business days

Users of instant rails expect instant. Banxico says SPEI payments and transfers "should not take more than 30 seconds" (Banxico), so a SPEI withdrawal that sits for an hour reads as broken. For bank-day rails, show an "arrives by" date instead of a spinner. Pix key, CPF, and CLABE details covers the fields users get wrong.

What should the withdrawal screen show?

The quote, in plain words. Illustrative example, with invented numbers:

A user in Brazil withdraws 1,000 USDC to Pix.

  • You withdraw: 1,000.00 USDC
  • Rate: 5.33 BRL per USDC
  • Fees: R$ 10.00
  • You receive: R$ 5,320.00
  • Arrives: in minutes, by Pix
  • Price held for: 4:52

The quote fields map straight onto those lines: sender_amount, blindpay_quotation, flat_fee and any partner fee, receiver_amount, and expires_at in epoch milliseconds. Fees come out of the received amount when cover_fees is false, or on top of the USDC sent when it's true.

Two settings matter for exchanges. If you charge your own withdrawal fee, pass a partner fee on the quote so it shows as its own line. And if the user wants a round local amount, set currency_type: receiver so request_amount is the reais they receive.

Large withdrawals hit limits. Customer limits are calculated on the stablecoin amount and are separate for payouts and payins, and they can be raised with additional documentation through the limit-increase endpoint (KYC). Tell users up front what their tier allows.

Who handles compliance on each side?

Split it by leg. The exchange runs its own program for its users and for crypto deposits and withdrawals on chain. The payout provider runs KYC on the customer it pays, monitoring on each payout, and the bank-side checks.

On the exchange's side, crypto transfers to and from other platforms can trigger travel rule obligations. In the US, the funds-transfer rule at 31 CFR 1010.410(f) applies to transmittal orders "in the amount of $3,000 or more" and requires the transmittor's name, address, amount, date, and the recipient's institution to travel with the order (Cornell LII). The travel rule for stablecoin off-ramps covers how that applies to virtual asset transfers.

On the payout side, expect review steps. ACH, wire, and RTP payouts pass through on_hold as a standard step for compliance review after the crypto is collected, and SWIFT payouts to third parties need documents. A withdrawal to the user's own SWIFT account doesn't.

What happens when a withdrawal fails?

The stablecoins come back, and your ledger has to follow. If a payout can't be delivered after the stablecoins are collected, BlindPay refunds them to the quote's refund wallet, or to the sending wallet when none is set. On Pix and other non-USD local rails the refund is automatic, while USD payouts and payouts whose review times out are refunded after an operator review.

Three rules keep balances right:

  1. Debit the user when you execute, and hold the debit as "pending withdrawal".
  2. Credit it back only on a refunded status from the webhook. A timeout is not a refund.
  3. For failed, contact support before crediting, because a failed payout doesn't refund automatically.

The economics behind each withdrawal, spread and fees, work the same way they do for any ramp. How on/off-ramp providers make money breaks them down.

What to do next

On a development instance, create one test user, add a first-party Pix account, and run a withdrawal with request_amount 77700 to see the refunded path end to end before you wire it to real balances. The payouts docs list the statuses.

FAQ