DEVELOPER DOCUMENTATION

Payment API v2

Create a payment from your server, send the customer to its payment page, and mark the order paid once Coino Live confirms the payment on chain.

Quick start

1. Create an API key for your gateway in the dashboard under Developers. 2. Create a payment with the order amount. 3. Redirect the customer to the returned checkout_url. 4. When the webhook arrives (or the customer comes back), read the payment again and ship once its status is "paid".

 curl https://coino.live/api/v2/payments \
  -H "Authorization: Bearer ck_live_..." \
  -H "Idempotency-Key: order-1001" \
  -H "Content-Type: application/json" \
  -d '{"amount": "25.00", "currency": "EUR", "order_id": "1001",
       "success_url": "https://shop.example/thanks",
       "notify_url": "https://shop.example/coino-webhook"}' 

Authentication

Send your secret key in the Authorization header of every request. Keys belong to one gateway and only see that gateway's payments. Keep them on your server: never put a key in a browser, an app or a public repository. If a key leaks, revoke it in the dashboard and create a new one.

Authorization: Bearer ck_live_...
Base URL: https://coino.live/api/v2

All amounts are decimal strings (never numbers), all times are ISO 8601 in UTC, and every answer is JSON with the result under "data".

POST · /payments

Create a payment

Prices can be in USD or in any currency listed by GET /currencies; other currencies are converted to USD at the current rate when the payment is created. The customer chooses the coin and network on the payment page.

FieldDescription
amountRequired. Up to 2 decimals, for example "25.00". A minimum of 1 USD (or its value) applies.
currencyThree-letter code, USD when left out.
order_idYour order number, up to 128 characters. Shown to you, searchable with GET /payments.
descriptionShown to the customer on the payment page, up to 500 characters.
customer_emailOptional customer email.
success_url, cancel_urlWhere the payment page sends the customer back to. A return to success_url is not proof of payment.
notify_urlWebhook URL for this payment (https only). Overrides the webhook URL in your gateway settings.
metadataYour own key/value data, up to 20 keys and 4 KB. Returned unchanged.
assetOptional coin code from GET /assets (for example "usdt-tron") to skip the coin choice.
expires_inMinutes the customer has to pay, 15 to 10080. Default 1440 (one day).
Idempotency-Key

Send a unique key per order attempt (up to 128 letters, digits and -_:.). Repeating a request with the same key returns the same payment instead of creating a second one, so a timeout or a double click is safe to retry. Use a new key when the amount changes or the last payment expired.

The answer (201 when created, 200 for a repeated key) is the payment object below.

The payment object

{
  "data": {
    "id": "01m62ewd502p9774d6trd7rhr7",
    "status": "paid",
    "amount": "22.32",
    "currency": "USD",
    "requested_amount": "20.00",
    "requested_currency": "EUR",
    "exchange_rate_usd": "1.115926958947",
    "received_usd": "22.32",
    "remaining_usd": "0.00",
    "order_id": "943",
    "description": "Order #943",
    "customer_email": "[email protected]",
    "metadata": {"cart": "abc"},
    "checkout_url": "https://coino.live/pay/01m62ewd502p9774d6trd7rhr7",
    "success_url": "https://shop.example/thanks",
    "cancel_url": "https://shop.example/cart",
    "asset": null,
    "quote": {
      "asset": "usdc-ethereum", "symbol": "USDC", "network": "Ethereum",
      "address": "0x8e2e…61bc", "amount": "22.330545",
      "network_fee": "0.000001", "rate_usd": "0.9995", "expires_at": "…"
    },
    "deposits": [{
      "asset": "usdc-ethereum", "symbol": "USDC", "network": "Ethereum",
      "tx_hash": "0xf13d…1439", "explorer_url": "https://etherscan.io/tx/0xf13d…1439",
      "amount": "22.330545", "network_fee": "0.000001",
      "platform_fee": "0.223305", "net": "22.107239",
      "usd_value": "22.32", "credited_at": "…"
    }],
    "expires_at": "…", "paid_at": "…", "created_at": "…"
  }
}

