PolyPay
Intermediate15 min read

API Key Mode - HTTP API Integration

Use API Key on server-side to create and manage orders via HTTP API. Suitable for scenarios requiring full control over payment flow.

Get Your API Key

  1. Log in to PolyPay merchant dashboard
  2. Go to "API Keys" page
  3. Click "Create Key"
  4. Securely save the generated API Key

⚠️ API Key is only shown once. Save it immediately! If lost, you need to regenerate. Never expose API Key in frontend code, Git repos, or logs.

Plan Quotas and Billing Scope

  • Subscription plans apply at the account level. All merchants under the account share monthly successful order, wallet address, notification, and notification channel quotas.
  • Production order creation checks the account-level monthly successful order quota. Once the limit is reached, any merchant under the account must upgrade the plan before creating new orders.
  • Payment Webhook callbacks are used for order status synchronization and do not count toward external alert notification quota.
  • Telegram, Email, WhatsApp, notification Webhook, WeCom, Discord, and other merchant business alerts count toward the account-level plan quota; in-app notifications and system subscription expiry notices do not.
  • Subscription expiring and expired notices are sent by PolyPay with fixed system copy and cannot be customized in notification templates.

API Reference

Basic Information

Base URLhttps://api.polypay.ai/api/v1/pay/sdk
AuthenticationX-API-Key (recommended) or Bearer Token (legacy-compatible)
Data FormatJSON

Authentication

All API requests must include the API Key in the header:

HeaderValue
X-API-KeyYOUR_API_KEY (recommended)
AuthorizationBearer YOUR_API_KEY (legacy-compatible)
Content-Typeapplication/json

Redirect to Hosted Checkout

If you want PolyPay to show the payment method selection page, do not call /order/add first. Use your API Key on the server to request the checkout_url below, then redirect the customer to that URL. Without currency/network, hosted checkout shows payment method selection. With both currency and network, it skips selection and opens the payment page directly. The same merchant order reuses an unexpired order only for the same payment method, so customers can return to selection and choose a different chain or currency.

POST /order/checkout

curl -X POST https://api.polypay.ai/api/v1/pay/sdk/order/checkout \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_API_KEY" \
  -d '{
    "mch_order_id": "ORDER_001",
    "amount": 10.00,
    "notify_url": "https://your-site.com/webhook",
    "redirect_url": "https://your-site.com/success",
    "locale": "en"
  }'

Response example:

{
  "code": 0,
  "message": "",
  "data": {
    "checkout_url": "https://checkout.polypay.ai/checkout?cs=cs_xxx&return_url=https%3A%2F%2Fmerchant.example%2Fsuccess",
    "payment_url": "https://checkout.polypay.ai/checkout?cs=cs_xxx&return_url=https%3A%2F%2Fmerchant.example%2Fsuccess"
  }
}

Create Order

POST /order/add

Request Parameters

ParameterTypeRequiredDescription
currencystringCurrency (USDT/USDC/BUSD)
networkstringNetwork (tron/ethereum/bsc/polygon/solana)
amountnumberAmount
mch_order_idstringMerchant order ID (max 32 chars, auto-generated if not provided)
notify_urlstringWebhook callback URL
redirect_urlstringRedirect URL after payment

Response Parameters

ParameterTypeDescription
trade_idstringPolyPay transaction ID
currencystringCurrency
networkstringBlockchain network
amountnumberOrder amount
actual_amountnumberActual payment amount (4 decimals)
addressstringPayment wallet address
expiration_timenumberExpiration timestamp (seconds)
payment_urlstringPayment page URL

Code Examples

curl -X POST https://api.polypay.ai/api/v1/pay/sdk/order/add \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "currency": "USDT",
    "network": "tron",
    "amount": 100.00,
    "mch_order_id": "ORDER_123456",
    "notify_url": "https://your-site.com/webhook",
    "redirect_url": "https://your-site.com/success"
  }'

Response Example

{
  "code": 0,
  "message": "success",
  "data": {
    "trade_id": "PP202412110001",
    "currency": "USDT",
    "network": "tron",
    "amount": 100.00,
    "actual_amount": 100.0001,
    "address": "TXxx...xxx",
    "expiration_time": 1704067200,
    "payment_url": "https://checkout.polypay.ai/status/PP202412110001"
  }
}

Query Order

POST /order/detail

Request Parameters

ParameterTypeRequiredDescription
trade_idstring*PolyPay transaction ID
mch_order_idstring*Merchant order ID

* Either trade_id or mch_order_id is required

Response Example

curl -X POST https://api.polypay.ai/api/v1/pay/sdk/order/detail \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "trade_id": "PP202412110001"
  }'

Webhook Callbacks

