Collect in local currency and get credited in USDT
A payin is a fiat collection: your customer pays in local currency and your
account gets credited in USDT automatically, converted at your payin
rate (payin_rate in GET /v1/rates) minus the fixed payin fee when
configured for your account.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
Venezuela
VES
Active collection c2p and debito_inmediato (pull)
Bolivia
BOB / USD
Collection QR, card payment page (card)
Paraguay
PYG
Announced bank transfer
Brazil
BRL
Dynamic PIX QR
Argentina
ARS
Dedicated CVU account
United States
USD
International card payment page (card), announced bank transfer (two rails: domestic wire + international SWIFT)
Availability may vary; the catalog (GET /v1/payins/methods) is always the
source of truth. In every case the credit works the same way: converted to
USDT at your current payin_rate and credited net of the fixed payin fee.
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
Venezuela
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 USDT 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.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. List your accounts with
GET /v1/payins/deposit-accounts.You can also use a one-off announced bank transfer
(POST /v1/payins with method: "bank_transfer", country: "MX").
Active collection (pull): you charge the payer directly with their
authorization. The result is synchronous — if the charge is approved,
the credit lands in the same call.For debito_inmediato, request the OTP first (free):
An active collection executes a real charge against the payer, so
idempotency_key is required (body or Idempotency-Key header): a
retry with the same key returns the original result with idempotency_hit
and never re-charges.
If the payer declines or the authorization fails, paid is false, the
payin is marked failed, and nothing is charged. The exact rejection
reason is persisted on the payin and exposed in the failure object (in
the synchronous response, in GET /v1/payins/{payin_id}, and on the
idempotent replay):
source tells you where the rejection originated (provider = the
payer’s bank declined; core = the pre-charge validation).
code and message carry the concrete reason (invalid or expired OTP,
wrong document, insufficient payer funds, etc.) so you can tell the
payer what to fix before retrying with a new idempotency key.
Collection QR (the local interoperable standard): you generate the QR
and your customer scans it with their banking app.
Display the QR to your customer — qr_image_url is a public CDN URL ready
for an <img> tag (prefer it over the base64 qr_image); when they pay,
your account is credited automatically. It also works in USD
(currency: "USD").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, address, city, country — plain text,
max 120 chars per field); the payer can complete/correct them on the
page.
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 15 minutes ahead) shortens the
session lifetime; the default is 24 hours. 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.
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.
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. 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 same endpoint generates a PIX QR with the amount
embedded.
In the response, charge.qr_payload is the PIX “copia e cola” code,
so the payer can paste it 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 per expires_in (default 1
hour); the payment is credited automatically once confirmed on the rail
(continuous reconciliation — check on demand with
GET /v1/payins/{charge_id}).
In Brazil collections work exclusively through dynamic PIX QR (one QR = one
payment, exact amount embedded). Announced bank transfers will come later.
Dedicated CVU account: create a fixed CVU bound to your account —
every ARS transfer arriving to it (from any CBU or CVU in the Argentine
system) is credited automatically, no references needed:
instrument is the 22-digit CVU you share with your payers. Creation is
free; every deposit pays the regular payin fee. List your accounts with
GET /v1/payins/deposit-accounts.
The CVU works in ARS only and is deposit-only (receive-only): no
third party can debit it. Direct debit attempts (DEBIN) against a deposit
CVU are rejected automatically.
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, 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.
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.
fx_rate is your payin_rate at credit time — the conversion happens at
exactly that rate: usdt_gross = 700.00 / 6.91.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.
Check method (qr, bank_transfer, fintoc, 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
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 payin_rate in force at credit time (see GET /v1/rates). 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. The credit still
enters in USDT and is converted right after at the real price;
conversion_status reports done or pending_retry (auto-retried).
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.
Why did my collect (pull) charge fail?
The response and GET /v1/payins/{id} persist a failure block with the
rail’s code and message (for example, a document that does not match the
payer’s bank registration). Fix the input and retry with a new key.