---
title: "Neobank API quickstart: from sign-up screen to first deposit and first payout"
seoTitle: "Neobank API quickstart: first deposit and first payout"
description: "Map every neobank screen to a BlindPay API call: terms, KYC, wallet, US account details, first deposit, bank account, quote, payout, and webhooks."
date: "2026-10-24"
updated: "2026-10-24"
category: "payments"
author: "BlindPay Team"
howto:
  name: "How to take a neobank user from sign-up to first deposit and first payout with the BlindPay API"
  steps:
    - name: "Get a development instance and an API key"
      text: "Create a development instance in the BlindPay dashboard and an API key scoped to it. Keys are shown once, so store the key in your secrets manager and call the API only from your backend."
    - name: "Collect terms acceptance"
      text: "Create a terms of service link for the user, open it in the app, and read the tos_id from your redirect URL or the tos.accept webhook."
    - name: "Create the customer with KYC data"
      text: "Send the user's identity data, documents, and the tos_id to the customers endpoint, then wait for kyc_status to become approved."
    - name: "Register the settlement wallet"
      text: "Add the blockchain wallet that receives USDC for this user, either by signed message on EVM or by direct address."
    - name: "Issue US account details"
      text: "Create a virtual account linked to that wallet. Show the routing and account numbers once the account is approved."
    - name: "Credit the first deposit"
      text: "Each deposit creates a payin. Credit the user's balance in your ledger when the payin completes, keyed on the payin id."
    - name: "Add a payout destination"
      text: "Save the recipient's bank account for the chosen rail, such as ACH, Pix, or SPEI, with the fields that rail requires."
    - name: "Quote, confirm, and execute the payout"
      text: "Create a payout quote, show the receiver amount and fees, then execute before the quote expires, about 5 minutes by default."
    - name: "Track the payout by webhook"
      text: "Verify each webhook signature, then update the transfer screen on payout.new and payout.complete."
faq:
  - q: "What is the fastest way to integrate a payments API into a neobank?"
    a: "Build against a development instance first and map each app screen to one API call: terms, customer, wallet, virtual account, payin, bank account, quote, payout. On a BlindPay development instance KYC is auto-approved, virtual accounts are created already approved, and payins complete by themselves in about 30 seconds, so a team can see a full deposit and payout loop in an afternoon."
  - q: "Which BlindPay endpoint creates a customer?"
    a: "POST /v1/instances/in_.../customers creates a customer from KYC data plus the tos_id the user received after accepting BlindPay's terms of service on the hosted page. The customer starts in verifying. KYC Standard typically resolves in about 60 seconds, while KYC Enhanced and KYB need a manual compliance review before the status becomes approved."
  - q: "How do I give a user US bank account details through the API?"
    a: "Create a virtual account for an approved customer, linked to a blockchain wallet that belongs to the same customer. The account starts in pending_review, then verifying, then approved, and only then carries real routing and account numbers. The virtualAccount.complete webhook tells you when to show them. Each account costs $1.50 per month."
  - q: "Can I connect a user's US bank with Plaid?"
    a: "Yes, on instances where BlindPay has enabled the Plaid feature. The user links a bank through Plaid, BlindPay reads the verified routing and account numbers, and the result is an ach bank account that can fund an ach_pull payin. Without the feature, the endpoint returns plaid_not_supported, so ask BlindPay to enable it first."
  - q: "Why did my payout request fail with an expired quote?"
    a: "A payout quote locks the rate and fees for about 5 minutes. If the user confirms after that, executing the old quote fails. Create a new quote, show the new amount, and ask the user to confirm again. Never retry an execute with a different quote under the same idempotency key, since each key should map to one intended payout."
  - q: "Which SDKs does BlindPay offer?"
    a: "Official SDKs exist for Node.js and TypeScript, Python, Go, PHP, and Swift, all wrapping the same REST API. For any other language, download the OpenAPI 3.1 spec from api.blindpay.com/doc and generate a typed client. The Swift SDK is a natural fit for an iOS neobank app's backend tooling and tests."
---

A neobank integration on BlindPay is nine API calls in a fixed order: terms, customer, wallet, virtual account, payin, bank account, quote, payout, webhooks. Map each one to a screen in your app and you get a user from sign-up to a first dollar deposit and a first local payout, testable end to end on a development instance.

This guide is the screen-by-screen version. For the general developer walkthrough, see [integrating a stablecoin API](/resources/more/how-to-integrate-a-stablecoin-api). For the product view of the whole build, start with [how to build a neobank](/resources/more/how-to-build-a-neobank).

**Key takeaways**

