Skip to main content
Banking gives you real bank accounts in the name of your verified profile: you receive funds over international rails (SEPA, SWIFT, ACH depending on the currency), hold fiat balances and send payments to third parties. It is a separate product from your CBPay balances: banking money lives in your bank accounts, not in your USDT or USD balance. For local fiat corridors, create a destination with purpose: "banking" to retain local fiat for payouts; default fondeo converts incoming fiat to your account’s principal asset (USD is the principal balance). Banking supports only BO/BOB/bank_transfer, MX/MXN/bank_transfer, and AR/ARS/bank_transfer.

Two kinds of local-fiat destinations

For local fiat corridors (BO/BOB, MX/MXN, AR/ARS), every payin destination carries a purpose that decides where inbound money lands: Create a second destination with purpose: "banking" to hold local fiat for payouts; destinations created without a purpose keep the historical fondeo behavior. Banking purpose is only supported in BO/BOB/bank_transfer, MX/MXN/bank_transfer and AR/ARS/bank_transfer.

Banking by currency

🇺🇸 USD — US dollar

Open a classic Banking customer account (ACH, Fedwire and SWIFT rails) with the profile → accounts → operations flow in this guide (open bank accounts). USD received through Banking is held for payouts (payouts); USD sent to your CBPay balances lands in your principal asset (USD is the principal balance).

🇪🇺 EUR — euro

Everything euro goes through a virtual IBAN: request a funding_usdt address to convert inbound euros to your principal asset, or a banking_eur address to hold BANK_EUR for SEPA payouts (funding direction and EUR banking). Sending euros to third parties is covered in SEPA Instant payouts.

🇧🇴 BOB — Bolivian boliviano

Receive bolivianos through deposit accounts with a purpose: fondeo (default) auto-converts the credit to your principal asset (USD is the principal balance); banking retains BOB for local payouts. See Funding for the funding flow.

🇲🇽 MXN — Mexican peso

Receive pesos through deposit accounts with a purpose: fondeo (default) auto-converts the credit to your principal asset (USD is the principal balance); banking retains MXN for local payouts (payouts).

🇦🇷 ARS — Argentine peso

Receive pesos through deposit accounts with a purpose: fondeo (default) auto-converts the credit to your principal asset (USD is the principal balance); banking retains ARS for local payouts (payouts).

Company multi-banking: operating accounts

Company accounts can create multiple purpose: "banking" deposit accounts in each enabled local corridor: BO/BOB, MX/MXN, and AR/ARS, always with method: "bank_transfer". Send a distinct Idempotency-Key for each destination; reusing it returns the same instrument and never provisions a second bank account. The soft operational limit is 50 banking instruments per company account and corridor (422 deposit_account_limit_reached). Person accounts retain one banking instrument per corridor.

Create and label an operating destination

Response 201:
label is a local nickname only and is optional; it is limited to 60 characters. Change or clear it with:

AR alias lifecycle

For a company AR/ARS destination, the CVU can be created without bank_alias. It is then operationally alias_pending: creation succeeds, but balance and movements return 409 alias_pending until the company sets the alias. Complete it with PUT /v1/payins/deposit-accounts/{instrumentID}/bank-alias. Person accounts keep the existing 400 missing_alias creation rule.

Balance and movements

Only purpose: "banking" instruments expose these operating views:
The balance response uses exact minor-unit strings:
funds_model is retained for BO and centralized for MX/AR. Movements are a paginated union of payins and payouts, with kind, id, asset, amount_minor, direction, status, optional local_amount, and created_at.
Banking fees come in two shapes. Standalone fixed fees (banking_customer, banking_account, banking_operation) are debited from your default settlement asset balance — USD is the principal balance — when each operation executes and refunded automatically if it fails. Transactional rail fees (banking_deposit, banking_transfer_ach, banking_transfer_swift, banking_transfer_wire, banking_transfer_sepa) are a percentage plus a fixed amount charged in the operation currency (your BANK_USD / BANK_EUR balance) — see rail fees. With a fee of 0 (the default) the service is free. The banking_fee and banking_fee_asset fields on each response show what was charged and in which currency.

The full flow

  1. Create your banking profile (POST /v1/banking/customer) — once.
  2. Upload verification documents and submit for review.
  3. Once approved, open accounts per currency.
  4. Register beneficiaries (counterparties) for third-party payments.
  5. Send payments: quote with prepare, execute with operations.
State changes arrive through the banking_customer_status_changed and banking_operation_status_changed webhooks (webhooks).

1. Create your banking profile

