Средний15 мин чтения
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
- Log in to PonponPay merchant dashboard
- Go to "API Keys" page
- Click "Create Key"
- 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.
API Reference
Basic Information
| Base URL | https://api.ponponpay.com/pay |
| Authentication | Bearer Token (API Key) |
| Data Format | JSON |
Authentication
All API requests must include the API Key in the header:
| Header | Value |
|---|---|
Authorization | Bearer YOUR_API_KEY |
Content-Type | application/json |
Create Order
POST /order/add
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
currency | string | ✅ | Currency (USDT/USDC/BUSD) |
network | string | ✅ | Network (tron/ethereum/bsc/polygon/solana) |
amount | number | ✅ | Amount |
mch_order_id | string | ❌ | Merchant order ID (max 32 chars, auto-generated if not provided) |
notify_url | string | ❌ | Webhook callback URL |
redirect_url | string | ❌ | Redirect URL after payment |
Response Parameters
| Parameter | Type | Description |
|---|---|---|
trade_id | string | PonponPay transaction ID |
currency | string | Currency |
network | string | Blockchain network |
amount | number | Order amount |
actual_amount | number | Actual payment amount (4 decimals) |
address | string | Payment wallet address |
expiration_time | number | Expiration timestamp (seconds) |
payment_url | string | Payment page URL |
Code Examples
curl -X POST https://api.ponponpay.com/pay/order/add \
-H "Authorization: Bearer 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.ponponpay.com/status/PP202412110001"
}
}Query Order
POST /order/detail
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
trade_id | string | * | PonponPay transaction ID |
mch_order_id | string | * | Merchant order ID |
* Either trade_id or mch_order_id is required
Response Example
curl -X POST https://api.ponponpay.com/pay/order/detail \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"trade_id": "PP202412110001"
}'Webhook Callbacks
When order status changes, we send HTTP POST request to your configured Webhook URL.
Status Types
| status Value | Description |
|---|---|
1 | Pending payment |
2 | Payment successful |
3 | Order expired |
4 | Order cancelled |
5 | Manual recharge (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)
- Accept callbacks on server-side only. Do not verify in frontend code.
- Validate source and freshness first; reject expired or replayed requests.
- Use the official plugin or your internal wrapper to verify request integrity.
- Process business logic only after verification and enforce idempotency by order number.
- 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.PONPONPAY_API_KEY || '';
const nonceStore = new Map(); // 生产建议换 Redis
app.use(express.json({
type: 'application/json',
verify: (req, _res, buf) => {
req.rawBody = buf.toString('utf8');
}
}));
function verifyPonponPayWebhook(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 (!verifyPonponPayWebhook(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
| Code | Description |
|---|---|
0 | Success |
10001 | Invalid parameters |
10002 | Invalid signature |
10003 | Order not found |
10004 | Merchant disabled |
10005 | Invalid 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