We send HTTP POST requests to your configured Webhook URL when an order is created and when its status changes.

Webhook Payload Example

{
  "order_no": "ORDER_123456",
  "status": 2,
  "amount": 100.0001,
  "currency": "USDT",
  "currency_name": "usdt",
  "network": "Tron",
  "contract_addr": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
  "hash": "abc123...",
  "wallet_address": "TXxx...xxx",
  "environment": "production",
  "event_id": "evt_xxx"
}

network is the payment chain; currency_name is the lowercase currency name such as usdt/usdc; hash is the on-chain transaction hash. contract_addr is an empty string for native assets or assets without a configured contract.

Status Types

status ValueDescription
1Pending payment
2Payment successful
3Order expired
4Order cancelled
5Manual recharge (handled as paid)
6On-chain confirming (treated as pending for merchants; no paid webhook is sent)
7Marked paid (handled as paid)

Verify Webhook Signature

Use the examples on this page together with the latest official docs as the source of truth. The protocol may evolve, so keep your server-side verifier updated. Never store API keys or verification secrets in frontend code.

Recommended Verification Flow (Server-side)

  1. Accept callbacks on server-side only. Do not verify in frontend code.
  2. Validate source and freshness first; reject expired or replayed requests.
  3. Use the official plugin or your internal wrapper to verify request integrity.
  4. Process business logic only after verification and enforce idempotency by order number.
  5. Return 401 for auth failures, 400 for bad payload, and 200/OK on success.
import crypto from 'crypto';
import express from 'express';
import { markEventProcessed } from './idempotency-store';

const app = express();
const apiKey = process.env.POLYPAY_API_KEY || '';
const nonceStore = new Map(); // 生产建议换 Redis

app.use(express.json({
  type: 'application/json',
  verify: (req, _res, buf) => {
    req.rawBody = buf.toString('utf8');
  }
}));

function verifyPolyPayWebhook(req) {
  const prefix = String(req.headers['x-key-prefix'] || '');
  const timestamp = String(req.headers['x-timestamp'] || '');
  const nonce = String(req.headers['x-nonce'] || '');
  const signature = String(req.headers['x-signature'] || '').toLowerCase();
  const rawBody = req.rawBody || '';

  if (!apiKey || !prefix || !timestamp || !nonce || !signature) return false;
  if (!/^\d+$/.test(timestamp)) return false;

  const ts = Number(timestamp);
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - ts) > 300) return false;
  if (prefix !== apiKey.slice(0, 12)) return false;
  if (!/^[A-Za-z0-9]{16,128}$/.test(nonce)) return false;

  const nonceKey = `${timestamp}:${nonce}`;
  if (nonceStore.has(nonceKey)) return false;
  nonceStore.set(nonceKey, Date.now());
  setTimeout(() => nonceStore.delete(nonceKey), 10 * 60 * 1000);

  const keyHash = crypto.createHash('sha256').update(apiKey).digest('hex');
  const payload = `${timestamp}\n${nonce}\n${rawBody}`;
  const expected = crypto.createHmac('sha256', keyHash).update(payload).digest('hex');

  const a = Buffer.from(signature, 'utf8');
  const b = Buffer.from(expected, 'utf8');
  if (a.length !== b.length) return false;
  return crypto.timingSafeEqual(a, b);
}

app.post('/webhook', express.json({ type: 'application/json' }), async (req, res) => {
  if (!verifyPolyPayWebhook(req)) {
    return res.status(401).send('Unauthorized');
  }

  const event = req.body;
  if (!event?.order_no || event?.status === undefined) {
    return res.status(400).send('Bad Request');
  }

  // 幂等保护:相同订单事件只处理一次
  const firstTime = await markEventProcessed(event.order_no, event.status);
  if (!firstTime) {
    return res.status(200).send('OK');
  }

  switch (Number(event.status)) {
    case 2:
    case 5:
      await handleOrderPaid(event.data);
      break;
    case 3:
      await handleOrderExpired(event.data);
      break;
    case 4:
      await handleOrderCancelled(event.data);
      break;
    default:
      break;
  }

  res.status(200).send('OK');
});

Error Codes

CodeDescription
0Success
10001Invalid parameters
10002Invalid signature
10003Order not found
10004Merchant disabled
10005Invalid API Key

Security Best Practices

  • Protect API Key: Never expose API Key in frontend code, Git repos, or logs
  • Use Environment Variables: Store API Key in environment variables
  • Verify Webhook Signatures: Always verify Webhook request signatures
  • Use HTTPS: Ensure your Webhook endpoint uses HTTPS
  • Implement Idempotency: Webhooks may be sent multiple times, ensure idempotent logic