API Reference

Complete ChainPay integration guide. Accept crypto payments in minutes.

Next.js Starter Template

Complete Next.js app with auth, pricing page, and ChainPay payments pre-wired. Clone and run in 5 minutes.

View on GitHub

Quick Start

Accept crypto payments in three steps — no blockchain knowledge required.

1

Create a payment order

Node.js
const response = await fetch("https://chainpay.pro/api/v1/orders", {
  method: "POST",
  headers: {
    "Authorization": "Bearer sk_live_YOUR_API_KEY",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    amount: 29.99,
    currency: "USDT",
    chain: "trc20",
    externalId: "order_abc123",           // your internal order ID
    metadata: { userId: "user_456" }      // arbitrary JSON, returned in webhook
  })
});

const order = await response.json();
// order.checkoutUrl  → redirect user here
// order.payAddress   → or show the address directly
2

Redirect the user to checkout

JavaScript
// Server-side redirect
res.redirect(order.checkoutUrl);

// Or client-side
window.location.href = order.checkoutUrl;
3

Receive a webhook when payment is confirmed

Webhook payload (POST to your server)
{
  "event": "payment.completed",
  "orderId": "ord_a1b2c3d4e5f6",
  "externalId": "order_abc123",
  "amount": "29.99",
  "netAmount": "29.75",          // after 0.8% fee
  "currency": "USDT",
  "chain": "trc20",
  "txHash": "e3b0c44298fc1c149afb...",
  "completedAt": "2026-03-22T08:15:00Z",
  "metadata": { "userId": "user_456" }
}

Verify the X-ChainPay-Signature header before processing. See the section for verification code.

Authentication

All API requests must include your API key as a Bearer token.

HTTP header
Authorization: Bearer sk_live_YOUR_API_KEY
cURL example
curl -X POST https://chainpay.pro/api/v1/orders \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount": 10, "currency": "USDT", "chain": "trc20"}'
Key prefixEnvironmentBlockchain
sk_live_…ProductionReal transactions
sk_test_…SandboxNo real funds moved
Security: Never expose your API key in client-side code or public repositories. Store it as an environment variable and call the API from your backend only.

Create Order

POSThttps://chainpay.pro/api/v1/orders

Request parameters

ParameterTypeRequiredDescription
amountnumberrequiredPayment amount in USD (e.g. 29.99). The equivalent crypto amount is calculated at current market rates.
currencystringrequiredCryptocurrency to receive. One of: "USDT", "ETH", "BTC", "SOL".
chainstringrequiredBlockchain network. One of: "trc20", "erc20", "btc", "sol". Must be compatible with the chosen currency.
externalIdstringoptionalYour internal order identifier. Returned unchanged in webhooks. Must be unique per merchant.
metadataobjectoptionalArbitrary JSON object (max 2 KB). Returned in all webhooks for this order — useful for passing user IDs, cart references, etc.

Supported currency + chain combinations

currencychainNetworkAvg confirmation time
USDTtrc20TRON (TRC-20)~1 min
USDTerc20Ethereum (ERC-20)~2 min
ETHerc20Ethereum~2 min
BTCbtcBitcoin~30 min
SOLsolSolana<5 sec

Response

201 Created
{
  "orderId":      "ord_a1b2c3d4e5f6g7h8",
  "payAddress":   "TXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "cryptoAmount": "29.99",
  "currency":     "USDT",
  "chain":        "trc20",
  "checkoutUrl":  "https://chainpay.pro/pay/ord_a1b2c3d4",
  "expiresAt":    "2026-03-22T09:00:00Z",
  "createdAt":    "2026-03-22T08:30:00Z"
}

Checkout Flow

