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".
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.
| Field | Description |
|---|---|
amount | Required. Up to 2 decimals, for example "25.00". A minimum of 1 USD (or its value) applies. |
currency | Three-letter code, USD when left out. |
order_id | Your order number, up to 128 characters. Shown to you, searchable with GET /payments. |
description | Shown to the customer on the payment page, up to 500 characters. |
customer_email | Optional customer email. |
success_url, cancel_url | Where the payment page sends the customer back to. A return to success_url is not proof of payment. |
notify_url | Webhook URL for this payment (https only). Overrides the webhook URL in your gateway settings. |
metadata | Your own key/value data, up to 20 keys and 4 KB. Returned unchanged. |
asset | Optional coin code from GET /assets (for example "usdt-tron") to skip the coin choice. |
expires_in | Minutes the customer has to pay, 15 to 10080. Default 1440 (one day). |
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).
| Status | Meaning | What to do |
|---|---|---|
open | Waiting for the customer to pay. | Nothing yet. |
confirming | A transaction was seen and waits for network confirmations. | Show "payment received, confirming". Do not ship yet. |
partially_paid | Less than the amount arrived. The payment page asks for the rest (remaining_usd). | Wait; the customer can still send the rest. |
paid | Fully paid and confirmed. Final. | Mark the order paid and ship. |
expired | The 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. |
cancelled | Cancelled 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.
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}/cancelCancels 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… }}
| Event | When |
|---|---|
payment.confirming, payment.partially_paid, payment.paid, payment.expired, payment.cancelled | The payment reached that status. |
custody.deposit.detected, custody.deposit.credited | A 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)
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 · /currenciesCurrencies 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 · /assetsCoins customers can pay with right now: code (for example "usdt-tron"), symbol, name, network, decimals and price_usd.
GET · /balancesYour 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.
| Request | Description |
|---|---|
POST /custody/users | Create 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}/deposits | Credited deposits, newest first, with next_cursor. |
POST /custody/users/{id}/transfers | Move 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. |
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
| HTTP | Meaning |
|---|---|
| 401 | Missing, wrong or revoked key, or the gateway is suspended. |
| 404 | Not found (or belongs to another gateway). |
| 422 | The request was refused: "message" says why, and "errors" names the fields. |
| 429 | Too many requests: 120 per minute per key. Wait and retry. |
| 5xx | Temporary 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.
| Language | Version | Requires | |
|---|---|---|---|
| PHP | 1.0.0 | PHP 8.1+ with cURL | Download |
| Node.js | 1.0.0 | Node.js 18+, TypeScript types included | Download |
| Python | 1.0.0 | Python 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.
| Platform | Version | Requires | |
|---|---|---|---|
| WooCommerce | 1.1.0 | WordPress 6.2+, WooCommerce 7.6+, PHP 7.4+ | Download |
| WHMCS | 1.0.0 | WHMCS 8.0+, PHP 7.4+ | Download |
| OpenCart 4 | 1.0.0 | OpenCart 4.0.2+, PHP 8.0+ | Download |
| Shopware 6 | 1.0.0 | Shopware 6.6.5+ or 6.7, PHP 8.2+ | Download |
| PrestaShop | 1.0.0 | PrestaShop 8.0+ or 9, PHP 7.2+ | Download |
| Easy Digital Downloads | 1.0.0 | Easy Digital Downloads 3, WordPress 6.7+, PHP 8.0+ | Download |
| GiveWP | 1.0.0 | GiveWP 3+, WordPress 6.6+, PHP 7.4+ | Download |
Other platforms: use the API directly as described above.