- Start on a development instance: KYC auto-approves, virtual accounts are created approved, and payins settle on their own in about 30 seconds.
- Every API call belongs to a screen. If you can't name the screen, the call is in the wrong place.
- The user's balance lives in your ledger. Webhooks tell you when to move it.
- Quotes expire in about 5 minutes. Build the re-quote path before you build the happy path.
- Send an Idempotency-Key on every call that moves money.

## What do you need before the first call?

A BlindPay account, a development instance, and an API key for it, all created in the dashboard.

API keys are scoped to one instance and shown once at creation, per the [API keys docs](/docs/learn/api-keys). A development key doesn't work against production, and the reverse. Keep the key on your server; the mobile app talks only to your backend.

The base URL is api.blindpay.com, and every request carries the key as a bearer token. A first smoke test lists customers on your instance:

```bash
curl https://api.blindpay.com/v1/instances/in_000000000000/customers \
  -H "Authorization: Bearer YOUR_API_KEY"
```

If that returns an empty list, you're ready. Use an SDK from the [SDKs page](/docs/sdks) (Node.js, Python, Go, PHP, Swift) or generate a client from the [OpenAPI 3.1 spec](https://spec.openapis.org/oas/v3.1.0.html) BlindPay serves at api.blindpay.com/doc.

## Which API call sits behind each screen?

One call per screen, nine in total. Paths below are relative to /v1/instances/in_.../ except the terms link.

| App screen | Method and path | Key fields | What the screen waits for |
| --- | --- | --- | --- |
| 1. Accept terms | POST /v1/e/instances/in_.../tos | idempotency_key (a UUID) | tos_id on your redirect URL, or the tos.accept webhook |
| 2. Verify identity | POST customers | tos_id, type, kyc_type, name, date of birth, tax_id, address, ID document, selfie | kyc_status: approved |
| 3. Set up balance | POST customers/re_.../blockchain-wallets | network, is_account_abstraction, address or signed message | bw_ id returned |
| 4. Show account details | POST customers/re_.../virtual-accounts | token, blockchain_wallet_id | status: approved and the virtualAccount.complete webhook |
| 5. Deposit received | none, a deposit creates a payin | payin id, amounts, fees | payin.complete webhook |
| 6. Add recipient | POST customers/re_.../bank-accounts | type plus the rail's fields | bankAccount.new webhook |
| 7. Review transfer | POST quotes | bank_account_id, request_amount in cents, currency_type, cover_fees, network, token | quote id and expiry |
| 8. Confirm transfer | POST payouts/evm | quote_id, sender_wallet_address | payout.new webhook |
| 9. Transfer status | GET payouts/po_... or webhooks | status | payout.complete webhook |

The full field lists live in the [payout quickstart](/docs/quickstart-payout), the [customers reference](/docs/learn/customers), and [create a virtual account](/docs/virtual-accounts-create).

## How do terms and KYC work on screens 1 and 2?

The user accepts BlindPay's terms on a hosted page, then you create the customer with their KYC data.

1. Your backend requests a terms link and the app opens it. Acceptance only works from the user's browser at app.blindpay.com; server-side acceptance is ignored, per the [terms of service docs](/docs/learn/terms-of-service).
2. BlindPay redirects to your redirect_url with a tos_id. Store it.
3. Create the customer with the tos_id and identity data. The status starts as verifying.
4. KYC Standard typically resolves in about 60 seconds. KYC Enhanced and KYB wait for manual review. Show "verifying" with a realistic time, not a spinner that never ends.
5. On rejection, read kyc_warnings and fraud_warnings, explain what to fix, and create a new customer with corrected data. Existing customers can't be re-submitted.

The design of this funnel (screen order, drop-off, tiered limits) is covered in [neobank KYC and KYB onboarding](/resources/more/neobank-kyc-kyb-onboarding-flow).

## How do screens 3 and 4 give the user a US account?

Register the wallet that receives USDC, then create a virtual account linked to it.

On EVM networks you can register the wallet by signed message, so BlindPay recovers the address from the signature. On Stellar, Solana, and Tron you pass the address directly. Then create the virtual account with the token (USDC or USDT) and the blockchain_wallet_id.

In production the account moves from pending_review (compliance) to verifying (bank) to approved, and only then carries real numbers. On a development instance it is created approved, with routing number 110000000 and a fake account number, so you can build screen 4 today. The product decisions behind this screen are in [giving every neobank customer a US bank account](/resources/more/neobank-us-bank-account-for-every-customer).

## How do you credit the first deposit on screen 5?

You don't call anything. A deposit to the account creates a payin, and your webhook handler credits the ledger.