amount and currency are what the customer pays in USD; requested_amount and requested_currency are what you asked for. quote is the coin, address and amount currently shown to the customer; deposits lists every transaction received, with the fee taken and what was added to your balance (net).

StatusMeaningWhat to do
openWaiting for the customer to pay.Nothing yet.
confirmingA transaction was seen and waits for network confirmations.Show "payment received, confirming". Do not ship yet.
partially_paidLess than the amount arrived. The payment page asks for the rest (remaining_usd).Wait; the customer can still send the rest.
paidFully paid and confirmed. Final.Mark the order paid and ship.
expiredThe time ran out. If received_usd is above zero, that part is in your balance.Cancel the order, or settle the part paid with the customer.
cancelledCancelled by you before any payment arrived. Final.Nothing.

A payment within a small tolerance of the amount counts as paid, so rounding by wallets does not leave orders open.

GET · /payments/{id}

Read and cancel payments

Read a payment again whenever you need its current state: after a webhook, when the customer returns, or from a scheduled job. Unknown IDs and other gateways' payments answer 404.

GET · /payments?status=paid&order_id=1001&cursor=…

Lists your payments, newest first, 50 per page. Both filters are optional. meta.next_cursor and meta.prev_cursor page through the results.

POST · /payments/{id}/cancel

Cancels an open payment that has received nothing, for example when the customer empties the cart. Payments with a deposit cannot be cancelled (422).

Webhooks

Coino Live sends a POST to notify_url (or the gateway webhook URL) whenever a payment changes status. Webhooks are only sent when your gateway has a signing secret: create it in the gateway settings.

 POST /coino-webhook
X-Coino-Event: payment.paid
X-Coino-Delivery: 01m62f23xhkz39ahqv4h64ttfk
X-Coino-Signature: t=1761696953,v1=5f2c…

{"id": "01m62f23xhkz39ahqv4h64ttfk", "event": "payment.paid",
 "created_at": "…", "data": { …the payment object… }} 
EventWhen
payment.confirming, payment.partially_paid, payment.paid, payment.expired, payment.cancelledThe payment reached that status.
custody.deposit.detected, custody.deposit.creditedA custody user's deposit was seen, then credited.
withdrawal.<status>An API withdrawal changed status.

Check the signature: compute HMAC-SHA256 of "<t>.<raw body>" with your signing secret, compare it with v1 in constant time, and refuse timestamps older than five minutes.

// PHP
$header = $_SERVER['HTTP_X_COINO_SIGNATURE'] ?? '';
$body = file_get_contents('php://input');
parse_str(str_replace(',', '&', $header), $sig);
$ok = isset($sig['t'], $sig['v1'])
    && abs(time() - (int) $sig['t']) <= 300
    && hash_equals(hash_hmac('sha256', $sig['t'] . '.' . $body, $secret), $sig['v1']);

// Node.js
const [t, v1] = header.split(',').map(p => p.split('=')[1]);
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const ok = Math.abs(Date.now() / 1000 - t) <= 300
  && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));

# Python
t, v1 = (p.split('=', 1)[1] for p in header.split(','))
expected = hmac.new(secret.encode(), f'{t}.{raw_body}'.encode(), 'sha256').hexdigest()
ok = abs(time.time() - int(t)) <= 300 and hmac.compare_digest(expected, v1)
Treat a webhook as a trigger

Before you mark an order paid, read the payment with GET /payments/{id} and check its status, amount and your order_id. Answer with any 2xx status within 10 seconds. Failed deliveries are retried after 1, 5, 30, 120, 360, 720 and 1440 minutes; redirects are not followed. The same event can arrive more than once, so handle it idempotently.

Currencies, coins and balances

GET · /currencies

Currencies a payment can be priced in, each with its USD value: {"code": "EUR", "usd_per_unit": "1.09"}. Hide crypto payment when your shop currency is not listed.