Once per account. If you omit type, name or email, they are filled from your CBPay account:
Response 201:
If your account already has a banking profile — 409 banking_customer_exists.
Durable idempotency. The platform derives a deterministic core claim for this account/profile. The first successful request returns 201. Repeating the same request returns 201 with idempotency_hit: true and never creates a second profile or charges the profile fee twice. A different payload for the same account returns 409 idempotency_conflict. If the upstream/core outcome is ambiguous, the API returns 503 banking_recovery_pending; do not retry with a new key. Operations must reconcile the durable claim first.
Application review. If your organization enabled banking application review, this request can answer 202 Accepted with {"status":"in_review","kind":"banking_application","review_id":"…"} instead of 201 — the profile is created only when compliance approves the review. The banking profile fee is charged when the application is held and refunded automatically if it is rejected. Track the result with the webhook txn_review_status_changed or in Transaction reviews.
Check the state at any time:
EUR uses direct vIBAN; generic onboarding is USD-only. Request {"purpose":"banking_eur"} from Operar EUR without a prior customer; approval creates the local linkage. Generic EUR returns 400 banking_eur_requires_viban.

2. Documents and verification

Upload each document as base64 (free):
Then submit the profile for review (free):
Profile states: draft → submitted → under_review → approved or rejected. The banking_customer_status_changed webhook notifies each change — for your own profile (customer_kind: self) and for the third parties you register (customer_kind: third_party, with their third_party_id):

3. Open bank accounts

With the profile approved, create the classic Banking customer account for USD only (ACH/Fedwire/SWIFT rails). EUR is not opened here; request a banking_eur virtual IBAN directly in the EUR virtual IBAN flow.
Response 201 — data carries the details to receive funds (account number/IBAN, routing, bank):
List your accounts, fetch the detail of a specific account, and check balances:
The detail endpoint (GET /v1/banking/accounts/{id}) returns the account LIVE — name, currency, status, and under data the requirements to receive funds (wire and local rails: bank, account number/IBAN, routing). Use it to show the deposit instructions of a specific account without walking the list:
source tells you where the detail came from: live (the bank answered in real time) or mirror (the bank could not serve the account at that moment and the last known snapshot is returned — deposit requirements remain available).
The list exposes only the accounts enabled for your operation according to the corridor configuration. A non-enabled account does not appear in the list and its by-id queries respond 404 not_found.
Limit for person accounts: a person account can hold at most 1 bank account. Attempting a second one returns 409 banking_account_limit. Company accounts have no limit.

Third-party users (companies only)

If your account is a company, besides your own accounts you can register third-party banking users — your end clients (persons or companies) — each with their own identity and verification and bank accounts in their name. No limit on third parties or accounts per third party.

Registering the third party

Registration requires the verification_id of an approved KYC/KYB verification of the third party — their single identity inside CBPay. The type comes from the verification kind (KYC ⇒ INDIVIDUAL, KYB ⇒ COMPANY), the data (name, email, address) auto-fills from the verified profile (whatever you send explicitly wins), and the already-validated documents are re-delivered automatically to the banking provider. The banking profile fee is charged (refunded if the registration fails):
Response 201:
documents_synced counts the verification documents that were loaded automatically into the third party’s banking profile. If one could not be synced (or the bank requests additional categories), upload it through the manual document flow below and then submit.
Application review. With banking application review enabled, registering a third party can also answer 202 Accepted (kind: banking_application): the third party is registered only when the review is approved, and the registration fee is refunded automatically on rejection. Track it via txn_review_status_changed or Transaction reviews.
Save the third_party_id: every third-party route uses it. List and fetch (the GET carries the live verification status):

Third-party verification (free)

Same as your own profile, but on the third party:

Third-party accounts

Once the third party is approved, open accounts for them (same banking_account fee) and operate just like your own:
  • Each third party belongs to you and only you: another CBPay account can never see or operate it (it gets 404).
  • A person account attempting to create third parties receives 403 company_required.
  • Without verification_id (or with a non-approved verification) the registration answers 422 verification_required / 422 verification_not_approved. If you send a type that does not match the verification kind, 422 verification_kind_mismatch. Third parties created before this rule keep operating normally.
  • Registered third parties feed the “new users” metric of your account summary.

4. Register beneficiaries

To pay third parties, first register the beneficiary with their banking details (free; it goes through moderation before it can be used):
List yours with GET /v1/banking/counterparties and attach more accounts to an existing beneficiary with POST /v1/banking/counterparties/{id}/accounts.

5. Send payments

Two operation types: Quote first (free, moves no money):
Execute with an idempotency key (the rail fee — or the default banking_operation fee when the rail has no configuration — is charged here):
Response 202:
banking_fee and banking_fee_asset only appear when a fee was charged. With a per-rail fee the asset is the operation currency (BANK_USD / BANK_EUR); with the default fallback it is your default settlement asset.
  • The final state arrives through the banking_operation_status_changed webhook (completed / failed); you can also poll GET /v1/banking/operations/{id}. Once the operation reaches a final state, the webhook includes its receipt_url and you can download the PDF receipt with GET /v1/banking/operations/{id}/receipt (receipts).
  • Retries with the same Idempotency-Key return the original operation (idempotency_hit: true) without charging the fee again.
