Skip to main content
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:

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:
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.
Response 201:
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.
Response 201:
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.
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.
  • amount is denominated in the settlement_asset: "50" with USDT means 50 USDT; "0.001" with BTC means 0.001 BTC; "2" with GOLD means 2 grams of gold. Do not send currency — that is the old contract and responds 400 (the charge is no longer tied to a local currency).
  • country is optional and only preselects the country on the page; the payer can change it.
  • GOLD has no payment rail of its own: the charge is always reached through automatic conversion from whatever the customer pays with.
Response 201:
Share the 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 the settlement_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): c2p and debito_inmediato charge 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_payload and qr_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 the settlement_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 responds 422 checkout_amount_mismatch with the current due. Integrators: POST /v1/transfers accepts the optional checkout_token field (or the extended QR in to_qr_token); the destination is forced to the link’s account.

Automatic conversion to the chosen balance

Every credit in an asset different from the settlement_asset is converted with your account’s conversion engine (same spreads and limits as POST /v1/swaps). The aggregate state travels in conversion_status:
  • GET {checkout_url}/state — link state: status, paid_method, settlement_asset, asset_amount, frozen fiat materializations (fiat_methods), crypto progress (crypto with due/received) and conversion_status.
  • GET {checkout_url}/quote — quotes BEFORE choosing: countries (catalog per country; each country lists its corridors in options[] — one row per method+currency, with collect: true on pull methods), cards (card options per country and currency with their local_amount), crypto (indicative due per pair) and cbpay (alias + dues per asset). With ?country=XX it adds country_quote with 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 &currency=YYY; crypto uses crypto:<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 the otp_reference that 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 responds paid: true and the link is settled in the same call.
Useful if you prefer to render your own payment page on top of the same link.
  • 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_in accepts 600 to 604800 seconds (10 minutes to 7 days; default 24 hours). If it expires unpaid the payin flips to expired and you receive the payin_expired webhook.
  • A retry with the same idempotency_key returns the same link (the URL never changes); a second charge is never opened.
  • The settlement_asset must be enabled for your organization; if it is disabled the creation responds 422 settlement_asset_disabled.
When the charge is paid you receive the 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 the payin_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.

Common errors

Last modified on July 17, 2026