---
title: "Designing a neobank's Add money and Withdraw flows: screens, states, and messages"
seoTitle: "Neobank Add money and Withdraw flows: screens and states"
description: "The screens, statuses, and error messages behind a neobank's Add money and Withdraw buttons, built on on-ramp and off-ramp APIs, with a provider check."
date: "2026-10-12"
updated: "2026-10-12"
category: "payments"
author: "BlindPay Team"
faq:
  - q: "What is the difference between an on-ramp and an off-ramp in a neobank?"
    a: "An on-ramp turns bank money into a stablecoin balance, so it sits behind the Add money button. An off-ramp turns a stablecoin balance back into bank money in a local account, so it sits behind Withdraw or Send to bank. Users never need to see either word. They see a deposit and a bank transfer."
  - q: "How many screens does a Withdraw flow need?"
    a: "Usually five: choose or add the bank account, enter the amount, review the quote with a countdown, a processing screen, and a receipt. Add one review state for payouts held for compliance and one failure screen. Fewer screens is fine on a phone, as long as the user confirms a locked amount before money moves."
  - q: "What should happen when a quote expires on the review screen?"
    a: "Fetch a new quote and show the new amount next to the old one, then ask the user to confirm again. Never execute on the old numbers. BlindPay quotes lock the rate and fees for 5 minutes by default, so a user who leaves the review screen open for longer must see fresh numbers before paying."
  - q: "Which Add money methods can a neobank offer with BlindPay?"
    a: "Users in the US can get a virtual account with their own routing and account number for ACH, wire, and SWIFT deposits. For local currency, a payin quote returns payment instructions for Pix in Brazil, SPEI in Mexico, PSE in Colombia, or Transfers 3.0 in Argentina. ACH pull from a Plaid-connected account is available when enabled on the instance."
  - q: "What should a neobank tell users when a withdrawal fails?"
    a: "Say what happened, what happens to the money, and what to do next. On BlindPay, if a payout cannot be delivered after the stablecoins are collected, they are refunded to the refund wallet on the quote or the sending wallet. Pix and other non-USD local rails refund automatically. USD payouts are refunded after an operator review, so say that."
  - q: "How do I pick an on-ramp and off-ramp provider for a neobank?"
    a: "Score each provider on ten points: locked quotes, fee breakdown before confirm, named rails per country, payin methods, a clear status model with signed webhooks, refund behavior, KYC and KYB in the API, limits and minimums, custody model, and a development environment that can force failures. Test the last one yourself."
---

A neobank's Add money and Withdraw flows are on-ramp and off-ramp calls dressed as banking screens. With BlindPay, Add money is a virtual account or a payin quote, and Withdraw is a bank account, a locked payout quote, and a payout. Design a screen for every status the API returns, and confirm only on locked numbers.

An on-ramp turns bank money into a stablecoin, a token that tracks a currency like the dollar. An off-ramp turns it back into money in a bank account. Your users don't need either word. This guide maps both flows screen by screen, with the states and messages each one needs. The product context lives in [how to build a neobank](/resources/more/how-to-build-a-neobank).

**Key takeaways**

- Add money has two shapes: permanent account details, or a one-time payment instruction from a quote.
- Withdraw is always three API steps: bank account, quote, payout. The quote locks the rate and fees for 5 minutes on BlindPay.
- Each payin and payout status needs its own screen copy. "Processing" for everything creates support tickets.
- Failure copy must say where the money goes. Refund timing differs by rail.
- Pick a provider on ten checks, and test the failure paths yourself.

## What does the Add money flow look like?

Two patterns. Permanent account details for repeat deposits, and a one-time instruction for local-currency top-ups.

| Pattern | When to use it | What the user sees | BlindPay object |
| --- | --- | --- | --- |
| Account details | Salaries, client payments, repeat deposits in USD | Routing and account number in their own name | US virtual account |
| One-time instruction | Top-ups in BRL, MXN, COP, or ARS | A Pix code, a CLABE, a PSE link, or a Transfers 3.0 account | Payin quote, then payin |
| Pull from linked bank | US users with a linked account | "Add $200 from Chase" style button | ACH pull from a Plaid-connected account (enabled on request) |

For account details, the build is in [give every customer US account details](/resources/more/neobank-us-bank-account-for-every-customer). For the one-time pattern:

1. User picks a currency and enters an amount.
2. Your backend creates a payin quote. Show the amount they'll receive in their balance and the fee.
3. User confirms. Your backend creates the payin from the quote within its window (5 minutes; BRL OTC quotes last 10 seconds).
4. Show the payment instruction: the Pix copy-and-paste code, the CLABE for SPEI, the PSE link, or the Transfers 3.0 account.
5. Show a waiting screen. For Pix, SPEI, PSE, and Transfers 3.0, BlindPay waits up to 30 minutes for the money before marking the payin `failed`.
6. Credit pending, then available, from the payin webhooks, as in [the deposit-to-balance ledger](/resources/more/neobank-deposit-to-balance-ledger).