Complete traceability. Every banking operation is recorded on your account: it shows up in the banking_operations section of the statement, its money reconciles in the BANK_USD/BANK_EUR mirror balances (assets section), and its volume adds to the gross_volume in analytics. The authoritative balance remains the bank’s: the mirror is reconciled periodically.
The full history, with filters:
Every banking operation — including inbound deposits and bank fees discovered automatically from the bank — exposes its direction (in / out), net amount, currency, counterparty and reference whenever the bank reports them. These fields are optional and appear in GET /v1/banking/operations and GET /v1/banking/operations/{id}.

Banking destinations for verified third parties

Company accounts can request a banking destination for an approved third-party KYC or KYB submission. The request is naturally idempotent per submission and corridor; approval triggers the destination automatically.
The create response is 201 for a new intent and 200 for a replay:
Statuses are pending, fulfilled, and abandoned. Approval creates the instrument and changes the intent to fulfilled; a rejected verification changes it to abandoned. A new request for an abandoned intent returns 409 intent_abandoned; an own-onboarding verification returns 422 verification_invalid. The banking_operation_status_changed webhook stays lightweight by design: it carries the identifiers and the new status only, never the enriched fields. When it fires, fetch the operation detail to read the direction, amount, counterparty and reference. See webhooks.

Euros: funding direction and EUR banking in one place

Everything euro lives in this section. A virtual IBAN is an addressing and routing instrument, not a separate provider balance, and comes in two purposes:

Request a virtual IBAN

The platform builds the provider registrant from the approved KYC/KYB profile. Request the default funding address with an idempotency key:
If a verified profile lacks a document issuing country or a document expiration date, an account request may send these optional top-level overrides (snake_case or camelCase aliases are accepted):
For company accounts the equivalent optional fields are incorporation_country / incorporationCountry and incorporation_date / incorporationDate. These values are used only to complete the verified registrant; they do not bypass KYC/KYB. The request is accepted into the manual approval queue — every allocation is manually approved by organization operations before the core calls the banking rail:
banking_eur is requested explicitly with the same call and {"purpose":"banking_eur"} as the body (same idempotency contract). List the account’s requests with the mandatory date window:
The list shape is {page, page_size, virtual_ibans}. Supported statuses are pending_approval, pending, active, rejected, failed, disabled and closed. Account-level responses show the full IBAN when one has been assigned; the organization-admin read surface masks it.
The banking corridor may require operations approval and a verified customer profile before a virtual IBAN can become active. Production host, certificate/mTLS, limits, commercial fees and activation details are configuration concerns and are not part of this provider-agnostic contract. Never infer them from examples.

Read, close and per-address operations

Read one request, close it, or list the operations attributed to it (from/to are required on the operations listing):

Virtual IBANs for third parties

Company accounts can request a banking_eur vIBAN for an owned third party after the third party has accepted the invitation and completed approved KYC/KYB. The third party is the balance owner; the company remains the relationship manager:
The resulting request is banking_eur and follows the same manual approval and status lifecycle as an account-owned request.

BANK_EUR balance

For a banking_eur request, query the reconciled balance explicitly:
The route returns 409 balance_not_available for funding_usdt, because that purpose converts to your principal asset through the payin credit chain instead of holding BANK_EUR.

Sending euros out

Both outbound paths resolve an allocated, active virtual IBAN as their source, each with its own purpose: Select the source with source_virtual_iban_id at the top level:
If the account has exactly one active virtual IBAN of the required purpose, the field may be omitted. No active source returns 422 funding_account_required; multiple active sources without an explicit UUID return 422 ambiguous_source_viban. A malformed source_virtual_iban_id returns 400 invalid_request; a valid UUID that is not found or does not belong to the account returns 404 not_found. A temporary source lookup failure returns 503 funding_account_unavailable. The source check runs before any debit or provider dispatch. The platform strips caller-supplied ordering-party fields and stamps payer server-side from the selected vIBAN’s registrant (unless a more-specific server-side company-wallet payer is present). Individual registrants provide first and last name; corporate registrants provide the registered company name and registration data. For active earlier rows without a usable registrant, the platform derives the ordering names from the current verified profile. If that profile cannot be read, the request returns 503 funding_account_unavailable; if the resulting identity is incomplete, it returns 422 registrant_incomplete. There is no per-vIBAN amount cap. source_virtual_iban_id is part of the operation intent: if the active source set changes between sequential retries, the same idempotency key returns the original operation. A concurrent in-flight request returns 409 operation_in_progress; retry with the same key. The key is the identity of the original operation — the core Banking replay does not compare payloads, so do not reuse a key for a materially different payload.
Operation holds created before this source gate are grandfathered: they continue through their existing dispatch and reconciliation path.