1. Receive the webhook and verify the signature (svix-id, svix-timestamp, svix-signature), as the [Svix verification guide](https://docs.svix.com/receiving/verifying-payloads/how) and BlindPay's [webhook verification docs](/docs/learn/webhooks-verification) describe.
2. Store the event id. Webhooks can arrive twice.
3. On payin.new, show a pending line in the history.
4. On payin.complete, move the amount to available, keyed on the payin id so a duplicate event can't credit twice.

The ledger side, pending versus available and how to post the entries, is in [neobank deposit to balance](/resources/more/neobank-deposit-to-balance-ledger).

## How do screens 6 to 9 send the first payout?

Save a bank account, quote, confirm, execute, then track.

- **Save the recipient.** Each rail has its own fields: ACH needs routing and account numbers plus address and compliance fields, Pix needs a pix_key (CPF, CNPJ, phone, email, or random key). A customer can pay a third party's account. US users can link a bank through [Plaid Auth](https://plaid.com/docs/auth/) on instances where BlindPay enables it.
- **Quote.** request_amount is in cents (1000 means 10.00). currency_type says whether the amount is what the sender sends or what the recipient gets. cover_fees decides who pays the fee.
- **Confirm.** Show the receiver amount, the fees, and a countdown. The quote expires in about 5 minutes.
- **Execute.** Call payouts/evm with the quote_id and the sender wallet. A BlindPay-managed wallet needs no signing. A customer-controlled EVM wallet approves the quoted amount on-chain first.
- **Track.** payout.new fires when the stablecoins are collected, payout.complete when the payout finishes, fails, or is refunded.

Send an Idempotency-Key header on the payout call, following the pattern in the [IETF Idempotency-Key draft](https://datatracker.ietf.org/doc/draft-ietf-httpapi-idempotency-key-header/) and [BlindPay's idempotency guide](/docs/learn/idempotency). A retry after a timeout then can't create a second payout.

## How do you test the whole loop?

On a development instance, with magic amounts.

| Test | How | Expected |
| --- | --- | --- |
| Happy deposit | Create a payin quote and payin, wait about 30 seconds | payin.complete, balance credited once |
| Happy payout | Any amount other than the two below | payout.complete |
| Failed payout | request_amount 66600 | Status failed, error screen shown |
| Refunded payout | request_amount 77700 | Status refunded, funds back in the wallet |
| Expired quote | Wait past the expiry, then execute | Error, app re-quotes and asks again |
| Duplicate webhook | Replay an event | Ledger unchanged |

Development instances use USDB, BlindPay's test stablecoin, on testnets. Real routing and real KYC start in production; [sandbox vs production](/docs/learn/sandbox-vs-production) lists what changes.

## What goes on the go-live checklist?

- Production instance onboarded (KYB done in the dashboard) and a production API key in secrets.
- Webhook endpoint registered for production, signature checks on.
- Every money-moving call sends an Idempotency-Key.
- Re-quote path tested for expired quotes.
- Failed and refunded payout screens written and checked with support.
- Customer limits and limit increase flow understood ([limit increases](/docs/learn/limit-increase)).
- One real deposit and one real payout run by an employee before the first user.

## Which errors trip teams up first?

| Symptom | Likely cause | Fix |
| --- | --- | --- |
| Customer stuck in verifying | KYC Enhanced or KYB, manual review | Show the expected wait; don't create duplicates |
| Virtual account create rejected | Customer not approved yet, or wallet belongs to another customer | Wait for approved; use the same customer's wallet |
| Account details empty | Account not approved yet | Wait for virtualAccount.complete |
| Payout execute fails | Quote expired | Re-quote and re-confirm |
| Amount off by 100x | Sent dollars instead of cents | Convert to integer cents |
| Balance credited twice | Webhook replay | Key ledger entries on the payin id |

## How BlindPay fits

BlindPay gives a neobank the money movement under these nine screens through one REST API: terms and KYC/KYB, US virtual accounts in the customer's own name, payins, bank accounts, quotes that lock rate and fees, and payouts over ACH, wire, RTP, Pix, SPEI, ACH Colombia, Transfers 3.0, SEPA, Faster Payments, and SWIFT (POBO/COBO). Webhooks are signed with Svix headers. Official SDKs cover Node.js, Python, Go, PHP, and Swift, and the OpenAPI 3.1 spec covers everything else. Operators get a dashboard and a native [iOS app](/ios).

For payouts from customer-controlled wallets, BlindPay is non-custodial: you authorize the exact amount and BlindPay executes that one transaction. Plans are on the [pricing page](/pricing), and per-transaction fees appear in each quote before you execute.

## What to do next

Create a development instance, then run screens 1 to 9 in order with the [payout quickstart](/docs/quickstart-payout) open next to your app. When the 66600 and 77700 tests pass, your error screens are real.