GET · /assets

Coins customers can pay with right now: code (for example "usdt-tron"), symbol, name, network, decimals and price_usd.

GET · /balances

Your account balance per coin, after fees: asset, symbol, network, balance and usd_value (null while no price is available).

Custody accounts

For apps with user accounts: each user gets lifetime deposit addresses, and deposits are credited to that user's balance in your gateway.

RequestDescription
POST /custody/usersCreate a user (external_id required; name, email, metadata optional). The same external_id returns the existing user.
GET /custody/users?external_id=…List users, 50 per page.
GET /custody/users/{id}Addresses (with the coins each accepts) and balances.
GET /custody/users/{id}/depositsCredited deposits, newest first, with next_cursor.
POST /custody/users/{id}/transfersMove a user's balance of one asset to your main balance (all of it unless amount is given). An optional reference makes the request safe to repeat.
Read the Custody guide
POST · /withdrawals

Withdrawals

Pay out from your balance to an address: asset, amount and address are required; custody_user charges a custody user's balance; reference (unique per gateway) makes the request safe to repeat. Off by default: turn on API withdrawals in the gateway settings, where you also set a daily USD limit and the server IP addresses allowed to call it (both endpoints answer only those IPs). A reference reused for a different withdrawal is refused with 409. Large withdrawals may wait for review.

GET · /withdrawals/{id}

Status: pending_review, approved, processing, broadcast, completed, rejected (see error), cancelled or failed. tx_hash is set once it is sent.

Errors and limits

HTTPMeaning
401Missing, wrong or revoked key, or the gateway is suspended.
404Not found (or belongs to another gateway).
422The request was refused: "message" says why, and "errors" names the fields.
429Too many requests: 120 per minute per key. Wait and retry.
5xxTemporary problem on our side. Retry with the same Idempotency-Key.
{"message": "The minimum payment is 1.00 USD."}

{"message": "amount must be a decimal string with at most 2 decimals, e.g. \"25.00\".",
 "errors": {"amount": ["amount must be a decimal string with at most 2 decimals, e.g. \"25.00\"."]}}

Code libraries

Official libraries for this API, with no dependencies. They verify webhook signatures, raise a clear error for every refusal, and retry network errors only when repeating the request is safe.

LanguageVersionRequires
PHP1.0.0PHP 8.1+ with cURLDownload
Node.js1.0.0Node.js 18+, TypeScript types includedDownload
Python1.0.0Python 3.8+Download
// PHP
$coino = new \CoinoLive\Client('ck_live_...');
$payment = $coino->payments->create(['amount' => '25.00', 'currency' => 'EUR', 'order_id' => '1001']);

// Node.js
const coino = new CoinoLive('ck_live_...');
const payment = await coino.payments.create({ amount: '25.00', currency: 'EUR', order_id: '1001' });

# Python
coino = coinolive.Client("ck_live_...")
payment = coino.payments.create(amount="25.00", currency="EUR", order_id="1001") 

Each download includes a README with every call, webhook handling and the error classes.

Platform integrations

Ready-made plugins built on this API. Install one, paste an API key and enable it: orders update automatically, and each plugin shows your balance and payments inside its admin.

PlatformVersionRequires
WooCommerce1.1.0WordPress 6.2+, WooCommerce 7.6+, PHP 7.4+Download
WHMCS1.0.0WHMCS 8.0+, PHP 7.4+Download
OpenCart 41.0.0OpenCart 4.0.2+, PHP 8.0+Download
Shopware 61.0.0Shopware 6.6.5+ or 6.7, PHP 8.2+Download
PrestaShop1.0.0PrestaShop 8.0+ or 9, PHP 7.2+Download
Easy Digital Downloads1.0.0Easy Digital Downloads 3, WordPress 6.7+, PHP 8.0+Download
GiveWP1.0.0GiveWP 3+, WordPress 6.6+, PHP 7.4+Download

Other platforms: use the API directly as described above.