Receiving euros (inbound)

When the banking provider sends a terminal inbound event, the core normalizes it into banking_virtual_iban_inbound. The platform resolves the owning vIBAN and applies its purpose:
  • funding_usdt creates a banking_funding payin in EUR, applies the marked EUR→USDT rate and the configured funding fee/spread, then follows the normal payin firewall and credit chain.
  • banking_eur creates an inbound Banking mirror in BANK_EUR, uses the Banking ledger path, emits KYT/operation status and queues the banking receipt email.
Only terminal statuses (completed, success, settled, credited or succeeded) apply the financial effect. Pending or unknown statuses remain reconcilable and do not credit a balance; failed and rejected are terminal negative outcomes that never create a credit (an uncredited funding payin is marked failed without a refund loop). The account operation detail can expose payment_rail: "SEPA_INSTANT", purpose (funding_usdt or banking_eur) and the associated virtual IBAN reference when reported by the banking rail, preserving the product purpose through the mirror, statement and analytics.
Returns and recalls are handled as terminal returned, recalled or reversed events. A return before credit prevents a later completion replay from crediting. A credited funding_usdt payin creates the deterministic chargeback/refund path; a ledgered banking_eur operation receives an append-only reverse entry. A later completion event cannot resurrect a returned operation.

EUR errors

EUR FAQ

No. The purpose is recorded on the request, but a virtual IBAN is not an independent provider balance. Only banking_eur flows touch BANK_EUR; funding_usdt converts to your principal asset through the payin credit chain.

Rail fees (deposits and transfers)

On top of the standalone fixed fees, banking supports transactional fees per rail — a percentage plus a fixed amount, always charged in the operation currency (BANK_USD / BANK_EUR), never in USDT: For transfers the available balance must cover amount + fee — if it does not, the API answers 402 insufficient_funds and the operation is not created. If the operation is definitively rejected right after dispatch, the fee is refunded automatically (same discipline as the default fee). Fallback: if the rail has no specific configuration (neither at account nor at platform level), the default banking_operation fee (fixed, debited from your default settlement asset) applies. A rail configured with 0% + 0 fixed is explicitly free — it does not fall back to the default fee.

Operation statuses

Errors

The general error catalog lives in Errors.

FAQ

A funding_usdt inbound converts to your principal asset through the funding payin chain (USD is the principal balance). A banking_eur inbound is credited to the Banking EUR mirror after the terminal event and ledger processing. The provider remains authoritative for the bank balance. Per-rail fees are charged in the operation currency (your BANK_USD/BANK_EUR balance); only the default banking_operation fallback fee is debited from your default settlement asset balance.
It is refunded automatically — profile, account and operation fees alike, including per-rail fees (refunded on the definitive synchronous rejection). A retry with the same Idempotency-Key returns the original operation (idempotency_hit: true) and never charges twice.
One per currency (USD, EUR). Additionally, person accounts can hold at most 1 bank account in total (409 banking_account_limit); company accounts have no limit.
The list only exposes accounts enabled for your operation per the corridor configuration. A non-enabled account does not appear and its by-id queries answer 404 not_found — contact your CBPay team if you need it enabled.
No — third parties are a company feature (403 company_required). Registration also requires the verification_id of an approved KYC/KYB verification of the third party.
Subscribe to banking_operation_status_changed: it fires on completed / failed and includes the receipt_url once final. You can also poll GET /v1/banking/operations/{id}.

Banking creation claims and recovery

Creation claims are durable records, not permission to send a second provider request. The platform stores banking_creation_claims with kind, idempotency_key, request_hash, status, attempts and refund state. Migration 105 creates this table and its single-flight lock; migration 106 stores transaction-firewall fee-refund recovery. Migration 074 persists the resolved core provider on customer idempotency claims.

Recovery rules

  • A completed account recovery compares the provider account ownership with the claimed customer’s ProviderID.
  • A terminal provider authentication response (401 or 403) does not by itself prove that the creation failed; the claim remains recoverable.
  • Reusing the same idempotency key with a different request hash is rejected.
  • A completed claim can be replayed with HTTP 200 and idempotency_hit: true; recovery does not call the provider again.
  • POST /v1/org/banking/creation-claims/refund retries a pending fee refund for kind=customer or kind=account.
  • POST /v1/org/txn-firewall/fee-refunds/{resourceID}/retry retries the durable firewall-fee refund. A completed refund replays as HTTP 200.
The core admin route POST /v1/ops/banking/account-creations/reconcile verifies the provider account and finalizes the local mirror. The org-admin routes list claims, reconcile customer claims, and retry refunds. These operations are locked so concurrent reconciliation cannot originate duplicate money movement.
Last modified on October 16, 2026