This does not block the public
/pay/{token} page or payment of a checkout
link that was created before the gate. The payer does not need a CBPay login.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: USD, USDT, USDC, BTC, GOLD,
SILVER or PLATINUM; when omitted it uses your payin_settlement_asset,
USD is the principal balance) 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. Local fiat (BOB, MXN, ARS) is rejected here.
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 10 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). - 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). On the payment page the payer can use a saved card: they type their email, verify it with a code and pick one — with “Remember this device” they skip the code for 30 days (see stored cards).
- Card link lifetime: card materialization requires at least 5 minutes
remaining on the checkout link. If less time remains, the public endpoint
returns
422 checkout_expiring_soon; ask the merchant for a new link before paying by card. - 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/SILVER/PLATINUM 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 6 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),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. Re-POSTing the same combination returns the SAME materialization.POST {checkout_url}/collect/otp/POST {checkout_url}/collect— the pull-collection endpoints. The option must be materialized first; the amount is always the frozen materialization amount and never comes from the payer.
VE/VES C2P option, the payer supplies the
bank-generated claveDinamica in the otp field of
POST {checkout_url}/collect. Do not call /collect/otp for C2P; that
endpoint is only for a method that issues an OTP before the charge.
Useful if you prefer to render your own payment page on top of the same
link.
Collect holds and safe retries
The platform screens the payer before calling the core/provider. A PEP or firewall hold returns202 with status: "in_review" and review_id; the
payer has not been charged. An admin approval only releases the review. Retry
the same collect request with the same idempotency_key to execute the
charge. Hosted checkout derives a stable internal key from the link and the
unchanged payer/charge data, so retry the identical request rather than
creating a new link.
If the core/provider call times out or returns 5xx, the payin remains
pending and is reconciled by the payin webhook. The platform never resends
the charge automatically. If another attempt has already decided the payin,
the endpoint returns 409 checkout_not_pending; read the link state instead
of creating a second link.
A payer who supplies only a document is screened as a separate identity:
document_value is sent as idNumber, while name remains empty. The
document is never copied into name, and document-only screening does not
qualify for the name-only PEP fast lane.
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.
A charge paid by card follows your organization’s card settlement
delay (the
settlement_hours of the payin_card fee): the payin confirms
as credited and the link closes as paid right away, but the balance
lands at settle_at — the payin carries settlement_pending: true until
then. Charges paid through any other method (QR, crypto, CBPay app,
fintoc) credit immediately, as always. See
fees — card payin settlement delay.GET /v1/payins and GET /v1/payins/{payin_id} checkout payins
always carry their denomination — settlement_asset + asset_amount —
in every status (pending, expired and credited); currency/local_amount
stay empty until a local payment method is used. A charge settled in
crypto or via the CBPay app exposes its usdt_credited without an
fx_rate (no FX quote applies).
Link-specific errors (seen by whoever opens the page):
Language
The hosted checkout page follows the public locale chain:?lang= / ?locale= (invalid values are ignored),
then the payer cookie cbpay_pay_locale, then the merchant account, then the org default_locale, then Accept-Language, then English.
JSON of the API stays in English. Details: Locale and language.
FAQ
Can the same link be paid twice?
Can the same link be paid twice?
No. One link = one charge: the first rail that pays wins (
already_paid,
409). A crypto deposit that arrives after the link was settled by another
rail is not credited — it is held for reconciliation.What if the payer sends less (or more) crypto than the quote?
What if the payer sends less (or more) crypto than the quote?
Partial crypto payments accumulate: the page shows how much is missing
until the quoted amount is covered. Late payments that arrive after the link
expired still credit your account.
How long does a link live?
How long does a link live?
expires_in between 600 s and 7 days (default 24 h). When it expires you
receive the payin_expired webhook and the public page answers
checkout_expired (410).What happens if the conversion to my settlement asset fails?
What happens if the conversion to my settlement asset fails?
The funds stay safe in the received asset and the payin reports
conversion_status: pending_retry; the platform retries automatically until
the swap succeeds — you never lose money nor get converted twice.Can the payer switch payment method after choosing one?
Can the payer switch payment method after choosing one?
Yes. Each method materializes independently; re-requesting the same method
returns the same materialization. Whichever rail pays first settles the
link.
Can I retry the link creation safely?
Can I retry the link creation safely?
Yes — retry
POST /v1/payins with the same idempotency_key and you
get the same link back. A new key creates a new, independent link.Card payin settlement timing
For card payins, thesettlement_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.
USD as the principal ledger asset
Accounts useUSD 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; v1 money-out pricing for those assets remains unavailable.
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.