---
title: "How crypto exchanges add local-currency withdrawals with an off-ramp API"
seoTitle: "Crypto exchange fiat withdrawals with an off-ramp API"
description: "How exchanges, brokers, and wallets let users withdraw to Pix, SPEI, ACH, or SEPA: the flow, the quote screen, the compliance split, and refunds."
date: "2026-10-10"
updated: "2026-10-10"
category: "payments"
author: "BlindPay Team"
howto:
  name: "How to add local-currency withdrawals to a crypto exchange with an off-ramp API"
  steps:
    - name: "Onboard each withdrawing user as a customer"
      text: "Create the customer and complete KYC, so the person receiving the money is visible to the payout provider."
    - name: "Add the user's bank account as first party"
      text: "Register the user's own Pix, SPEI, ACH, or SEPA account with the first_party relationship, which checks the beneficiary name against the customer."
    - name: "Request a quote on the withdrawal screen"
      text: "Create a payout quote for the amount the user typed and show the rate, fees, and amount received before confirmation."
    - name: "Execute before the quote expires"
      text: "When the user confirms, execute the payout against the quote from your treasury wallet, with an idempotency key on the call."
    - name: "Update the user's balance from webhooks"
      text: "Mark the withdrawal complete, failed, or refunded from payout events, and credit refunded amounts back to the user's balance."
faq:
  - q: "How does an exchange let users withdraw to a local bank account?"
    a: "It converts the user's stablecoin balance into local currency through an off-ramp API and pays it to the user's own bank account over a local rail, such as Pix in Brazil or SPEI in Mexico. The user sees a quote with the amount they'll receive, confirms, and the exchange executes the payout from its treasury wallet. The exchange keeps its own ledger and debits the user."
  - q: "Does each exchange user need KYC with the payout provider?"
    a: "On BlindPay, yes. The person receiving a payout has to be an onboarded customer, because moving money for parties the provider never screened is nesting, which BlindPay's rules prohibit. KYC Standard is automated and takes about 60 seconds, so most exchanges run it at the first withdrawal rather than at signup."
  - q: "What should the withdrawal screen show?"
    a: "The amount debited from the balance, the rate, each fee, the local amount that will arrive, the rail, and when the quote expires. Request the quote when the user opens the confirm step, not when they open the app, and request a new one if it expires. Showing the received amount up front removes most support tickets about short withdrawals."
  - q: "Which rails can a fiat withdrawal use?"
    a: "BlindPay pays out over ACH, domestic wire, RTP, and SWIFT (POBO/COBO) in US dollars, Pix, PIX Safe, and TED in Brazil, SPEI in Mexico, ACH Colombia, Transfers 3.0 in Argentina, and SEPA in euros. Pix, PIX Safe, SPEI, and Transfers 3.0 settle in minutes; ACH, wire, TED, ACH Colombia, and SEPA take about 1 to 2 business days."
  - q: "What happens if a withdrawal can't be delivered?"
    a: "The stablecoins come back instead of the fiat. 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; USD payouts and payouts whose review times out are refunded after an operator review. Credit the user's balance when the refunded event arrives, not before."
---

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](/resources/more/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](/docs/kb/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](/docs/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](/docs/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](/docs/learn/idempotency)).
5. **Close the loop.** `payout.update` and `payout.complete` events tell you when the withdrawal moved, finished, failed, or was refunded ([payouts](/docs/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](/docs/kb/nested-payments)). Withdrawals to verified users in their own name are the clean version of this flow. [First-party vs third-party payouts](/resources/more/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](/docs/kb/payment-methods)):

| Country | Rail | Recipient identifier | Typical settlement |
| --- | --- | --- | --- |
| Brazil | Pix | Pix key: CPF, CNPJ, phone, email, or random key | Minutes |
| Brazil | PIX Safe | CPF or CNPJ, bank code, branch, and account | Minutes |
| Brazil | TED | CPF or CNPJ, bank code, branch, and account | 1 to 2 business days |
| Mexico | SPEI | CLABE | Minutes |
| Argentina | Transfers 3.0 | CVU, CBU, or alias | Minutes |
| Colombia | ACH Colombia | Bank account and document ID | 1 to 2 business days |
| United States | ACH, domestic wire, RTP | Routing and account number | RTP instant; ACH and wire 1 to 2 business days |
| Global | SWIFT (POBO/COBO) | BIC and IBAN or account number | Up to 5 business days, tracked by UETR with MT103 confirmations |
| SEPA zone | SEPA | IBAN | 1 to 2 business days |

Users of instant rails expect instant. Banxico says SPEI payments and transfers "should not take more than 30 seconds" ([Banxico](https://www.banxico.org.mx/services/interbanking-electronic-payme.html)), 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](/resources/more/pix-key-cpf-clabe-payout-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](/docs/kb/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](https://www.law.cornell.edu/cfr/text/31/1010.410)). [The travel rule for stablecoin off-ramps](/resources/more/travel-rule-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](/resources/more/how-onramp-offramp-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](/docs/payouts) list the statuses.