For SPEI, show the CLABE in groups and with a copy button. [Banxico's SPEI page](https://www.banxico.org.mx/services/interbanking-electronic-payme.html) tells payers they need the 18-digit CLABE and that banks often make them register a new recipient first. Warn first-time payers about that delay.

## What does the Withdraw flow look like?

Five screens, in this order. Each maps to one BlindPay call or webhook.

1. **Choose a bank account.** List saved accounts, or add one. Adding is once per recipient.
2. **Enter the amount.** Let the user type either what they send or what the recipient gets.
3. **Review.** Your backend creates a payout quote. Show rate, fees, the amount that lands, the arrival estimate, and a countdown to `expires_at`.
4. **Processing.** The user confirms and your backend executes the payout. `payout.new` fires once the funds are captured.
5. **Receipt.** Show the final amount and a reference the recipient's bank can trace.

The review screen carries the most risk. [How to show a cross-border quote](/resources/more/cross-border-quote-screen-fee-disclosure) covers the disclosure rules in detail. In short: show the rate, every fee, and the total, and don't paint an estimate as a promise. For US consumer remittances, the [Remittance Transfer Rule disclosures](https://www.law.cornell.edu/cfr/text/12/1005.31) require the exchange rate rounded to between two and four decimal places, among other items.

### How should the countdown behave?

Show it, warn before it ends, and re-quote on expiry. A silent expiry turns into a failed confirm tap.

[WCAG's timing guidance](https://www.w3.org/WAI/WCAG21/Understanding/timing-adjustable) asks that users be warned before a time limit ends and given at least 20 seconds to extend it with a simple action. "Refresh quote" is that action. Never extend by executing on stale numbers.

**Illustrative example.** Numbers are invented. A user withdraws $500 to a Mexican account.

| Moment | Screen shows |
| --- | --- |
| Quote created | Rate 18.40 MXN per USD, fee $3.00, recipient gets MX$ 9,144.80, 5:00 left |
| 4:40 elapsed | "Your rate expires in 20 seconds. Refresh to keep going." |
| Expired, user taps Refresh | New rate 18.36, recipient gets MX$ 9,124.92. Old amount shown struck through |
| User confirms | Payout created from the new quote |

Showing both amounts after a refresh stops the "you changed my rate" ticket before it starts.

## Which states and messages does each flow need?

One message per status. Map them once and reuse them in the app, push notifications, and email.

| Flow | BlindPay status | Message | Push? |
| --- | --- | --- | --- |
| Add money | `processing` | "Waiting for your deposit" | No |
| Add money | `on_hold` | "We're reviewing this deposit" | Yes |
| Add money | `completed` | "$X added to your balance" | Yes |
| Add money | `failed` | "We didn't receive this deposit" | Yes |
| Add money | `refunded` | "This deposit was returned to the sender" | Yes |
| Withdraw | `processing` | "Sending to your bank" | No |
| Withdraw | `on_hold` | "Your transfer is under review" | Yes |
| Withdraw | `completed` | "Delivered to account ending 1234" | Yes |
| Withdraw | `failed` | "Your transfer didn't go through. Here's what happens to your money" | Yes |
| Withdraw | `refunded` | "Your money is back in your balance" | Yes |

Failure copy must match how refunds work. On BlindPay, if a payout cannot be delivered after the stablecoins are collected, they are refunded 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. Say "usually within" only for the automatic case. [Payout statuses explained](/resources/more/stablecoin-payout-statuses-explained) has the full lifecycle.

## Common mistakes

- **Executing on an expired quote.** Re-quote and reconfirm.
- **One "Processing" message for every state.** Holds and refunds look like bugs.
- **Crediting Add money on the first webhook.** Pending first, available on completion.
- **Hiding the fee inside the rate.** Show the fee line, even when you pass it through.
- **Promising instant on every rail.** BlindPay documents Pix and SPEI payins settling within minutes, while ACH and wire can take up to 5 business days.
- **Asking for bank details at every withdrawal.** Save accounts once per recipient.
- **No tracking reference on the receipt.** Support needs it. [Tracking a payout](/resources/more/how-to-track-a-crypto-off-ramp-payout) lists the reference per rail.

## How do you choose an on-ramp and off-ramp provider?

Score ten things. Each one shows up on a screen above.

| # | Criterion | Screen it affects |
| --- | --- | --- |
| 1 | Quotes lock rate and fees until a stated expiry | Review |
| 2 | Fee breakdown returned before confirm | Review |
| 3 | Named payout rails per country | Choose bank account |
| 4 | Payin methods per country (account details, Pix, SPEI, pull) | Add money |
| 5 | Clear status model with signed webhooks | Processing, notifications |
| 6 | Documented refund behavior per rail | Failure screens |
| 7 | KYC and KYB in the same API | Onboarding |
| 8 | Published limits and minimums | Amount entry |
| 9 | Custody model you can explain to users | Balance, failure copy |
| 10 | A test environment that can force failed and refunded outcomes | All of them, before launch |

[The 10-point liquidity scorecard](/resources/more/onramp-offramp-liquidity-scorecard) adds depth tests, and [where neobanks hold customer funds](/resources/more/where-neobanks-hold-customer-funds) covers criterion 9.

## How BlindPay fits

BlindPay covers both buttons with one API. Add money runs through US virtual accounts in each customer's own name, or through payin quotes that return a Pix code, a CLABE, a PSE link, or a Transfers 3.0 account, with ACH pull from a Plaid-connected account when enabled. Each deposit becomes a payin that settles to the customer's wallet as USDC or USDT.

Withdraw is a saved bank account, a payout quote that locks the rate and fees for 5 minutes, and a payout over Pix, SPEI, ACH, RTP, wire, ACH Colombia, Transfers 3.0, SEPA, or SWIFT (POBO/COBO). Payouts pull the quoted amount at execution, so there is no balance to pre-fund. Statuses map one to one onto the screens above, and Svix-signed webhooks drive your notifications.

KYC, KYB, and sanctions screening run inside the same flows. On a development instance, sentinel amounts force failed and refunded outcomes so you can see every screen before launch.

## What to do next

List your screens against the status table above and mark any status with no message. Then run one payin and one payout on a development instance, using the [payout quotes docs](/docs/payout-quotes) and the [payins docs](/docs/payins).
