CleonPay

Crypto payments

Accept stablecoins; settle to your own wallet.

Accept USDC on Ethereum and Base and USDT on TRON. Same API keys, same webhook engine, same idempotency as the rest of CleonPay. Deposits settle to a wallet you register — you never hold custody in your integration.

Enable it first. Crypto is off until an operator switches it on for your account; calls return 403 crypto_not_enabled until then.

Amounts are the token amount, not fiat

The amount is the number of tokens to collect: "25.00" means 25 USDC/USDT. Stablecoins are USD-pegged and CleonPay does no FX, so read one token as one US dollar. There is no minimum or maximum.

Create a payment

POST /v1/crypto/payments
FieldTypeNotes
chainstringRequired. ethereum, base or tron.
assetstringRequired. USDC (ethereum, base) or USDT (tron).
amountstringRequired. Token amount, e.g. "25.00".
order_referencestringYour order id. Searchable.
hold_minutesintegerHow long the address is held (1–1440; default 30).
metadataobjectYour own key/values, returned untouched on every read.

Send an Idempotency-Key header so a retried create returns the same payment and deposit address rather than reserving a new one. See Idempotency.

curl https://api.cleonpay.com/v1/crypto/payments \
  -H "Authorization: Bearer $CLEONPAY_KEY" \
  -H "Idempotency-Key: order-8891" \
  -H "Content-Type: application/json" \
  -d '{ "chain": "base", "asset": "USDC", "amount": "25.00", "order_reference": "order-8891" }'
{
  "id": "9b1c0f2e-…",
  "object": "crypto_payment",
  "status": "pending",
  "chain": "base",
  "asset": "USDC",
  "network": "Base network",
  "deposit_address": "0xea28…8836",
  "amount": "25.00",
  "amount_base": "25000000",
  "order_reference": "order-8891",
  "expires_at": "2026-09-21T12: 30: 00.000Z",
  "created_at": "2026-09-21T12: 00: 00.000Z",
  "qr": "data:image/png;base64,…",
  "checkout_url": "https://pay.cleonpay.com/crypto/checkout/9b1c0f2e-…"
}

Show the customer the deposit_address and amount, or send them to checkout_url — a hosted page with the QR, the exact amount and live confirmation status.

Let the payer choose the network

POST /v1/crypto/payment_sessions

Same body as a payment but without chain/asset. Returns deposit_options — one payment (its own id, checkout_url and webhooks) per supported network, sharing your order_reference. Show them all, or a picker; whichever address is paid fires that payment's crypto.payment.received.

Reading a payment

GET /v1/crypto/payments/{id}

Adds received_amount, confirmations, confirmations_required and tx_hash (the on-chain deposit) as they appear.

GET /v1/crypto/payments

Lists your crypto payments newest first. Accepts status, reference, limit and created_before for keyset pagination — the same shape as GET /v1/payments.

POST /v1/crypto/payments/{id}/cancel

Cancels a still-pending payment the customer never funded. Once a deposit is seen it can no longer be cancelled.

Balances and settlements

GET /v1/crypto/balances

Your available balance per network (net of fees).

GET /v1/crypto/settlements

The payouts we've sent to your wallet — net, fee, gross, to_address, tx_hash, status — keyset-paged like the payments list.

Testing in the sandbox

With a test key, drive a payment through to paid without sending real testnet coin:

POST /v1/crypto/payments/{id}/simulate

Runs the exact ingest path a real deposit would — the status transition, ledger credit and crypto.payment.received webhook are all real. Pass { "amount": "10.00" } (less than due) to test underpaid instead. Live keys are rejected.

curl -X POST https://api.cleonpay.com/v1/crypto/payments/9b1c…/simulate \
  -H "Authorization: Bearer $CLEONPAY_TEST_KEY"

Statuses

StatusMeaning
pendingWaiting for a deposit
detected / confirmingDeposit seen, gaining confirmations
paidConfirmed in full. Fulfil here.
underpaidConfirmed, but less than due
heldHeld for review — the sender failed sanctions screening
swept / settledCollected to treasury / paid out to your wallet
expiredNo deposit before the window closed

Sanctions screening

Every deposit's source address is screened against sanctions lists before it's credited. A hit puts the payment in held — never credited, swept or settled — and fires crypto.payment.held. Treat held like a decline: do not fulfil. This runs on every deposit, however the payment was created.

Webhooks

Listen for crypto.payment.received (fulfil here), crypto.payment.underpaid and crypto.payment.expired; and crypto.settlement.completed / crypto.settlement.failed when we pay your balance out to your wallet. Their data carries crypto_payment_id (or crypto_settlement_id), not payment_id. See Webhooks.

Settlement

Received deposits settle to the wallet you register per network on the Crypto Processing tab, net of fees. CleonPay handles every on-chain movement.