How the hosted payment page works.

  1. 1
    RedirectYour server redirects the user to the checkoutUrl returned when creating the order.
  2. 2
    Payment pageThe hosted page displays a QR code and wallet address pre-populated with the exact crypto amount. The user scans the QR or copies the address into their wallet app.
  3. 3
    ConfirmationChainPay monitors the blockchain. Once the required number of confirmations is reached, the order status changes to confirming, then completed.
  4. 4
    Redirect backThe payment page shows a success screen. If you have a successUrl configured in the dashboard, the user is automatically redirected there.
  5. 5
    WebhookYour server receives a payment.completed webhook. Fulfil the order only after verifying the webhook signature.
Expiry: Orders expire 30 minutes after creation. If no payment is detected within that window, the status changes to expired and a payment.expired webhook is sent. Create a new order to retry.

Webhooks

ChainPay sends signed HTTP POST requests to your webhook URL when order status changes.

Setup

1

Go to Dashboard → Apps → New App

2

Fill in your Webhook URL (e.g. https://yourapp.com/api/webhooks/chainpay)

3

Copy the generated Webhook Secret — use it to verify signatures

Webhook Events

Currently supported events:

EventWhen triggered
payment.completedOrder paid and confirmed on-chain
payment.expiredOrder expired without payment

Payload Format

JSON
{
  "event": "payment.completed",
  "orderId": "ord_xxxxxxxx",
  "amount": "9.99",
  "currency": "USDT",
  "chain": "trc20",
  "txHash": "abc123...",
  "netAmount": "9.92",
  "completedAt": "2026-01-01T00:00:00.000Z",
  "metadata": {
    "userId": "your-user-id",
    "plan": "monthly"
  }
}

Signature Verification

Every webhook request includes a X-ChainPay-Signature header. Verify it using HMAC-SHA256 with your App's Webhook Secret:

Node.js / TypeScript
import crypto from 'crypto'

function verifyWebhook(rawBody: string, signature: string, secret: string): boolean {
  const computed = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex')
  return crypto.timingSafeEqual(
    Buffer.from(computed),
    Buffer.from(signature)
  )
}

// In your webhook handler:
export async function POST(req: Request) {
  const rawBody = await req.text()
  const signature = req.headers.get('x-chainpay-signature') ?? ''

  if (!verifyWebhook(rawBody, signature, process.env.CHAINPAY_WEBHOOK_SECRET!)) {
    return new Response('Unauthorized', { status: 401 })
  }

  const event = JSON.parse(rawBody)
  if (event.event === 'payment.completed') {
    const { userId, plan } = event.metadata
    // activate user's plan
  }

  return new Response('OK')
}
Python
import hmac, hashlib

def verify_webhook(raw_body: bytes, signature: str, secret: str) -> bool:
    computed = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(computed, signature)

Test your webhook

1

Go to Dashboard → Apps → select your app

2

Click "Send Test Event"

3

Check your server received the request (HTTP 200 expected)

Retry Policy

Failed deliveries are retried with exponential backoff: 1 min → 5 min → 30 min → 2 hours → 24 hours (5 attempts total).

After 5 failed attempts, the webhook is marked as failed. Check Dashboard → Alerts for failed deliveries.

Best Practices

  • Always verify the signature before processing
  • Return HTTP 200 quickly — do heavy processing asynchronously
  • Use the orderId to deduplicate events (webhooks may be delivered more than once)
  • Store the raw body before parsing to ensure accurate signature verification

Query Order

GEThttps://chainpay.pro/api/v1/orders/:id
Response
{
  "id":          "ord_a1b2c3d4e5f6g7h8",
  "amount":      "29.99",
  "cryptoAmount":"29.99",
  "netAmount":   "29.75",
  "currency":    "USDT",
  "chain":       "trc20",
  "status":      "completed",
  "payAddress":  "TXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "txHash":      "e3b0c44298fc1c149afbf4c8996fb92...",
  "externalId":  "order_abc123",
  "metadata":    { "userId": "user_456" },
  "createdAt":   "2026-03-22T08:30:00Z",
  "expiresAt":   "2026-03-22T09:00:00Z",
  "completedAt": "2026-03-22T08:45:00Z"
}

Order status values

StatusDescription
pendingOrder created, waiting for the user to send payment.
confirmingTransaction detected on-chain, waiting for enough block confirmations.
completedPayment fully confirmed. Safe to fulfil the order.
expired30 minutes elapsed without a detected payment.
failedAn unexpected error occurred. Contact support with the orderId.

Rates API

Fetch current USD exchange rates. No API key required.

GEThttps://chainpay.pro/api/v1/rates
Response
{
  "USDT": 1.0,
  "ETH":  3450.00,
  "BTC":  87500.00,
  "SOL":  145.00,
  "updatedAt": "2026-03-22T08:00:00Z"
}

Rates are updated every 60 seconds from aggregated market data. The amount you pass to the Create Order endpoint is always interpreted in USD; the API converts to crypto at the rate current at order creation time.

Sandbox Mode

Test your full integration without moving real funds.

Register with ?mode=test to receive a sk_test_ API key. All standard endpoints work identically in sandbox mode — blockchain polling is skipped and you trigger confirmation manually.

Step 1 — Get a sandbox API key

cURL
curl -X POST "https://chainpay.pro/api/auth/register?mode=test" \
  -H "Content-Type: application/json" \
  -d '{"email": "[email protected]", "password": "yourpassword"}'

# Response includes:
# { "merchant": { "apiKey": "sk_test_xxxxxxxxxxxxxxxx" } }

Step 2 — Create an order with your test key

cURL
curl -X POST https://chainpay.pro/api/v1/orders \
  -H "Authorization: Bearer sk_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount": 10, "currency": "USDT", "chain": "trc20", "externalId": "test_order_1"}'

