QR Crypto POS is available to verified company accounts with the
pos service
enabled. Every charge uses an exclusive address (an ephemeral wallet
from the universal checkout link
engine): payments can never cross between charges.End-to-end flow
1. Register the merchant (once per client)
Every merchant is bound to an approved third-party KYC/KYB verification — the merchant identity comes from there, never hand-declared. You can set an informative commission (fee_percent +
fee_fixed): it moves no money, but the API computes it on every paid
charge and the summary tells you the net to distribute.
GET /v1/pos/merchants (paginated) and GET /v1/pos/merchants/{id}.
PATCH /v1/pos/merchants/{id} updates status (active/disabled — a
disabled merchant cannot generate new charges), the commission and the
external_ref. Identity is never edited: it comes from the verification.
2. Create the charge (one per sale)
The contract mirrors the universal checkout link: the amount is denominated in yoursettlement_asset (your account default when omitted) and the due
the customer pays is quoted in the QR’s crypto at charge-creation time —
when the customer pays in a different asset than yours, the conversion is
already included in the due (you receive your exact target).
crypto:tron:usdt,eth:usdt,eth:usdcorbtc:btc.expires_in: 300–86400 seconds (default 900). Quotes involving BTC are frozen for 15 minutes (quote_expires_at).idempotency_keyis required: retrying with the same key returns the SAME charge and the SAME address (idempotency_hit: true) — a POS retry can never open a second charge.- The QR is the raw address (compatible with Binance and every wallet);
print the
duenext to it.
3. Detect the payment (polling or webhook)
POS polling:GET /v1/pos/charges/{charge_id} every few seconds. As
soon as the deposit appears on-chain (2-3 s typical, BEFORE confirmations),
the response carries the early detection:
confirming is a UX signal (“payment detected, confirming…”) — the actual
credit only happens on on-chain confirmation: status flips to paid,
received reflects the accumulated total and paid_at is stamped.
Webhook (recommended to close the sale): payin_credited arrives with
the attribution block:
Charge states
Partial payments accumulate (
received) toward the target; paid only
when complete. Late payments: money arriving AFTER expiry is still
credited to your account (the address stays alive) and the webhook fires —
the expired charge’s received reflects it for reconciliation; a real
payment is never lost. Overpayments (tips) are credited too.
4. Reconcile and distribute per merchant
GET /v1/pos/charges?from=…&to=…&merchant_id=… lists each merchant’s
charges (merchant_id, status filters; standard pagination).
GET /v1/pos/summary?from=…&to=… returns the PER-MERCHANT aggregate — the
view for settling with each client:
gross is what was collected (the target of paid charges), processor_fee
is your commission configured on the merchant, and net_for_merchant is
what you owe them (same-asset refunds already deducted). Distribute over the
existing rails: transfers,
fiat payouts or crypto withdrawals.
5. Refunds
POST /v1/pos/charges/{charge_id}/refund returns (part of) what was
received to the payer as a regular crypto withdrawal from your balance —
with hold, withdrawal fee and every compliance control of the rail.
to_addressis always explicit. We never auto-refund to the deposit origin: it may be an exchange hot wallet your customer does not control (the money would be lost). Ask for and confirm the refund address; the receivedfrom_addressvalues stay in the charge detail as a reference.- Hard cap: the sum of a charge’s refunds can never exceed what it
received (
422 refund_exceeds_received), even under concurrent requests. - Available on any charge with received funds — including an expired charge that got a late payment (the most common refund case).
- The amount is refunded in the charge’s crypto: since the payment was
converted to your settlement asset, you need balance in that crypto
(swap back if missing; the error is
insufficient_funds). - The status follows the withdrawal lifecycle (
pending→processing→completed/failed; a failed withdrawal refunds your debit and releases the cap). Query withGET /v1/pos/charges/{id}/refunds.
Product errors
FAQ
How long does the POS wait for confirmation?
How long does the POS wait for confirmation?
Early detection (
confirming: true) appears within 2-3 seconds. Final
confirmation depends on the network: TRON ~1 minute, Ethereum a few minutes,
Bitcoin ~30 minutes. You decide whether to release the sale on confirming
(your risk) or wait for paid. For POS we recommend TRON/USDT.Can I print the QR on paper?
Can I print the QR on paper?
Yes —
qr_png_base64 is print-ready and qr_payload is the raw address in
case your POS renders the QR locally. Print the due and the network next
to it: the customer must send the exact amount over the right network.What if the customer pays less, more, or late?
What if the customer pays less, more, or late?
Less: the charge stays
pending, accumulating (received) toward the
target. More (tips): the excess is credited too. Late (after expiry): still
credited with its webhook — the expired charge’s detail shows the received
for reconciliation. A real payment is never lost.Is the money held in the restaurant's name?
Is the money held in the restaurant's name?
No — everything is credited to YOUR account (the processor), converted to
your settlement asset. The per-merchant attribution (charges, webhooks and
summary) tells you exactly how much belongs to each merchant so you can
distribute over the rail of your choice.
Which fees apply?
Which fees apply?
Every credited payment pays your account’s
funding fee (percent + fixed)
and, when the paid asset differs from your settlement asset, the automatic
conversion with its spread. The per-merchant commission
(fee_percent/fee_fixed) is yours with your client: informative, we never
charge it.Can a POS terminal query the status directly against CBPay?
Can a POS terminal query the status directly against CBPay?
In this version the POS talks to YOUR backend and your backend queries with
your API key (a physical terminal must never hold your key). If you need
backend-less POS terminals, tell us: a public read-only polling token per
charge is on the roadmap.