Banking fees come in two shapes. Standalone fixed fees
(
banking_customer, banking_account, banking_operation) are debited
from your USDT 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
- Create your banking profile (
POST /v1/banking/customer) — once. - Upload verification documents and submit for review.
- Once
approved, open accounts per currency. - Register beneficiaries (counterparties) for third-party payments.
- Send payments: quote with
prepare, execute withoperations.
banking_customer_status_changed and
banking_operation_status_changed webhooks (webhooks).
1. Create your banking profile
Once per account. If you omittype, name or email, they are filled
from your CBPay account:
201:
409 banking_customer_exists.
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.2. Documents and verification
Upload each document as base64 (free):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 profileapproved, create one account per currency. Available
currencies: USD (ACH/Fedwire/SWIFT rails) and EUR (SEPA/SWIFT):
201 — data carries the details to receive funds (account
number/IBAN, routing, bank):
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 theverification_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):
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.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 (samebanking_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 answers422 verification_required/422 verification_not_approved. If you send atypethat 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):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):
banking_operation fee when the rail has no configuration — is charged
here):
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 legacy fallback it is USDT.
- The final state arrives through the
banking_operation_status_changedwebhook (completed/failed); you can also pollGET /v1/banking/operations/{id}. Once the operation reaches a final state, the webhook includes itsreceipt_urland you can download the PDF receipt withGET /v1/banking/operations/{id}/receipt(receipts). - Retries with the same
Idempotency-Keyreturn 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.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_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.
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
legacy fee).
Fallback: if the rail has no specific configuration (neither at account
nor at platform level), the legacy banking_operation fee (fixed, in USDT)
applies. A rail configured with 0% + 0 fixed is explicitly free — it
does not fall back to the legacy fee.
Operation statuses
Errors
The general error catalog lives in Errors.
FAQ
Does banking money show up in my USDT balance?
Does banking money show up in my USDT balance?
No. Banking money lives in your bank accounts and is queried with
GET /v1/banking/accounts/{id}/balance. The authoritative balance is the
bank’s; your statement reconciles it in the
BANK_USD/BANK_EUR mirror balances. Per-rail fees are charged in the
operation currency (your BANK_USD/BANK_EUR balance); only the legacy
banking_operation fallback fee is debited from your USDT balance.What happens to the fee if an operation fails?
What happens to the fee if an operation fails?
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.How many bank accounts can I open?
How many bank accounts can I open?
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.Why doesn't one of my accounts appear in the list?
Why doesn't one of my accounts appear in the list?
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.Can a person account register third-party users?
Can a person account register third-party users?
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.How do I know when a payment reached its final state?
How do I know when a payment reached its final state?
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}.