CryptoPAY Merchant API
Accept USDT TRC-20 payments on your platform. Fast integration, real-time blockchain confirmation, automatic webhook notifications.
https://cryptopay.webprompt.icuAll API requests use HTTPS. HTTP requests are redirected.
How it works
Authentication
Every API request must include your api_key in the Authorization header:
Authorization: Bearer YOUR_API_KEY
You receive your api_key and api_secret after your merchant account is approved by the platform administrator. Find them in your Merchant Dashboard → API Keys.
Supported Currencies
CryptoPAY accepts payments in fiat currencies and converts them to USDT TRC-20 at the current market rate.
| Code | Currency | Min Amount | Rate Source | Notes |
|---|---|---|---|---|
RUB |
Russian Ruble | 1 ₽ | CoinMarketCap (live, updated every 60s) | Default. Rate markup may apply per merchant. |
USD |
US Dollar | 1 $ | CoinMarketCap (live, ≈1.00) | No rate markup. USDT ≈ USD. |
"currency": "RUB" or "currency": "USD" in the request body.
If omitted, defaults to RUB. The amount field is always in the specified fiat currency — the system calculates the USDT equivalent automatically.
Rate calculation
// Example: 1000 RUB order at rate 83.21 RUB/USDT actual_usdt = 1000 / 83.21 = 12.0178 USDT // Example: 50 USD order at rate ~1.00 USD/USDT actual_usdt = 50 / 1.00 = 50.0000 USDT // The rate_used field in the response shows the exact rate applied. // Max order: 10,000 USDT equivalent (configurable by platform admin).
Request Signing (HMAC-SHA256)
Every POST request body must include a sign field. This prevents request tampering and proves your identity.
Collect fields
Take all request body fields except sign.
Sort alphabetically
Sort field names by ASCII value (A-Z, a-z).
Concatenate
Join as key1=value1&key2=value2&...
HMAC-SHA256
Compute HMAC-SHA256 of the string using your api_secret as the key. The result (lowercase hex) is your sign value.
Signing example
Given these fields and api_secret = "abc123secret":
// Input fields (excluding "sign"):
order_id = "ORDER-001"
amount = 1000
currency = "RUB"
notify_url = "https://example.com/callback"
// Step 2: Sort by key name
amount, currency, notify_url, order_id
// Step 3: Concatenate
"amount=1000¤cy=RUB¬ify_url=https://example.com/callback&order_id=ORDER-001"
// Step 4: HMAC-SHA256 with api_secret
sign = hmac_sha256("amount=1000¤cy=RUB¬ify_url=https://example.com/callback&order_id=ORDER-001", "abc123secret")
// → "e5f3a1b2c4d6..."
Integration Flow
Create an order
Call POST /api/v1/orders/create from your backend with the payment amount and your webhook URL.
Redirect the user
Redirect the user to the payment_url from the response. They'll see a checkout page with a QR code, wallet address, and countdown timer.
Receive the webhook
When payment is confirmed on the TRON blockchain (~5 seconds), we POST to your notify_url. Verify the sign field and respond with HTTP 200.
Fulfill the order
Mark the order as paid in your system. The user is automatically redirected to your redirect_url.
Create Order
Create a new payment order. Returns a payment URL to redirect the user to.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| order_id | string | required | Your unique order identifier (max 100 chars) |
| amount | number | required | Payment amount in fiat currency (min 1 RUB / 1 USD) |
| currency | string | optional | "RUB" (default) or "USD". See Currencies. |
| notify_url | string | optional | Webhook URL for payment notifications (HTTPS required) |
| redirect_url | string | optional | URL to redirect user after payment |
| sign | string | required | HMAC-SHA256 signature |
Response
| Field | Type | Description |
|---|---|---|
| trade_id | string | CryptoPAY unique payment ID |
| order_id | string | Your order ID (echoed back) |
| amount | number | Original fiat amount |
| actual_amount | number | USDT amount to be paid (4 decimal places) |
| currency | string | Currency code |
| rate_used | number | Exchange rate applied (fiat/USDT) |
| token | string | TRON wallet address for payment |
| expiration_time | integer | Unix timestamp when order expires |
| payment_url | string | Redirect user here to pay |
Code examples
curl -X POST https://cryptopay.webprompt.icu/api/v1/orders/create \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"order_id": "ORDER-001",
"amount": 1000.00,
"currency": "RUB",
"notify_url": "https://yoursite.com/webhook",
"redirect_url": "https://yoursite.com/success",
"sign": "COMPUTED_HMAC_SHA256_HEX"
}'
Response example
{
"status_code": 200,
"message": "success",
"data": {
"trade_id": "20260322a1b2c3d4e5f67890",
"order_id": "ORDER-001",
"amount": 1000.00,
"actual_amount": 12.0758,
"currency": "RUB",
"rate_used": 82.81,
"token": "TN4JsVEyUBMcBjJbRGTriAPBDMjZaxnMet",
"expiration_time": 1774131385,
"payment_url": "https://cryptopay.webprompt.icu/pay/checkout-counter/20260322a1b2c3d4e5f67890"
},
"request_id": "req_abc123"
}
Cancel Order
Cancel a pending (unpaid) order. Only orders with status 0 (Awaiting) can be cancelled.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| trade_id | string | required | CryptoPAY trade ID from create response |
| sign | string | required | HMAC-SHA256 signature |
curl -X POST https://cryptopay.webprompt.icu/api/v1/orders/cancel \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"trade_id": "20260322a1b2c3d4e5f67890", "sign": "HMAC_HEX"}'
Query Order
Retrieve the current status and details of an order.
curl https://cryptopay.webprompt.icu/api/v1/orders/query/20260322a1b2c3d4e5f67890 \ -H "Authorization: Bearer YOUR_API_KEY"
Response example
{
"status_code": 200,
"data": {
"trade_id": "20260322a1b2c3d4e5f67890",
"order_id": "ORDER-001",
"amount": 1000.00,
"actual_amount": 12.0758,
"currency": "RUB",
"rate_used": 82.81,
"token": "TN4JsVEyUBMcBjJbRGTriAPBDMjZaxnMet",
"status": 1,
"block_transaction_id": "7f3a2b1c...",
"callback_status": 1,
"commission": 0.2415,
"net_amount": 11.8343,
"created_at": "2026-03-22T12:00:00Z",
"paid_at": "2026-03-22T12:03:45Z",
"expiration_time": null,
"callback_payload": { "..." }
}
}
Payment Status (Polling)
Lightweight endpoint to check if a payment has been received. Useful for frontend polling while the user is on the checkout page. No authentication required.
// Response
{"status_code": 200, "data": {"status": 1}}
Webhook Callback
When a payment is confirmed on the TRON blockchain, CryptoPAY sends a POST request to your notify_url with the payment details.
Callback payload
{
"trade_id": "20260322a1b2c3d4e5f67890",
"order_id": "ORDER-001",
"amount": 1000.00,
"actual_amount": 12.0758,
"currency": "RUB",
"rate_used": 82.81,
"token": "TN4JsVEyUBMcBjJbRGTriAPBDMjZaxnMet",
"block_transaction_id": "7f3a2b1c4d5e6f7890abcdef...",
"status": 1,
"sign": "a3f2b1c4d5e6f7890123456789abcdef..."
}
| Field | Type | Description |
|---|---|---|
| trade_id | string | CryptoPAY payment ID |
| order_id | string | Your order ID |
| amount | number | Original fiat amount |
| actual_amount | number | USDT amount received |
| currency | string | Currency code |
| rate_used | number | Exchange rate used |
| token | string | TRON wallet that received payment |
| block_transaction_id | string | TRON blockchain transaction hash |
| status | integer | Always 1 (Paid) |
| sign | string | HMAC-SHA256 signature — verify this! |
Verifying Webhook Signatures
Always verify the sign field in webhook callbacks to ensure the request is authentic.
# Flask example
from flask import Flask, request, jsonify
import hmac, hashlib
app = Flask(__name__)
API_SECRET = "your_api_secret"
def verify_sign(payload: dict, secret: str) -> bool:
received_sign = payload.pop("sign", "")
sorted_str = "&".join(f"{k}={v}" for k, v in sorted(payload.items()))
expected = hmac.new(secret.encode(), sorted_str.encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(received_sign, expected)
@app.route("/webhook", methods=["POST"])
def webhook():
data = request.json
if not verify_sign(dict(data), API_SECRET):
return "Invalid signature", 403
trade_id = data["trade_id"]
order_id = data["order_id"]
amount_usdt = data["actual_amount"]
# ✅ Mark order as paid in your database
print(f"Payment confirmed: {order_id} — {amount_usdt} USDT")
return "ok", 200 # Must return 200!
Retry Policy
If your server doesn't respond with HTTP 200, we retry with exponential backoff:
| Attempt | Delay | Total elapsed |
|---|---|---|
| 1st | Immediate | 0 min |
| 2nd | 1 minute | 1 min |
| 3rd | 2 minutes | 3 min |
| 4th | 5 minutes | 8 min |
| 5th | 10 minutes | 18 min |
| 6th | 15 minutes | 33 min |
| 7th (final) | 30 minutes | ~63 min |
After 7 failed attempts, the callback is marked as failed. You can still query the order status via the API. The platform administrator can also trigger a new retry cycle from the admin panel.
Order & Callback Statuses
Order status
| Value | Name | Description |
|---|---|---|
| 0 | Awaiting | Order created, waiting for USDT payment |
| 1 | Paid | Payment confirmed on TRON blockchain |
| 2 | Expired | Order expired (default: 20 minutes) |
| 3 | Cancelled | Cancelled by merchant via API |
Callback status
| Value | Name | Description |
|---|---|---|
| 0 | Pending | Not yet attempted |
| 1 | Delivered | Merchant returned HTTP 200 |
| 2 | Retrying | Delivery in progress (retry cycle) |
| 3 | Failed | All 7 attempts exhausted |
Error Codes
| Code | Name | Description |
|---|---|---|
10001 | INVALID_REQUEST | Missing or invalid request parameters |
10002 | UNAUTHORIZED | Invalid API key or signature |
10003 | MERCHANT_SUSPENDED | Your account has been suspended |
10004 | ORDER_ALREADY_EXISTS | Duplicate order_id for this merchant |
10005 | PAY_AMOUNT_TOO_SMALL | Amount below minimum (1 RUB / 1 USD) |
10006 | PAY_AMOUNT_TOO_LARGE | Amount exceeds maximum (10,000 USDT equivalent) |
10007 | RATE_NOT_AVAILABLE | Exchange rate temporarily unavailable |
10008 | ORDER_NOT_CANCELLABLE | Order is not in Awaiting status |
10009 | NOT_AVAILABLE_AMOUNT | No payment slot available (all amounts taken) |
10010 | NOT_AVAILABLE_WALLET | No wallet address available |
10011 | INSUFFICIENT_BALANCE | Not enough balance for withdrawal |
10012 | ORDER_NOT_FOUND | Order doesn't exist or belongs to another merchant |
10013 | ACCOUNT_PENDING | Account pending admin approval |
10014 | ACCOUNT_REJECTED | Account registration was rejected |
10015 | TWO_FACTOR_REQUIRED | 2FA verification needed |
Error response format
{
"status_code": 10004,
"message": "order_id already exists for this merchant",
"data": null,
"request_id": "req_abc123"
}
Rate Limits
| Endpoint | Limit | Scope |
|---|---|---|
POST /api/v1/orders/create | 100 req/min | Per API key |
POST /api/v1/orders/create | 1,000 req/min | Global (all merchants) |
All /api/v1/* endpoints | 300 req/min | Per API key |
GET /pay/status/* | 60 req/min | Per IP address |
When rate limited, the API returns HTTP 429 (Too Many Requests).
Best Practices
Always verify webhook signatures
Use constant-time comparison (hmac.compare_digest in Python, crypto.timingSafeEqual in Node.js) to prevent timing attacks.
Make your webhook handler idempotent
We may send the same callback multiple times. Check if the order is already fulfilled before processing. Use trade_id as the idempotency key.
Respond to webhooks quickly
Return HTTP 200 within 30 seconds. Do heavy processing asynchronously after acknowledging.
Use unique order_ids
Each order_id must be unique per merchant. Reusing an order_id returns error 10004.
Handle order expiration
Orders expire after 20 minutes by default. If the user doesn't pay in time, create a new order.
Keep your API secret secure
Store it in environment variables or a secrets manager. Rotate via the merchant dashboard if compromised.