Cripay Docs

How it works

The objects you work with, what every payment status means and what to do about it, test versus live, fees, settlement, partial payments, webhooks and idempotency, in plain words.

The objects

ObjectWhat it isWhere it lives
MerchantYour business: name, brand, fee terms, accepted networks, payout wallets. One account can own several.App → Profile · GET /v1/merchant
API keyA secret that identifies your merchant in code. cpr_test_… or cpr_live_…. Shown once.App → Developers · /v1/api-keys
PaymentA request to get paid: amount in USD, a checkout_url, a status, and once paid the coin, network and transaction. Ids look like pay_…./v1/payments
Payment linkA reusable page that creates a payment each time it is opened. Fixed or open amount, optional expiry and email. Ids look like plink_….App → Charge → Link · /v1/payment-links
Webhook endpointA URL on your server plus a signing secret (whsec_…). Cripay POSTs events to it.App → Developers → Webhooks · /v1/webhook-endpoints
Payout walletYour own address on a network family. Direct settlement sends money there.App → Profile → Payout wallets
BalanceOptional: money kept in a Cripay-managed wallet until you withdraw it.App → Withdraw · /v1/balances, /v1/payouts

A payment's life, in words

StatusPlain meaningFinal?What you do
pendingCreated; nobody has paid yet. The customer may be looking at the checkout.NoNothing. Show "waiting" if you display it.
processingThe transaction is on the network but not yet final (for example, waiting for confirmations).NoNothing yet. Do not ship.
confirmedPaid and final. The exact amounts and the transaction hash are on the payment.YesDeliver the product. This is the webhook to act on.
underpaidLess than the quoted amount arrived (exchanges often deduct a withdrawal fee). The checkout shows the customer what is missing; new transfers to the same address add up.NoWait; it becomes confirmed when the rest arrives, or expired.
overpaidMore than quoted arrived. You received it all.YesDeliver. Refund the difference yourself if you want to.
expiredThe price expired (30 minutes by default) and nothing arrived. No money moved. A transfer that arrives late, inside a 24-hour window, still confirms it.Yes (reopens on a late payment)Let the customer create a new payment.
failedThe on-chain transaction reverted or could not be processed.YesTreat as not paid.
cancelledYou cancelled it before anything arrived.Yes—
heldMoney arrived but the sender's address failed compliance screening. Funds are frozen pending review.NoDo not deliver until it becomes confirmed.

Every payment carries is_final. When it is true, nothing about that payment changes again.

Test mode and live mode

Everything exists twice. A test key only sees test networks (Sepolia, Solana devnet, Tron Nile, Bitcoin signet or a local chain) and payments created with it can be simulated with POST /v1/test/payments/{id}/simulate to any outcome (success, underpaid, overpaid, expired, failed). No wallet, no crypto, real webhooks. A live key sees real networks and real money. Test payments never appear under live keys and the other way around; the app has a Test / Live switch that does the same for the screens.

Build with test keys until your webhook handler is done and you have simulated every outcome. Then create a live key, add a payout wallet, and change only the key in your configuration.

Money and fees

  • Pricing. You price in USD. The crypto amount is quoted when the customer opens the checkout and is locked while the payment is open. Stablecoins are 1:1, so a 25 USD payment is 25 USDT or 25 USDC.
  • The Cripay fee is a percentage of each confirmed payment (0.5 % by default). You can absorb it (you receive 25 minus the fee) or pass it on (the customer pays 25 plus the fee). Profile → Fees & currency, or fee_payer on the merchant.
  • Network fees are paid by whoever sends the transaction, to the network, not to Cripay. They are cents on Solana, Tron, Base, Polygon, Arbitrum and BNB Chain and a few dollars on Ethereum mainnet. The checkout preselects the cheapest network for the chosen coin and shows an estimate for each.
  • Pay by transfer (QR / address) on some networks carries a small fixed component in the fee because the platform moves the funds out of the per-payment address; it is listed per network in GET /v1/currencies (transfer_fixed_fee_usd) together with a minimum amount. POST /v1/estimates shows the exact split before you create anything.
  • Each payment snapshots its fee terms. Changing your settings later never changes an existing payment.

Settlement: where the money goes

  • Direct (default). Each confirmed payment is sent to your payout wallet for that network family. Cripay never holds your money.
  • Balance (optional). Payments accrue in a Cripay-managed wallet and you withdraw to any address when you want (/v1/payouts, or Withdraw in the app). Useful when you do not want to manage wallets on several networks.

Switch between them under Profile → Fees & currency → Settlement, or settlement_mode on the merchant.

Two ways to pay, on purpose

The checkout shows both at the same level because customers come from two worlds:

  • Scan or transfer. For people who hold crypto on an exchange (Binance, Coinbase, OKX…) or in a mobile wallet. They see the exact amount, the address, a QR and a clear warning about the network. Works everywhere, including Tron and Bitcoin.
  • Connect a wallet. For people with MetaMask, Phantom or any WalletConnect wallet. One signed transaction for the exact amount through Cripay's settlement contract. Available on EVM networks and Solana.

You can restrict either per payment (allowed_payment_methods) or per merchant.

Webhooks: how you find out

Polling GET /v1/payments/{id} tells you the status, but your server should act on webhooks: Cripay POSTs a JSON event to your endpoint when a payment changes (payment.processing, payment.confirmed, payment.underpaid, payment.overpaid, payment.expired, payment.failed, payment.held, and payout.* for withdrawals).

Three rules keep this safe:

  1. Verify the signature before trusting anything. The header X-CryptoPay-Signature is an HMAC-SHA256 of timestamp.body with your endpoint's secret. The SDK does it in one call (constructEvent); Python and PHP samples exist.
  2. Be idempotent. Deliveries are retried (five attempts with growing delays) and can be replayed from the app, so the same event may arrive twice. Store event.id and ignore repeats.
  3. Answer fast with 2xx. Do the real work after responding. A non-2xx or a slow response is retried.

Every delivery, its response and its attempts are visible under Developers → Webhooks, with a button to send it again.

Idempotency keys: safe retries

When your server creates a payment and the network drops the response, you do not know whether the payment was created. Send an Idempotency-Key header (your order id is a good one): retrying with the same key returns the first result instead of creating a second payment. The SDK sends one automatically.

Errors, limits and logs

  • Every error is the same shape: { "error": { "type", "code", "message", "request_id" } }. The code is stable and documented per endpoint; the request_id lets you find the call under Developers → Request logs.
  • Rate limits are generous for normal use; a 429 carries Retry-After.
  • Lists are paginated with a cursor (has_more, next_cursor).
  • The full contract is the OpenAPI document; the reference and the TypeScript SDK are generated from it, so they never drift.

On this page