API reference
The CryptoPayr API lets you accept cryptocurrency payments, track their status, and pull live market data. It's a plain REST API that returns JSON. The base URL is:
https://cryptopayr.com
Create a payment from your backend, send your customer to the returned hosted checkout, and receive a signed webhook the moment it confirms. No coins ever touch your servers.
On the hosted checkout the customer picks any supported coin and network. Set pay_currency on a payment (or a payment link) to pin a specific coin and skip the picker. Prefer no code? Create a payment link in the dashboard and share the URL.
Authentication
Authenticate write requests with your secret API key as a Bearer token. Find your key in the dashboard. Market-data endpoints (rates, convert) are public.
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxx
Fees
Crypto processing fees are a percentage that starts at 3% and drops automatically with your volume, plus a flat $0.10 per payment — billed only on successful payments. No setup or monthly fees.
| Rolling 24h volume | Fee |
|---|---|
| $0 – $999 | 3% + $0.10 |
| $1,000 – $4,999 | 2% + $0.10 |
| $5,000+ | 1% + $0.10 |
Your current rate is shown in the dashboard. By default the merchant absorbs the fee; set buyer_pays_fees: 1 to add it on top of the amount your customer pays.
Create a payment
POST /api/v1/payment/create
Creates a charge and returns a hosted checkout_url to send your customer to.
| Field | Type | Description |
|---|---|---|
amount | number | Required. Amount in the fiat currency. |
currency | string | Required. 3-letter fiat code, e.g. USD. |
webhook_url | string | Per-payment endpoint we POST the signed confirmation to. Overrides your account default for this payment; omit to use the default. |
success_url | string | Redirect after a successful payment. |
cancel_url | string | Redirect if the customer cancels. |
pay_currency | string | Optional. Pin the pay-in coin (e.g. USDT) to skip the picker. |
pay_network | string | Optional. Network for the pinned coin (e.g. TRON). |
buyer_pays_fees | 0 / 1 | Add the processing fee on top of the amount. |
metadata | string | Your own reference (≤ 255 chars), echoed back in webhooks. |
on_behalf_of | string | Platforms only. A client's account ID (usr_…) to credit instead of yourself. Requires a platform account. |
platform_fee_percent | number | Platforms only. Commission percent of the order value (0–50). Deducted from the client's net. |
platform_fee_fixed | number | Platforms only. Fixed commission in USD on top of the percent (0–10000). |
Marketplaces. If your account is a platform account, pass on_behalf_of with a client's account ID (they share their usr_… id with you) to create the charge on their account, authenticated with your API key. Your commission (platform_fee_percent of the order value plus platform_fee_fixed) is deducted from the client's net when the payment completes and credited to your balance — capped at the client's net so it can never go negative. List the Orders you've created with GET /api/v1/transactions?role=platform, and register a platform webhook in your platform settings to receive a signed order.completed event (with the commission_usd) for each one. Ordinary merchants can ignore these fields.
Send an Idempotency-Key header (any unique string, e.g. a UUID) to make retries safe — replaying the same key for 24h returns the original payment instead of creating a duplicate. Replays come back with an Idempotency-Replayed: true header.
curl -X POST https://cryptopayr.com/api/v1/payment/create \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 149.00,
"currency": "EUR",
"webhook_url": "https://shop.example/hook",
"success_url": "https://shop.example/thanks",
"metadata": "order_4815"
}'
Response
{
"status": "success",
"message": "Payment created",
"data": {
"tid": "8f14e45f-ceea-467e-9a36-dcd1f8d7a2b1",
"status": "CREATED",
"amount": 149.00,
"currency": "EUR",
"checkout_url": "https://cryptopayr.com/payment/8f14e45f-ceea-467e-9a36-dcd1f8d7a2b1"
}
}
Look up a payment
GET /api/v1/payment/lookup?tid={tid}
Public, read-only status check. Used for reconciliation and by the hosted checkout to detect confirmation.
curl https://cryptopayr.com/api/v1/payment/lookup?tid=8f14e45f-ceea-467e-9a36-dcd1f8d7a2b1
Response
{
"status": "success",
"data": {
"tid": "8f14e45f-ceea-467e-9a36-dcd1f8d7a2b1",
"status": "COMPLETED",
"amount": "149.0000",
"amount_usd": "172.3200",
"currency": "EUR",
"created_at": "2026-06-06 12:00:00",
"expires_at": "2026-06-06T13:00:00+01:00",
"expires_in": 3214,
"coin": "USDT",
"crypto_amount": 172.32,
"received": 172.32,
"remaining": 0,
"partial": 0
}
}
The coin, crypto_amount, received, remaining and partial fields appear once the customer has selected a coin on the hosted checkout — handy for showing live payment progress. expires_in is the seconds left before the invoice expires.
List transactions
GET /api/v1/transactions
Returns your transactions, newest first, 50 per page. Authenticate with your secret API key.
| Query | Type | Notes |
|---|---|---|
page | integer | 1-based page number. Default 1. |
status | string | Optional filter: CREATED, PENDING, COMPLETED, EXPIRED or CANCELLED. |
sort | string | date (default, newest first) or status (grouped by status). |
role | string | Platforms only. merchant (default — your own received payments) or platform (the Orders you created on behalf of clients). The platform listing adds on_behalf_of, platform_fee_percent, platform_fee_fixed and commission_usd (the credited commission, or null until the Order completes) to each row. |
curl https://cryptopayr.com/api/v1/transactions?page=1&status=COMPLETED \
-H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxx"
Response
{
"status": "success",
"data": {
"page": 1,
"per_page": 50,
"total": 128,
"total_pages": 3,
"has_more": true,
"data": [
{
"tid": "8f14e45f-ceea-467e-9a36-dcd1f8d7a2b1",
"status": "COMPLETED",
"amount": 149.00,
"currency": "EUR",
"amount_usd": 172.32,
"buyer_pays_fees": 0,
"metadata": "order-1024",
"created_at": "2026-06-06T12:00:00+01:00"
}
]
}
}
Cancel a payment
POST /api/v1/payment/cancel
Void an unpaid invoice early instead of waiting for it to expire. Body: { "tid": "…" }. Only CREATED/PENDING payments can be cancelled — a COMPLETED one returns 409.
curl -X POST https://cryptopayr.com/api/v1/payment/cancel \
-H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"tid":"8f14e45f-ceea-467e-9a36-dcd1f8d7a2b1"}'
Response
{ "status": "success", "data": { "tid": "8f14…", "status": "CANCELLED" } }
Balance
GET /api/v1/balance
Returns your current wallet balance, all USD-denominated. Authenticate with your secret API key.
curl https://cryptopayr.com/api/v1/balance \
-H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxx"
Response
{
"status": "success",
"data": {
"currency": "USD",
"available": 12840.50,
"pending": 50.00,
"lifetime_credited": 184210.77
}
}
available is withdrawable now; pending is reserved for in-flight payouts; lifetime_credited is everything ever credited to you, net of fees.
Account info
GET /api/v1/account
Your own account details — including your public account id (usr_…), which others use to send you internal transfers.
curl https://cryptopayr.com/api/v1/account \
-H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxx"
Response
{
"status": "success",
"data": {
"account_id": "usr_AbC123…",
"business_name": "Acme Store",
"email": "[email protected]",
"is_activated": true,
"fee_percent": 2,
"fixed_fee": 0.10,
"referral_code": "AbC123",
"default_webhook": "https://acme.com/cryptopayr",
"default_success": null,
"default_cancel": null,
"created_at": "2026-01-01T00:00:00+00:00"
}
}
Payment links
GET /api/v1/payment-links — list your links.
POST /api/v1/payment-links — create a reusable link served at /link/{slug}.
| Field | Type | Notes |
|---|---|---|
mode | string | fixed (default) or open (customer enters the amount). |
amount | number | Required for fixed mode. |
currency | string | 3-letter fiat, default USD. |
title | string | Optional label. |
pay_currency / pay_network | string | Optional — pin a coin/network. |
success_url / webhook_url | string | Optional per-link overrides. |
curl -X POST https://cryptopayr.com/api/v1/payment-links \
-H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"title":"T-shirt","mode":"fixed","amount":25,"currency":"USD"}'
Response
{
"status": "success",
"message": "Payment link created",
"data": { "slug": "Ab12…", "url": "https://cryptopayr.com/link/Ab12…", "mode": "fixed", "amount": 25, "currency": "USD", "active": true }
}
Create a refund
POST /api/v1/refund/create
Refund a completed payment. We email the customer a link to choose where to receive it; funds move once they claim it. Authenticate with your standard API key.
| Field | Type | Notes |
|---|---|---|
tid | string | Required. The payment to refund. |
amount | number | Optional USD amount; omit for the full remaining refundable amount. |
email | string | Optional; defaults to the payment's stored customer email. |
reason | string | Optional note shown to the customer. |
curl -X POST https://cryptopayr.com/api/v1/refund/create \
-H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"tid":"8f14…","amount":25,"email":"[email protected]"}'
Response
{ "status": "success", "data": { "refund_id": "…", "status": "REQUESTED", "tid": "8f14…", "claim_url": "https://cryptopayr.com/refund/…" } }
List refunds
GET /api/v1/refunds?page={n}&status={s}
Your refunds, newest first, 50 per page. Optional status: REQUESTED, PROCESSING, COMPLETED, REJECTED, CANCELLED. Same {page,per_page,total,total_pages,has_more,data[]} envelope as the other lists.
Webhooks
When a payment is confirmed we POST a JSON event to your webhook_url — the endpoint you set per payment, or your account default from the dashboard settings. This is how you fulfil orders server-side; never rely on the buyer's browser reaching your success_url.
Request we send
POST /your/webhook HTTP/1.1
Content-Type: application/json
X-CryptoPayr-Signature: 9f86d081884c7d659a2feaa0c55ad015a839f4f8...
{
"tid": "8f14e45f-ceea-467e-9a36-dcd1f8d7a2b1",
"status": "COMPLETED",
"amount": "149.0000",
"currency": "EUR",
"metadata": "order_4815",
"timestamp": 1781049600
}
Payload fields
| Field | Type | Description |
|---|---|---|
tid | string | The transaction id you got from create. |
status | string | The event — see the table below. |
amount | string | Order amount in the fiat currency. |
currency | string | 3-letter fiat code. |
metadata | string | Your own reference, echoed back verbatim. |
timestamp | integer | Unix time the event was sent. |
Events
status | When it fires |
|---|---|
COMPLETED | Payment confirmed on-chain (or an in-tolerance underpayment was accepted). Funds are credited to your balance. This is the event to fulfil orders on. |
PENDING | Sent if you re-send an event while a transaction is still awaiting confirmations (e.g. via the dashboard's Resend webhook). |
EXPIRED / CANCELLED | Sent if you re-send an event for a transaction that expired or was cancelled. Use it to release a held order. |
REFUNDED | A refund against this payment was sent on-chain. Fires automatically when the refund completes — reconcile your records and notify the customer if needed. |
COMPLETED event automatically. The other statuses share the same signed format and are delivered when an event is re-sent from the dashboard — treat your handler as status-driven and idempotent (key on tid) so it stays correct as more automatic events are added.Verifying the signature
Every webhook carries an X-CryptoPayr-Signature header: an HMAC-SHA256 of the raw request body keyed with your secret API key (the same sk_live_… key you authenticate with). Compute the HMAC over the exact bytes you received and compare in constant time. Our official plugins verify the same way.
$body = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_CRYPTOPAYR_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $body, $API_KEY); // your sk_live_… key
if (!hash_equals($expected, $sig)) {
http_response_code(400);
exit; // reject — not from CryptoPayr
}
$event = json_decode($body, true);
if ($event['status'] === 'COMPLETED') {
// mark order $event['metadata'] as paid (idempotent on $event['tid'])
}
http_response_code(200);
200 OK once you've stored the event. We retry on non-2xx responses. Rotating your API key in the dashboard also rotates the webhook signing key.Send a buyer receipt
POST /api/v1/payment/receipt
A customer email is never required to take a payment. Call this after a completed payment to email the buyer a branded receipt — it's what the hosted checkout uses when a buyer opts in. The transaction must already be COMPLETED.
| Field | Type | Description |
|---|---|---|
tid | string | Required. The completed transaction id. |
email | string | Required. Where to send the receipt. |
curl -X POST https://cryptopayr.com/api/v1/payment/receipt \
-H "Content-Type: application/json" \
-d '{ "tid": "8f14e45f-ceea-467e-9a36-dcd1f8d7a2b1", "email": "[email protected]" }'
Response
{ "status": "success", "message": "Receipt sent", "data": { "tid": "8f14e45f-..." } }
Exchange rates
GET /api/v1/rates — every tracked coin's USD price.
GET /api/v1/rates/{COIN} — one coin.
curl https://cryptopayr.com/api/v1/rates/BTC
Response
{
"status": "success",
"data": { "currency": "BTC", "usd_rate": 68000, "base": "USD" }
}
Currency conversion
GET /api/v1/convert?from={A}&to={B}&amount={n}
Convert between any supported fiat/crypto pair, routed through USD. Crypto results carry 8 decimals, fiat 2.
curl "https://cryptopayr.com/api/v1/convert?from=USD&to=BTC&amount=100"
Response
{
"status": "success",
"data": {
"from": "USD", "to": "BTC", "amount": 100,
"result": 0.00147058, "rate": 0.0000147058
}
}
Underpayments & expiry
Invoices expire 1 hour after creation. The hosted checkout shows a live countdown.
If a buyer sends less than the invoiced amount, the checkout shows the exact shortfall so they can top up to the same address. Merchants can opt to accept underpayments in the dashboard — when enabled, a short payment is marked complete and the normal webhook fires.
A customer email is never required to take a payment. After a successful payment the buyer can optionally enter their email to receive a receipt.
Request a withdrawal
Programmatically withdraw your available balance to a crypto address. Because this endpoint moves funds out, it is authenticated with a separate withdrawal key — not your standard API key.
wk_live_…) is shown once at generation. Regenerating invalidates the previous key immediately.POST /api/v1/payout/create
Authenticate with the withdrawal key as a Bearer token:
Authorization: Bearer wk_live_xxxxxxxxxxxxxxxxxxxxxxxx| Field | Type | Notes |
|---|---|---|
amount | number | Required. USD amount to withdraw from your available balance. |
currency | string | Coin to receive, e.g. USDT. Default USDT. |
network | string | Network, e.g. TRON. Default TRON. |
address | string | Destination wallet address. Required unless address_id is given. |
address_id | integer | Optional. Use a saved address book entry — its address, currency and network are used. |
curl -X POST https://cryptopayr.com/api/v1/payout/create \
-H "Authorization: Bearer wk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"amount":50,"currency":"USDT","network":"TRON","address":"T..."}'Response
{
"status": "success",
"message": "Payout requested",
"data": {
"payout_id": 42,
"status": "PROCESSING",
"amount": 50,
"platform_fee": 0.5,
"receive_estimate": 48.4,
"currency": "USDT",
"network": "TRON",
"address": "T..."
}
}Payouts are dispatched immediately — there is no manual approval step. A successful call returns PROCESSING; on-chain confirmation moves it to COMPLETED. Your balance is checked atomically, so a payout can never exceed your available balance.
Idempotency-Key so a retried request replays the original result instead of dispatching a second payout.If a withdrawal whitelist is enabled on your account, the address must be one of your saved addresses.
List payouts
GET /api/v1/payouts
Returns your payouts, newest first, 50 per page. This is a read endpoint — authenticate with your standard API key (not the withdrawal key).
| Query | Type | Notes |
|---|---|---|
page | integer | 1-based page number. Default 1. |
status | string | Optional filter: PENDING, PROCESSING, COMPLETED or REJECTED. |
curl https://cryptopayr.com/api/v1/payouts?page=1 \
-H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxx"
Response
{
"status": "success",
"data": {
"page": 1,
"per_page": 50,
"total": 7,
"total_pages": 1,
"has_more": false,
"data": [
{
"payout_id": 42,
"status": "COMPLETED",
"amount": 50.00,
"platform_fee": 0.50,
"receive_estimate": 48.40,
"currency": "USDT",
"network": "TRON",
"address": "T...",
"tx_hash": "9d2c…",
"created_at": "2026-06-06T12:00:00+01:00"
}
]
}
}
Get a payout
GET /api/v1/payout/lookup?id={payout_id}
Returns a single payout you own. Authenticate with your standard API key. A payout id that isn't yours returns 404.
curl https://cryptopayr.com/api/v1/payout/lookup?id=42 \
-H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxx"
Response
{
"status": "success",
"data": {
"payout_id": 42,
"status": "COMPLETED",
"amount": 50.00,
"platform_fee": 0.50,
"receive_estimate": 48.40,
"currency": "USDT",
"network": "TRON",
"address": "T...",
"tx_hash": "9d2c…",
"created_at": "2026-06-06T12:00:00+01:00"
}
}
The tx_hash is the processor/on-chain reference once the payout has been dispatched (null until then).
Transfer to an account
Move balance to another CryptoPayr account instantly and for free — an internal book transfer, nothing touches the chain. Because it moves funds out of your balance, it uses your withdrawal key (wk_live_…), not your standard API key.
POST /api/v1/transfer/create
Authorization: Bearer wk_live_xxxxxxxxxxxxxxxxxxxxxxxx| Field | Type | Notes |
|---|---|---|
recipient | string | Required. The destination account's public id, e.g. usr_AbC123… (aliases: to, user_id). |
amount | number | Required. USD amount to send from your available balance. |
note | string | Optional memo (≤255 chars). |
curl -X POST https://cryptopayr.com/api/v1/transfer/create \
-H "Authorization: Bearer wk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"recipient":"usr_AbC123","amount":25,"note":"invoice 1024"}'Response
{
"status": "success",
"message": "Transfer complete",
"data": {
"transfer_id": "txf_AbC123…",
"status": "COMPLETED",
"amount": 25,
"currency": "USD",
"recipient": "usr_AbC123",
"recipient_name": "Acme Store",
"note": "invoice 1024",
"created_at": "2026-06-08T12:00:00+01:00"
}
}The transfer is atomic — your balance is debited and the recipient credited in one step, or nothing happens. Insufficient balance returns 422; an unknown recipient returns 404; sending to your own account is rejected. Send an Idempotency-Key header so a retried request replays the original result instead of sending twice.
List transfers
GET /api/v1/transfers?page={n}&direction={in|out}
Your internal transfers (sent and received), newest first, 50 per page. Each row has a direction (in/out) and the counterparty account id. Authenticate with your standard API key.
Get a transfer
GET /api/v1/transfer/lookup?id={txf_…}
One transfer you're a party to (by its txf_ id). Returns 404 if you're neither sender nor recipient.
Estimate a payout
GET /api/v1/payout/estimate?amount={n}¤cy={c}&network={n}
Preview the platform fee, network fee and net "you receive" amount before sending a withdrawal — nothing is created. currency defaults USDT, network defaults TRON.
{ "status": "success", "data": { "amount": 50, "platform_fee": 0.5, "network_fee": 1.1, "receive_estimate": 48.4, "currency": "USDT", "network": "TRON" } }
Payout addresses
GET /api/v1/payout-addresses — list saved addresses.
POST /api/v1/payout-addresses — add one (currency, network, address, optional label, make_default).
DELETE /api/v1/payout-addresses?id={id} — remove one.
Your saved address book — the same one the withdrawal endpoint can target with address_id, and that the optional withdrawal whitelist enforces.
Payment statuses
| Status | Meaning |
|---|---|
CREATED | Payment created, awaiting the customer. |
PENDING | Funds detected, awaiting confirmations. |
COMPLETED | Paid and confirmed. Webhook fired. |
EXPIRED | Checkout link expired before payment. |
CANCELLED | Cancelled by the customer or merchant. |
Errors
Errors use standard HTTP status codes and a JSON body:
{ "status": "failed", "error": "Invalid API key", "timestamp": 1781049600 }
| Code | Meaning |
|---|---|
| 401 | Missing or invalid API key. |
| 404 | Resource or currency not found. |
| 422 | Missing or invalid parameters. |
| 429 | Rate limit exceeded — see below. |
| 500 | Something went wrong on our side. |
Rate limits
Every API response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (unix time). When you exceed a window we return 429 with a Retry-After header.
| Endpoint | Limit |
|---|---|
POST /payment/create | 120 / min per merchant |
GET /rates, /convert, /payment/lookup | 120 / min per IP |
POST /payment/receipt | 20 / min per IP |
Need a hand? Email [email protected].