Collect in local currency and credit the account principal asset
Approved identity is required before account-authenticated collection. A
person account needs approved KYC and a company account needs approved KYB
before it can create a payin. This applies to POST /v1/payins (QR, card,
checkout, bank_transfer and fintoc), POST /v1/payins/collect/otp,
POST /v1/payins/collect, and POST /v1/payins/deposit-accounts, as well as
stored-card charges and POST /v1/subscriptions. Until approval, these
requests return HTTP 403 verification_required.
This gate applies to authenticated account creation. Public payment pages for
links that already exist remain payable, passive deposits already received
remain processable, org-admin assignment remains operational, and
system-created deposit instruments are not blocked.
A payin is a fiat collection: your customer pays in local currency and your account is credited directly in USD cents. When another asset is configured, the net follows
the post-credit conversion flow at your payin rate (payin_rate in
GET /v1/rates) minus the fixed payin fee. In USDT v1, payin_rate_source tells you whether
that rate is backed by enabled payin FIFO inventory (lot) or spot (spot).
The rate is fixed before the asynchronous payment is credited.Whatever the mode, every path ends the same way — automatic credit +
webhook:
delivery describes how the payment is confirmed on CBPay’s side (bank
notification, polling or both) — it changes nothing in your integration:
you always receive the payin_credited webhook.Collection corridors and modes:
Country
Currency
Modes
Chile
CLP
Hosted payment page (fintoc), announced bank transfer
Peru
PEN
Announced bank transfer
Mexico
MXN
Dedicated CLABE account, announced bank transfer
Bolivia
BOB / USD
Dedicated BOB account (bank_transfer), collection QR, card payment page (card)
Paraguay
PYG
Announced bank transfer
Brazil
BRL
Dynamic PIX QR (qr), hosted PIX redirect (pix)
Argentina
ARS
Dedicated CVU account
Venezuela
VES
Active collection (c2p)
United States
USD
International card payment page (card), announced bank transfer (two rails: domestic wire + international SWIFT)
Ecuador
USD
Announced bank transfer
Venezuela (VES): active collection is available through c2p. The payer
generates a claveDinamica in their bank app and gives it to your
integration; send that value in the otp field of the collect request. There
is no preceding /collect/otp request for C2P. debito_inmediato is not a
live corridor. Outgoing payments to Venezuela (pago_movil,
bank_transfer) are unchanged: see payouts.
Availability may vary; the catalog (GET /v1/payins/methods) is always the
source of truth. For a USD-principal account, the net credit is written directly in USD cents. Configured non-USD targets use
the documented post-credit conversion. The quoted payin_rate and fixed fee still
apply.
Payin lots are consumed FIFO when the credit is applied. If enabled inventory
does not cover the credit, the existing quote remains authoritative and the
uncovered portion is recorded as spot fallback with an operator alert.
If you’d rather keep your collections in another balance (USDC, BTC or
GOLD), configure default_payin_asset — see
the money model.
Each country has its own collection mode. The real request and response of
each one:
Chile
Peru
Mexico
Bolivia
Paraguay
Brazil
Argentina
United States
Hosted payment page (fintoc) — recommended: you get a payment_url;
the payer opens it and transfers from any Chilean bank or wallet (Banco
Estado, Santander, Mach, Tenpo, Mercado Pago…). The payment is
detected and validated automatically — no manual references.
{ "payin_id": "7a2b…", "status": "pending", "reference": "7a2b…", "payment_url": "https://pay.fintoc.com/plink_K2zwNNSxPyx8w3GZ", "expires_at": "2026-07-08T18:48:25Z", "note": "share the payment_url with the payer; the deposit is credited automatically once the transfer is detected"}
Share the payment_url with the payer (link, redirect or WebView). Once
the payment is confirmed your account is credited in USD and you receive
the payin_credited webhook. The CLP amount must be an integer (the
Chilean peso has no decimals) and the payment session expires after 24
hours by default. A retry with the same idempotency_key returns the same
payin and the same URL — it never opens a second payment session.For method: "card", idempotency_key is required. Send it either in
the JSON body or as the Idempotency-Key header. The platform records the
operation before contacting the core: if the response is ambiguous, retry
with the same key so the original charge can be reconciled. The platform
never creates a second core charge for that retry; do not generate a new key
to recover the same payment.Announced bank transfer (manual alternative): announce the incoming
deposit and share the reference with the sender.
{ "payin_id": "4f81…", "status": "pending", "reference": "CBJ6T3W9M2K5", "note": "include the reference in the transfer description so the deposit is credited automatically"}
When the transfer arrives it is matched by the reference in the transfer
description and your account is credited automatically. If the reference
does not travel, the payer’s document backs it up — see
matching an announced transfer.
Announced bank transfer, same as Chile but in soles:
{ "payin_id": "6d20…", "status": "pending", "reference": "CBK7M2Q9X4T3", "note": "include the reference in the transfer description so the deposit is credited automatically"}
The reference is a short 12-character alphanumeric code (it fits any
bank concept field) and must travel in the transfer description for the
automatic match. Send payer_document as a backup —
how matching works.
Dedicated CLABE account (recommended): create a fixed CLABE bound to
your account — every SPEI arriving to it is credited automatically, no
references needed:
instrument is the CLABE you share with your payers. Creation is free;
each deposit pays the regular payin fee. The optional details.alias,
details.bank_name and details.merchant_nit fields are returned when the
receiving rail provides them (merchant_nit is used by BO/BOB accounts).
List your accounts with GET /v1/payins/deposit-accounts.
fondeo is the default: incoming fiat is converted to your account’s
principal asset (USD is the principal balance) and
credited to the CBPay balance.
banking keeps the incoming amount in its local fiat balance so it can be
used for fiat payouts. It is currently available only for
BO/BOB/bank_transfer, MX/MXN/bank_transfer, and
AR/ARS/bank_transfer.
Create a Banking destination by sending the purpose explicitly:
The response and GET /v1/payins/deposit-accounts include purpose.
The slot is deterministic per account, corridor, and purpose: replaying the
same idempotency key returns the original resource with
idempotency_hit: true; it never creates a second destination.fiat_hold_local is a default fallback for deposits that arrive without a
bound instrument. It does not replace the explicit purpose on a destination.
Company accounts and multiple deposit destinations. A company account can
create multiple immutable deposit accounts in enabled additional corridors.
The current additional corridors are MX/MXN/bank_transfer (CLABE), BO/BOB/bank_transfer (BOB receiving account), and AR/ARS/bank_transfer (CVU). Every destination remains
bound to the same CBPay account; incoming transfers are credited by the
destination instrument. Person accounts keep one deposit account per
corridor, and the same one-per-corridor rule applies to other or existing
corridors.Funding-account provisioning starts only after the account’s own verification
is approved: KYC for a person or KYB for a company. Registration does not
create a deposit account. For AR/ARS bank transfers, the platform requires the caller-chosen bank_alias; it is separate from the internal account label and the verified-name-derived profile alias.For a company CLABE, send an idempotency key in the body or header:
A new key returns 201 and a new destination for the selected corridor.
Replaying the same key returns
200 with the original instrument and idempotency_hit: true; a
concurrent in-flight replay can return 409 idempotency_conflict.
Creation is free; subsequent payins keep the normal payin fee. The BOB
destination is receive-only and is reconciled push-first with a polling
backstop; it does not create or select a payout routing source account. List
destinations with GET /v1/payins/deposit-accounts?page=1&page_size=50.You can also use a one-off announced bank transfer
(POST /v1/payins with method: "bank_transfer", country: "MX").
If a deposit-account request timed out after the core had started provisioning,
do not create a new key blindly. Query the durable claim with the core route
below using the same account-scoped credential:
GET /v1/payins/deposit-accounts/idempotency?idempotency_key=<key>
The response is 200 with status: pending, not_found, or a completed
claim whose recovered destination has status: active and
idempotency_status: completed. pending means the original operation is
still unresolved. A missing query key returns 400 invalid_payload. The
platform reconciliation action replays the original key and never originates
a second external destination:
POST /v1/org/payins/deposit-accounts/{instrumentID}/reconcile
It requires an org-admin credential with ops:write. If the claim is not an
MX/MXN/bank-transfer instrument, recovery is rejected with
422 deposit_account_not_recoverable. A concurrent recovery returns
409 idempotency_conflict. The platform-admin adoption endpoint is a
separate last-resort path, not an idempotent replay: if the local instrument
is no longer pending and recoverable it returns the same 422; use
reconcile for a verified replay of the original fence.
Dedicated BOB receiving account: for the fixed-account flow, use the
Funding guide. A person
account receives one stable instrument for the corridor; a company can
create additional BOB instruments with distinct idempotency keys. The payer
transfers BOB to the selected number and the credit is confirmed push-first,
with statement polling as the backstop. No announcement or payer reference is required.Collection QR (the local interoperable standard): you generate the QR
and your customer scans it with their banking app. See the dedicated
QR payins guide for the request, response, retry and
error contract.Card payment page (card): you receive a payment_url for a hosted
3-D Secure checkout — the payer enters their card on a secure page branded
with your organization’s identity and, when their bank requires it,
completes the authentication challenge right there. Card data never touches
your system or your integration.
{ "payin_id": "b41c…", "status": "pending", "reference": "b41c…", "payment_url": "https://api.qbank.cl/pay/cards/9f3XkT…", "expires_at": "2026-07-16T18:30:00Z", "note": "share the payment_url with the payer; the balance is credited automatically once the card payment is approved"}
Share the payment_url (link, redirect or WebView). Flow details:
customer is an optional prefill of the billing details (email,
first_name, last_name, phone_number, address1, locality,
administrative_area, postal_code, country — plain text, max 120 chars
per field); the
payer can complete/correct them on the page. administrative_area is
the billing state/region: an ISO 3166-2 code (US-CA) or its suffix
(CA).
Billing address — state/region required by country: the hosted page
collects the full billing address (name, email, street, country, city,
state/region and postal code). The state/region field is mandatory
whenever the billing country has ISO 3166-2 subdivisions (for example
the United States, Canada, Mexico, Brazil or Chile): the payer picks it
from a dropdown fed by the address catalog, and the page does not submit
the payment without it. If the country has no subdivisions in the
catalog, the field is hidden. The value travels as administrative_area
(ISO 3166-2). The billing address is validated before the charge is
sent for authorization: if it is incomplete, or the state/region is
missing for a country that requires it, the payment is rejected with
invalid_payload and no authorization is created on the card.
A card authorization created with an incomplete billing address can be
declined later at capture by the issuer. Always collect the full billing
address — including the state/region when the country has subdivisions —
so the payment can be captured.
success_url / failure_url (optional, public https) redirect the payer
when done; without them the page shows the final result.
expires_at (optional, RFC3339, at least 5 minutes ahead) sets the
session lifetime; the default is 60 minutes and the maximum is
48 hours. The API validates this value before creating the session
and includes the effective RFC3339 expiry in successful card responses.
If it expires unpaid, the payin moves to expired and you receive the
payin_expired webhook.
The payer has a limited number of attempts; an issuer decline lets them
retry with another card within the same session.
Stable card failure classification. The hosted card page receives a
machine-readable failure_code when a payment attempt fails and translates it
to a payer message in English, Spanish or Chinese. The stable values are
declined, authentication_failed, provider_unavailable, invalid_data,
card_unavailable, expired, needs_review and unknown. The historical
failure_reason text remains available to the hosted page for compatibility;
integrations should branch on the stable code, never on free text. A
needs_review result is ambiguous: do not submit a new payment or create a
new idempotency key while reconciliation is pending.
Approval is online: once the charge is approved your account is credited
in USDT at your payin_rate and you receive payin_credited — same as
every other mode.
A retry with the same idempotency_key returns the same payin and the
same payment_url; it never opens a second payment session.
Open card-session reuse: when stored_card_id is absent, a new
method: "card" request reuses an open pending or challenge session
for the same account, country, currency, amount and normalized
payer_reference. If payer_reference is absent, customer.email is the
fallback identity. The response is 200 with session_reused: true, the
existing payment_url and its expires_at; no second charge or hosted
session is created. Without a reliable payer reference or email, the
request creates a new session.
An account may have at most 10,000 open card sessions. At the cap, only
unused pending sessions with zero attempts are eligible for eviction. If
the cap cannot be relieved, the API returns 429 too_many_open_card_sessions; this is an account limit, not a provider
retry signal.
The hosted page requests the provider capture context lazily when the payer
focuses or clicks the card-number field, not when the page first loads. A
payer who abandons the page therefore does not consume a provider context.
If the payer already saved cards with you, the page offers them on its
own: they type their email (first field), verify it with a code and pay
with one of them without re-typing it — with “Remember this device” they
skip the code for 30 days. Details in
stored cards.
It also works in USD (currency: "USD").
Settlement delay: when your payin_card fee is configured with
settlement_hours above zero, an approved charge confirms the payin as
credited immediately — the payin_credited webhook fires and the
checkout closes as paid — but the balance only becomes available at
settle_at (RFC 3339, in the create/GET/list responses together with
settlement_pending: true), or earlier if your org admin releases it
manually; once released, the payin carries settled_at. The
payin_settlement_scheduled webhook fires exactly once at confirmation
with status: "credited" and the scheduled amounts. The same delay
applies when a checkout link or a
POS charge is paid by card. Details in
fees — card payin settlement delay.
Announced bank transfer in guaraníes: you announce the deposit, your
payer transfers (interbank SIPAP or an internal transfer at the receiving
bank) with the reference in the transfer concept, and the credit is
detected automatically.
{ "payin_id": "8f41…", "status": "pending", "reference": "CBW4N8R2T6P9", "note": "include the reference in the transfer description so the deposit is credited automatically"}
Guaraníes use no decimals: announce the exact integer amount your
payer will transfer (e.g. "596000"). The reference is a short
12-character alphanumeric code — designed for the SIPAP concept field,
which accepts at most 20 characters and no special characters — and
putting it in the concept ensures the automatic match. Send
payer_document as a backup — see
matching an announced transfer.
Dynamic PIX QR: the qr method generates a PIX QR with the amount
embedded.
The response contains charge.qr_payload, the PIX “copia e cola” code,
which the payer can paste into their banking app instead of scanning the
image (charge.qr_image base64 or charge.qr_image_url, the public CDN
URL). The QR expires according to expires_in (default 1 hour); the payment
is credited automatically after confirmation and reconciliation.
The qr method is one payment per dynamic QR with the exact amount embedded.
Hosted PIX redirect: create a pix payin and redirect the payer to the
returned payment_url. The payer name and document are required, and the
minimum amount is 1 BRL.
The response contains payment_url and starts as pending. Redirect the
payer to the hosted page; the account is credited after signed confirmation.
Use the same idempotency key to recover the original charge.
Brazil supports both qr (dynamic PIX QR) and pix (hosted redirect).
The hosted pix amount is BRL with up to two decimal places and the minimum
is 1 BRL.
Dedicated CVU account: create a fixed CVU bound to your account.
For AR/ARS/bank_transfer, the platform requires the bank-addressable
bank_alias you want the Argentine banking system to use:
The alias must be 6–20 characters from [A-Za-z0-9.-] and contain at least
one letter. The bank decides whether it is available; 422 alias_taken is
actionable, so choose another alias. The response includes the CVU and, when
accepted, details.bank_alias; details.alias_error explains why a requested
alias was not stored.To set or rename the alias on an existing AR/ARS deposit account:
The response returns instrument_id, account_number, bank_alias and
updated. A repeated value returns updated: false. invalid_alias is
400; an unsupported rail is 422 alias_unsupported; a provisioning slot
still in flight is 409 deposit_account_not_recoverable.
International card payment page (card): charge in US dollars with
Visa, Mastercard, American Express, Discover and Diners cards issued
anywhere. You get a payment_url for a hosted checkout with 3-D Secure
branded with your organization; card data is typed into the processor’s
secure fields embedded in that page and never touches your system or your
integration.
{ "payin_id": "3ab7…", "status": "pending", "reference": "3ab7…", "payment_url": "https://api.qbank.cl/pay/cards/Kt9XmQ…", "expires_at": "2026-07-26T18:30:00Z", "note": "share the payment_url with the payer; the balance is credited automatically once the card payment is approved"}
The contract is the same as the Bolivian card page (optional customer,
success_url/failure_url, expires_at, limited attempts, open-session
reuse by payer_reference or customer.email, lazy capture-context loading,
and an idempotent retry returns the same payment_url). What is specific to the international
corridor:
3-D Secure runs inside the page: if the issuer asks for a challenge, the
payer completes it right there without leaving the checkout.
Most charges are approved online; if the issuer leaves the charge under
verification, the credit lands as soon as the rail confirms it — you still
get payin_credited, just a few minutes later.
save_card: true plus payer_reference store the card with the payer’s
consent for later charges (see
stored cards and subscriptions).
If the payer already has saved cards, the page offers them after they
verify their email with a code (once per device when they check
“Remember this device”, valid for 30 days) — they pay with 3-D Secure
without re-typing the card.
The international card corridor is enabled per account. Check
GET /v1/payins/methods — it is the source of truth for what your account
can collect today.
Announced bank transfer (bank_transfer) — two rails: domestic wire and international SWIFT: collect dollars
from any US bank account, with the same announced-transfer contract as the
other countries. The US/USD corridor publishes two deposit instructions
on purpose — a domestic rail (ABA routing number) for senders banking inside
the US, and an international rail (SWIFT/BIC through a correspondent bank)
for senders wiring from abroad. Announce the deposit once and the response
carries both blocks — deposit_instructions (domestic) and
deposit_instructions_swift (international) — each with its own copy-paste
QR, so your payer picks the rail their bank supports:
{ "payin_id": "8f4e…", "status": "pending", "reference": "CBM4X8Q2T7K9", "note": "include the reference in the transfer description so the deposit is credited automatically", "payer_source": "declared", "payer_name": "Acme Holdings LLC", "deposit_instructions": { "bank_name": "Partner Bank, N.A.", "account_number": "000123456789", "account_type": "checking", "holder_name": "CBPay Operations LLC", "holder_tax_id": "88-1234567", "routing_number": "021000021", "holder_address": "25 SW 9th Street, Suite 406, Miami, FL 33130, US", "reference_required": true, "qr_payload": "Bank: Partner Bank, N.A.\nAccount type: checking\nAccount number: 000123456789\nRouting number (ABA): 021000021\nHolder: CBPay Operations LLC\nHolder address: 25 SW 9th Street, Suite 406, Miami, FL 33130, US\nTax ID: 88-1234567\nAmount: 1250.00 USD\nReference: CBM4X8Q2T7K9", "qr_png_base64": "iVBORw0KGgoAAAANSUhEUgAA…" }}
For payers banking inside the US: a domestic wire (or ACH) with the
routing_number (ABA). This rail has no SWIFT code — domestic US transfers
do not need one.
{ "payin_id": "8f4e…", "status": "pending", "reference": "CBM4X8Q2T7K9", "note": "include the reference in the transfer description so the deposit is credited automatically", "payer_source": "declared", "payer_name": "Acme Holdings LLC", "deposit_instructions_swift": { "bank_name": "Partner Bank International", "account_number": "9870001234", "account_type": "checking", "holder_name": "CBPay Operations LLC", "holder_tax_id": "88-1234567", "swift": "PRTBPRI3", "bank_address": "200 Example Blvd, San Juan, PR 00901, PR", "intermediary_bank_name": "Intermediary Bank N.A.", "intermediary_bank_swift": "INTRUS33", "holder_address": "25 SW 9th Street, Suite 406, Miami, FL 33130, US", "notes": "Select Puerto Rico as the final beneficiary bank country", "reference_required": true, "qr_payload": "Bank: Partner Bank International\nAccount type: checking\nAccount number: 9870001234\nSWIFT: PRTBPRI3\nBank address: 200 Example Blvd, San Juan, PR 00901, PR\nIntermediary bank: Intermediary Bank N.A.\nIntermediary SWIFT: INTRUS33\nHolder: CBPay Operations LLC\nHolder address: 25 SW 9th Street, Suite 406, Miami, FL 33130, US\nTax ID: 88-1234567\nAmount: 1250.00 USD\nReference: CBM4X8Q2T7K9\nNote: Select Puerto Rico as the final beneficiary bank country", "qr_png_base64": "iVBORw0KGgoAAAANSUhEUgAA…" }}
For payers wiring from outside the US: an international SWIFT transfer
with the swift (BIC) and the correspondent bank
(intermediary_bank_name / intermediary_bank_swift). The notes field
carries the operational hints the sending bank needs to fill its form
correctly (here: which country to select for the final beneficiary bank) —
show it to the payer verbatim.
The payer copies the reference (CB…) into the transfer memo /
remittance field of whichever rail they use: it is the signal that matches
the deposit to your announcement (see
matching an announced transfer).
holder_address is the postal address of the account holder — US banks ask
for it in their wire form, and the QR of each rail includes it as a
“Holder address” line whenever it has a value (same for notes as a “Note”
line). intermediary_bank_name / intermediary_bank_swift appear only on
the rail that receives wires through a correspondent bank — show them to the
payer exactly as they come; a wire that needs them and travels without them
can bounce or arrive short.US deposit instructions are mandatory on this corridor: if your
organization has not configured them yet, the announcement responds
422 deposit_instructions_unavailable and nothing is created (see
common errors). You can preview both destination accounts
without announcing with
GET /v1/payins/deposit-instructions?country=US¤cy=USD&method=bank_transfer.
The EC/USD/bank_transfer corridor supports announced payins and
provider-agnostic manual payouts. Follow the dedicated guide for the bank
confirmation endpoints, evidence requirements, states, and idempotent retries:
Manual Ecuador USD operations
Announce or confirm a USD bank transfer, or confirm a payout paid manually by
an authorized operator.
For VE/VES/c2p, the payer first generates a claveDinamica in their bank
app. Your integration sends that value as otp; it is not an OTP returned by
CBPay and there is no request-OTP step:
The request uses the existing collect contract. payer_document,
payer_phone, payer_bank and otp are the payer data needed by the C2P
rail; otp carries the bank-generated dynamic key. The response keeps the
existing payin shape and, on synchronous success, includes paid: true,
provider_reference and the credited payin identifiers.
`POST /v1/payins/collect/otp` is only for a live pull method that explicitlyissues an OTP before the charge. It is not part of the `c2p` flow. Calling itfor C2P returns the public `422 provider_rejected` response; do not interpretthat response as a failed C2P charge, and do not replace the C2P request with`debito_inmediato`.
The platform screens the payer before calling the core. A hold returns
202 in_review without charging; retry the same request after approval. If
the account is not approved, the collect request returns
403 verification_required before the corridor is executed. A timeout or 5xx
does not authorize a new operation: keep the same idempotency context and
follow the resource/reconciliation state. Always check
GET /v1/payins/methods because corridor availability can change.
An announced transfer (method: "bank_transfer") has no payment session:
the payer moves the money from their own bank, so the deposit is recognized
when it lands. Matching runs in this order and stops at the first hit:
reference — the 12-character code in the transfer description.
Payer document — the announcement’s payer_document against the
payer the bank reports (dots, dashes and check digit are ignored).
Single candidate — exactly one pending announcement for that amount
and currency.
If none of the three resolves to one announcement — two pending
announcements for the same amount, no reference, no payer document — the
deposit is not credited on a guess: it lands as unassigned and your
CBPay operator routes it. No money is lost; it is already in the collection
account.
{ "payin_id": "4f81…", "status": "pending", "reference": "CBJ6T3W9M2K5", "note": "include the reference in the transfer description so the deposit is credited automatically", "payer_source": "declared", "payer_document": "17438319-7"}
payer_source always comes back so your checkout knows what to ask the
payer for:
Value
Meaning
declared
You sent payer data — the document backs up the reference
account_identity
No payer sent: the verified tax ID of your account is used (the holder deposits to themselves)
none
No identity available — insist on the reference, it is the only strong signal left
A document shorter than 5 characters or with no digits is dropped as a
signal (it cannot be told apart from an amount or a bank code). The
announcement is still created and payer_source reports the real coverage.
The announcement accepts idempotency_key (body) or the Idempotency-Key
header. A retry with the same key returns the original announcement —
same reference — with idempotency_hit: true and HTTP 200 instead of
creating a second one.
{ "payin_id": "4f81…", "status": "pending", "reference": "CBJ6T3W9M2K5", "note": "include the reference in the transfer description so the deposit is credited automatically", "payer_source": "declared", "payer_document": "17438319-7", "idempotency_hit": true}
Two live announcements that look identical (same account, currency, amount
and payer) are exactly the case matching refuses to resolve: the real
deposit matches both and lands unassigned. That is why a POST without
a key reuses a live identical announcement instead of duplicating it (also
200 with idempotency_hit: true).To collect two real payments of the same amount from the same payer,
send a different idempotency_key for each one — each key creates its own
announcement with its own reference.
Keys are unique per account and per logical operation: if you reuse an
idempotency_key that was already used with ANOTHER payin method (QR,
checkout, card), the API replies 409 idempotency_conflict instead of
returning an object that does not match your request.
On corridors where your organization registered a destination account for
announced transfers (today Chile, Paraguay and the United States), the
announcement response includes a deposit_instructions block — the exact
bank account the payer must transfer to, with the amount and reference
already baked into a copy-paste QR:
Response 201 (with deposit instructions)
{ "payin_id": "4f81…", "status": "pending", "reference": "CBJ6T3W9M2K5", "note": "include the reference in the transfer description so the deposit is credited automatically", "payer_source": "declared", "payer_document": "17438319-7", "deposit_instructions": { "bank_name": "Banco Ejemplo", "account_number": "001122334455", "account_type": "checking", "holder_name": "CBPay Operations SpA", "holder_tax_id": "77123456-7", "reference_required": true, "qr_payload": "Bank: Banco Ejemplo\nAccount type: checking\nAccount number: 001122334455\nHolder: CBPay Operations SpA\nTax ID: 77123456-7\nAmount: 500000 CLP\nReference: CBJ6T3W9M2K5", "qr_png_base64": "iVBORw0KGgoAAAANSUhEUgAA…" }}
You can also preview the destination account before creating a payin —
useful to show the payer where they will need to send money once they
confirm:
On the US/USD corridor the preview returns two blocks: the domestic
rail under deposit_instructions and, when your organization has the
international variant configured, the SWIFT rail under
deposit_instructions_swift (same shape, its own QR). The rail fields are
absent (not empty) on corridors that do not use them:
Field
What it is
routing_number
ABA routing number of the destination bank — domestic rail (deposit_instructions)
swift
SWIFT/BIC of the destination bank — international rail (deposit_instructions_swift)
bank_address
Registered address of the destination bank
intermediary_bank_name
Correspondent bank, when wires arrive through one
intermediary_bank_swift
SWIFT/BIC of the correspondent bank
holder_address
Postal address of the account holder (US wire forms ask for it)
notes
Free operational note for the sending bank (e.g. which country to select for the final beneficiary bank)
{ "deposit_instructions": { "bank_name": "Partner Bank, N.A.", "account_number": "000123456789", "account_type": "checking", "holder_name": "CBPay Operations LLC", "holder_tax_id": "88-1234567", "routing_number": "021000021", "holder_address": "25 SW 9th Street, Suite 406, Miami, FL 33130, US", "reference_required": true, "qr_payload": "Bank: Partner Bank, N.A.\nAccount type: checking\nAccount number: 000123456789\nRouting number (ABA): 021000021\nHolder: CBPay Operations LLC\nHolder address: 25 SW 9th Street, Suite 406, Miami, FL 33130, US\nTax ID: 88-1234567", "qr_png_base64": "iVBORw0KGgoAAAANSUhEUgAA…" }, "deposit_instructions_swift": { "bank_name": "Partner Bank International", "account_number": "9870001234", "account_type": "checking", "holder_name": "CBPay Operations LLC", "holder_tax_id": "88-1234567", "swift": "PRTBPRI3", "bank_address": "200 Example Blvd, San Juan, PR 00901, PR", "intermediary_bank_name": "Intermediary Bank N.A.", "intermediary_bank_swift": "INTRUS33", "holder_address": "25 SW 9th Street, Suite 406, Miami, FL 33130, US", "notes": "Select Puerto Rico as the final beneficiary bank country", "reference_required": true, "qr_payload": "Bank: Partner Bank International\nAccount type: checking\nAccount number: 9870001234\nSWIFT: PRTBPRI3\nBank address: 200 Example Blvd, San Juan, PR 00901, PR\nIntermediary bank: Intermediary Bank N.A.\nIntermediary SWIFT: INTRUS33\nHolder: CBPay Operations LLC\nHolder address: 25 SW 9th Street, Suite 406, Miami, FL 33130, US\nTax ID: 88-1234567\nNote: Select Puerto Rico as the final beneficiary bank country", "qr_png_base64": "iVBORw0KGgoAAAANSUhEUgAA…" }}
The preview endpoint’s qr_payload has no Amount/Reference lines (there
is no payin yet); the one embedded in an actual announcement always has
both, so the payer can pay without typing anything by hand. Both blocks on
the announcement are a frozen snapshot: if your CBPay operator later
updates a registered account, live announcements keep pointing at the
account they were created with — only new ones pick up the change.
The exact same deposit_instructions (and deposit_instructions_swift,
when present) blocks are echoed back on
GET /v1/payins/{id} and on the list (GET /v1/payins), so your front end
does not need to cache it from the creation response. On corridors without a
registered destination account, the field is simply absent — fall back to
showing the reference and asking the payer to use their usual bank details
for your organization.
Pushes credited to a dedicated deposit account expose instrument_id, an instrument block and the bank-reported payer block — full shape in the Funding guide. Filter the history with ?instrument_id=<uuid>.
fx_rate is your payin_rate at credit time — the conversion happens at
exactly that rate: usdt_gross = 700.00 / 6.91. When present,
rate_source is the quote provenance (lot or spot); historical payins
without a loaded quote omit it.The payin object keeps the full detail:
Deposit received without an automatic match (routed by the administrator)
expired
The charge expired unpaid
failed
The collection failed
A deposit that cannot be resolved to a single announcement stays
unassigned until the CBPay team routes it to an account (see
matching an announced transfer). Once
assigned, it is credited with the destination account’s rate and fees, and
the announcement it belonged to is closed.
When an active charge (QR or checkout) dies unpaid, the payin moves from
pending to expired (or failed) automatically and you receive the
payin_expired webhook. No funds move: to retry the
collection, create a new payin.
# One payincurl https://api.qbank.cl/platform/v1/payins/9c2a… \ -H "Authorization: Bearer <token>"# History with filters: dates, status, country, method, kind, refund status, searchcurl "https://api.qbank.cl/platform/v1/payins?from=2026-07-01&to=2026-07-08&status=credited&country=BO&method=qr&kind=qr&refund_status=none&search=9c2a&page_size=50" \ -H "Authorization: Bearer <token>"
from/to use YYYY-MM-DD (organization timezone); an invalid date responds
400 invalid_range. method and kind narrow the rail and payin shape,
refund_status accepts none, partial or full, and search performs a
case-insensitive partial match on the payin ID, reference or idempotency key.
Check method (qr, bank_transfer, fintoc, pix, card; collect has its own endpoint)
400
idempotency_key_required
Collect requires an idempotency key (real debit against the payer)
403
service_disabled
Payins is not enabled for your account — see services
429
too_many_open_card_sessions
The account has 10,000 open card sessions and no unused, zero-attempt pending session can be evicted — complete or let an existing session expire before creating another
503
card_reuse_unavailable
The platform could not verify an existing open card payment — retry the same card request with the same idempotency key; do not create a second charge
503
checkout_recovery_pending
The checkout payment option is being reconciled — retry the same materialization request and do not create another option
422
core_rejected
The processor rejected the charge; check the message
422
deposit_instructions_unavailable
bank_transfer on a corridor that requires a registered destination account (today CL, PY, US) and your organization has not configured one yet — contact your CBPay operator
502
core_unavailable
The charge could not be created; retry the creation (nothing was charged)
GET /v1/payins/deposit-instructions responds 404 not_found when the
corridor has no active destination account configured — treat it the same
way as the 422 above: the payer cannot be shown an account yet.
Subscribe to payin_credited: it carries the FX rate applied, the fee and
the exact usdt_credited. You can also poll GET /v1/payins/{id}.
Which FX rate applies to my payin?
The quoted payin_rate returned by GET /v1/rates and carried by the payin
at credit time. Check payin_rate_source: lot means enabled USDT payin
inventory backed the quote, while spot means the spot path was used. Your
agreed spread is already inside the rate — it is never itemized.
Can payins land in a balance other than USDT?
Yes — set default_payin_asset with PUT /v1/settlement. Accounts credit directly in USD cents; another configured target uses the post-credit conversion flow and
conversion_status reports done or pending_retry.
What happens when a charge (QR, checkout) expires unpaid?
You receive payin_expired and the payin closes without moving money.
Create a new charge — nothing was debited or credited.
The payer transferred a different amount than announced — what now?
The reference still matches the announcement, but the amount that arrived is
what gets credited. A transfer that resolves to no announcement stays
unassigned for reconciliation; your CBPay team can assign it to the right
payin manually.
Two clients announced the same amount and neither sent the reference — who gets the money?
Nobody by chance. If the payer document does not tell them apart, both
announcements stay pending and the deposit lands as unassigned for the
operator to route. Sending payer_document in the announcement is what
turns this case into an automatic credit.
Do I have to send payer_document now?
No — it is optional and nothing breaks without it. When you omit it, the
verified tax ID of the account is used (payer_source: account_identity),
which covers self-deposits. Send it whenever a third party pays for your
client, and always show the reference to the payer.
I retried the announcement POST — did I create two announcements?
No. With idempotency_key (body or Idempotency-Key header) the retry
returns the original announcement with idempotency_hit: true. Even without
a key, a POST that is identical to a live announcement (same account,
currency, amount and payer) reuses it — duplicating it would leave the real
deposit unassigned for ambiguity. Send different keys only when you really
want to collect twice.
How does my US client pay — domestic wire or international SWIFT?
The corridor publishes two destination accounts on purpose, and your payer
picks the rail their bank supports: senders banking inside the US use
the domestic rail (deposit_instructions) with the routing_number (ABA) —
a Fedwire or ACH; senders outside the US use the international rail
(deposit_instructions_swift) with the swift (BIC), the correspondent
bank (intermediary_bank_name / intermediary_bank_swift) and the notes
hints for the wire form. Whatever the rail, the reference (CB…) in the
memo / remittance field is what credits the deposit automatically. A wire is
usually reported the same business day; an ACH can take one to three
business days depending on the sending bank — the credit and the
payin_credited webhook happen the moment the bank reports the deposit.
Why is the bank QR just text to copy, not something my banking app scans?
Banks don’t share a common QR standard for arbitrary destination accounts
(unlike a merchant QR at checkout) — every bank encodes account transfers
differently, and most banking apps can’t auto-fill a transfer from a
third-party QR at all. qr_png_base64 renders the account details as a QR
purely as a copy shortcut on mobile: the payer scans it, gets the
multi-line text (bank, account, holder, amount, reference), and pastes it
into their own bank’s transfer form — they still confirm the transfer
themselves. Don’t build a scan-and-pay flow around it; show it next to the
plain-text fields so the payer can always type them manually.
For card payins, the settlement_hours setting controls when the balance becomes available after the payment is confirmed. It accepts 0 or a multiple of 24: 0 makes the balance available immediately, while 24 is one US business day and 48 is two US business days. Business days are Monday through Friday excluding observed US federal holidays, evaluated in your organization’s timezone. For example, Friday at 15:00 plus 48 hours settles Tuesday at 15:00 when no holiday intervenes; Saturday plus 48 hours also settles Tuesday. A value such as 27 is rejected with HTTP 400 invalid_settlement_hours. The payment is confirmed as credited immediately; only balance availability waits for settle_at. Existing settle_at timestamps and earlier non-multiple configurations retain calendar-hour semantics.
Set fiat_hold_local: true with PATCH /v1/accounts/{accountID} to retain
future BOB, MXN or ARS credits in their local balance instead of converting
them to USDT. Existing credited payins are never rewritten.
credit_asset is always present; fiat_credited is present only for local
fiat. Cards always settle in USD. Auto-conversions, checkout swaps and OTC
offers skip locally held payins.
refunded_amount uses the units of credit_asset (micro-USDT for USDT,
minor fiat units for BOB/MXN/ARS). usdt_credited is always micro-USDT.
Branch on credit_asset; never calculate usdt_credited - refunded_amount
when the credit is fiat.
Accounts use USD as the default for settlement_asset and
payin_settlement_asset; change it any time with PUT /v1/settlement.
In-flight operations are never re-quoted.USD ledger amounts use cents (two decimal places). Direct credits and debits,
fees, and supported payout, checkout, POS, and card paths use USD only where
their asset field accepts it. Swaps between USD and USDT use 1:1 pricing and
do not use an FX oracle. BOB, MXN, and ARS remain separate fiat-ledger
assets; same-currency payouts debit those balances directly (see
payouts).A payin credited directly to a USD-principal account exposes
credit_asset: USD and the credited fiat amount in cents; usdt_credited
remains the USD-equivalent reporting field. A controversy hold follows the
credited asset: USD credits use hold_asset: USD. disputed and held use hold-asset units;
disputed_usdt and held_usdt are normalized equivalents.