Step 3 — Simulate a payment

cURL
curl -X POST https://chainpay.pro/api/v1/sandbox/simulate-payment \
  -H "Authorization: Bearer sk_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"orderId": "ord_abc123"}'

# Response:
{
  "success":   true,
  "orderId":   "ord_abc123",
  "txHash":    "sandbox_ord_abc123_1711000000000",
  "netAmount": "9.92000000"
}
# The order status is now "completed" and your webhook is fired.
Sandbox transaction hashes are prefixed with sandbox_. The simulate-payment endpoint is only available with sk_test_ keys. Attempting it with a live key returns 403.

Error Codes

All error responses use a consistent JSON envelope:

Error response envelope
{
  "error": "INVALID_CHAIN",
  "message": "chain 'foo' is not supported for currency USDT",
  "status": 400
}
HTTP statusCommon error codesCause & resolution
400INVALID_AMOUNT, INVALID_CURRENCY, INVALID_CHAIN, MISSING_FIELDRequest body is malformed or missing a required field. Check the request parameters table above.
401UNAUTHORIZED, INVALID_API_KEYMissing or invalid Authorization header. Ensure you are sending Bearer <key>.
403FORBIDDEN, SANDBOX_ONLYAPI key does not have permission for this action (e.g. using simulate-payment with a live key).
404ORDER_NOT_FOUNDNo order exists with the given ID, or it does not belong to your merchant account.
429RATE_LIMITEDToo many requests. Default limit is 60 req/min per API key. Back off and retry after the Retry-After header value.
500INTERNAL_ERRORUnexpected server-side error. Retry with exponential backoff. If persistent, contact support with the request ID.

Settlement

How and when ChainPay sends collected funds to your wallet.

Settlement schedule
Daily at 02:00 UTC
Processing fee
0.8% per transaction
Minimum payout
$10 equivalent

Settlement sends all completed, unsettled amounts to the wallet addresses you configure in the dashboard under Settings → Settlement Wallets. You can configure separate payout addresses per currency.

Tip: The processing fee is deducted per order — netAmount in webhooks already reflects the post-fee amount. Settlement transfers the sum of all netAmount values for the day.

Ready to integrate?

Create your account and get your API key in seconds.