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:
1. Discover the available corridors
The available countries, currencies and collection modes are defined by CBPay. Always check the catalog: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:
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.
2. Pick the mode and create the charge
Each country has its own collection mode. The real request and response of each one:- Chile
- Peru
- Mexico
- Venezuela
- Bolivia
- Paraguay
- Brazil
Hosted payment page (Response Share the Response When the transfer arrives it is matched by the reference in the transfer
description (or by amount+currency as a fallback) and your account is
credited automatically.
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.201: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.201:Universal checkout link (checkout)
Create a universal checkout link: a single POST /v1/payins with
method: "checkout" returns a branded public URL where the payer chooses
how to pay. The charge is denominated in the virtual balance you
choose (settlement_asset: USDT, USDC, BTC or GOLD, default
USDT) and every payment is converted automatically to that balance
when it credits — unless the payer pays in the same asset, in which case
there is no conversion.
The page organizes the payment into four tabs:
- CBPay — direct payment with the app: the merchant’s alias and QR; scanning with the app pays instantly through an internal transfer, in any of the 4 balances.
- Crypto — the available coins grouped by network (today USDT on TRON and Ethereum, USDC on Ethereum and BTC; new networks show up on their own once enabled), each with a deposit address exclusive to that charge and a scannable QR compatible with external wallets (Trust Wallet, MetaMask, Binance and similar apps).
- Fiat — the payer picks their country among every country with a live payin corridor and sees the available methods (QR, bank transfer, hosted payment) with the local amount quoted on the spot.
- Card — credit or debit card payment on a secure hosted page, listed by charge currency (today BOB and USD; currencies from future acquirers show up on their own). Each currency is an independent payment option with its own quoted amount.
amountis denominated in thesettlement_asset:"50"withUSDTmeans 50 USDT;"0.001"withBTCmeans 0.001 BTC;"2"withGOLDmeans 2 grams of gold. Do not sendcurrency— that is the old contract and responds400(the charge is no longer tied to a local currency).countryis optional and only preselects the country on the page; the payer can change it.GOLDhas no payment rail of its own: the charge is always reached through automatic conversion from whatever the customer pays with.
201:
checkout_url (link, email, WhatsApp, printed QR). The page
requires no login, carries your organization’s branding and updates by
itself: once the payment is confirmed through any method it shows “paid”
and redirects to your success_url if you set one.
How each rail pays
- Multi-country fiat: the payer picks a country and a method; the
local amount is quoted on the spot (target → USD → local currency using
your corridor’s
payin_rate, rounded up) and is frozen when the method is chosen. The credit arrives with the normal payin conversion and fees, and is then converted to thesettlement_asset. An announced payment SMALLER than the frozen quote still credits (the money is real) but does not mark the link as paid. When a country offers the same method in several currencies (e.g. Bolivia with QR in BOB and in USD), the page lists each currency as an independent option. On bank transfers in Mexico the link issues a dedicated CLABE exclusive to that charge: the payer transfers the exact amount with no reference needed — the deposit is detected and settled automatically because the account itself identifies the link. If a dedicated account cannot be issued at that moment, the page degrades to the classic path (the merchant’s general account plus a mandatory reference in the transfer description). - Pull collections (Venezuela):
c2panddebito_inmediatocharge the payer’s account directly. The page asks for bank, document, phone (C2P) or account (immediate debit) and the OTP — generated in their banking app for C2P, or sent on demand for immediate debit (“Request key” button). The amount is ALWAYS the one frozen at quote time; if the rail confirms synchronously the link is paid instantly. A rejection does not kill the link: the payer fixes the data or picks another method. - Card (multi-currency): the tab lists every available charge currency with its quoted amount; picking one opens the hosted payment page in that currency. Each currency is an independent materialization (you can quote BOB and USD on the same link; the first one to complete pays it).
- Crypto (wallet per charge): choosing a currency generates an
exclusive address with its
qr_payloadandqr_png_base64— the QR always carries the raw address (BTC bech32, TRON base58, ETH hex) for maximum wallet and exchange compatibility (Binance and similar apps reject BIP-21/EIP-681 URIs); the exact amount is shown next to it with a copy button. If the paid asset differs from thesettlement_asset, the quoted due already includes the conversion (the payer covers it; you receive your exact target). Partial payments accumulate and the page shows what’s missing. Quotes involving BTC/GOLD refresh every 15 minutes. - CBPay app: the merchant QR embeds the link
(
cbpay:pay?to=…&checkout=…). The app pays through an internal transfer in any of the 4 balances: same asset ⇒ exact target; different ⇒ due with the conversion included. The amount is validated server-side against a fresh quote — if it does not cover the charge it responds422 checkout_amount_mismatchwith the current due. Integrators:POST /v1/transfersaccepts the optionalcheckout_tokenfield (or the extended QR into_qr_token); the destination is forced to the link’s account.
Automatic conversion to the chosen balance
Every credit in an asset different from thesettlement_asset is
converted with your account’s conversion engine (same spreads and limits
as POST /v1/swaps). The aggregate state travels in conversion_status:
Public link endpoints (no auth, rate-limited)
GET {checkout_url}/state— link state:status,paid_method,settlement_asset,asset_amount, frozen fiat materializations (fiat_methods), crypto progress (cryptowithdue/received) andconversion_status.GET {checkout_url}/quote— quotes BEFORE choosing:countries(catalog per country; each country lists its corridors inoptions[]— one row per method+currency, withcollect: trueon pull methods),cards(card options per country and currency with theirlocal_amount),crypto(indicative due per pair) andcbpay(alias + dues per asset). With?country=XXit addscountry_quotewith that country’s local amount per option.POST {checkout_url}/methods/{method}— materializes the chosen option. Fiat methods require?country=XX; when the country offers the method in more than one currency (cards, QR BOB/USD in Bolivia) it also requires¤cy=YYY; crypto usescrypto:<chain>:<asset>(e.g.crypto:tron:usdt) without a country. Pull methods return the payer form (banks[],requires_otp_request) with the frozen quote. Re-POSTing the same combination returns the SAME materialization.POST {checkout_url}/collect/otp— requests the OTP of a pull collection when the rail sends it on demand (requires_otp_request: true, e.g. VE immediate debit). Returns theotp_referencethat accompanies the final charge. Strictly rate limited (each call is a real SMS/push).POST {checkout_url}/collect— runs the pull charge with the payer’s data (bank, document, phone or account, OTP). The amount is always the frozen one; if the rail confirms synchronously it respondspaid: trueand the link is settled in the same call.
Link rules
- One link = one charge: the first method that completes the payment wins; a later payment through another rail is not credited (picking a method does NOT lock the others while nobody has paid).
expires_inaccepts 600 to 604800 seconds (10 minutes to 7 days; default 24 hours). If it expires unpaid the payin flips toexpiredand you receive thepayin_expiredwebhook.- A retry with the same
idempotency_keyreturns the same link (the URL never changes); a second charge is never opened. - The
settlement_assetmust be enabled for your organization; if it is disabled the creation responds422 settlement_asset_disabled.
payin_credited with
settled_via (e.g. crypto:tron:usdt, qr, cbpay),
settlement_asset and asset_amount; crypto payments add
crypto_amount and CBPay app payments add transfer_id, asset and
amount.
Link-specific errors (seen by whoever opens the page):
3. Receiving the credit
When the payment arrives (through any of the modes), your account is credited automatically and thepayin_credited webhook fires:
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:
Statuses
Deposits arriving by direct transfer without a clear reference stay
unassigned until the CBPay team routes them to an account. Once assigned,
they are credited with the destination account’s rate and fees.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.Reads and history
from/to use YYYY-MM-DD (UTC); an invalid date responds
400 invalid_range.