Getting Started
ZyndPay API Reference
The ZyndPay API is a RESTful payment gateway for accepting USDT on the TRON blockchain. Resource-oriented URLs, JSON bodies, and a consistent response envelope make it easy to integrate in any environment.
All amounts are in USDT (TRC20). The minimum payin is 20 USDT. A $2 USDT minimum fee floor and flat $2 USDT payout fee apply to all transactions.
Base URL
https://api.zyndpay.com/v1All responses are wrapped in a consistent envelope:
{
"success": true,
"data": { ... },
"message": "optional string",
"meta": {
"page": 1,
"limit": 20,
"total": 100,
"totalPages": 5
}
}Getting Started
Authentication
Include your API key in the X-Api-Key header on every request. Alternatively, pass it as a Bearer token in the Authorization header.
curl https://api.zyndpay.com/v1/payin \
-H "X-Api-Key: zyp_live_sk_..."API key prefixes let you identify the key type at a glance:
| Parameter | Type | Required | Description |
|---|---|---|---|
zyp_live_sk_... | secret | required | Live secret key — full API access. Never expose client-side. |
zyp_live_pk_... | publishable | optional | Live publishable key — limited read access, safe for browsers. |
zyp_test_sk_... | secret | optional | Sandbox secret key — identical to live but no real transactions. |
zyp_test_pk_... | publishable | optional | Sandbox publishable key. |
zyp_rk_... | restricted | optional | Restricted key — scoped to specific operations only. |
zyp_live_sk_ or zyp_test_sk_ in client-side code, mobile apps, or public repositories.Getting Started
Quick Start
Create your first payment in three steps — no SDK required, just a single HTTP request.
Get your API key
Sign up at dashboard.zyndpay.com, complete KYB verification, and copy your live or sandbox secret key.
Create a payin
POST /v1/payin with your amount and an externalRef (your order ID). You receive a TRON deposit address.
Receive the webhook
When your customer sends USDT to the address, ZyndPay fires a payin.confirmed webhook and credits your balance.
Payins
Create a Payin
Generates a unique TRON wallet address for your customer to send USDT to. The address expires after expiresInSeconds seconds (default 1 hour).
/v1/payinRequest body
| Parameter | Type | Required | Description |
|---|---|---|---|
amount | string | required | Amount in USDT as a decimal string (e.g. "100.00"). Minimum: "20.00". |
externalRef | string | optional | Your order or reference ID (optional). Must be unique per merchant if provided. |
expiresInSeconds | integer | optional | Seconds until the address expires. Minimum: 900. Default: 3600. |
successUrl | string | optional | URL to redirect the customer after a successful payment. |
cancelUrl | string | optional | URL to redirect the customer if the payment expires or is cancelled. |
metadata | object | optional | Arbitrary key-value pairs stored with the payment and included in webhooks. |
curl -X POST https://api.zyndpay.com/v1/payin \
-H "X-Api-Key: zyp_live_sk_..." \
-H "Content-Type: application/json" \
-d '{
"amount": "100.00",
"externalRef": "order_123",
"expiresInSeconds": 3600,
"successUrl": "https://yoursite.com/payment/success",
"cancelUrl": "https://yoursite.com/payment/cancel"
}'{
"success": true,
"data": {
"id": "cma1xyz8f0001yx5k9abc1234",
"depositAddress": "TRXabc123def456ghi789jkl",
"amount": "100.00",
"status": "PENDING",
"externalRef": "order_123",
"expiresAt": "2026-03-06T12:00:00.000Z",
"createdAt": "2026-03-06T11:00:00.000Z"
}
}Get a Payin
Retrieve a single payin by its ID.
/v1/payin/:id{
"success": true,
"data": {
"id": "cma1xyz8f0001yx5k9abc1234",
"depositAddress": "TRXabc123def456ghi789jkl",
"amount": "100.00",
"amountReceived": "100.00",
"fee": "3.00",
"netAmount": "97.00",
"status": "CONFIRMED",
"txHash": "abc123def456...",
"externalRef": "order_123",
"confirmedAt": "2026-03-06T11:01:02.000Z",
"createdAt": "2026-03-06T11:00:00.000Z"
}
}Payment statuses
PENDING— Awaiting paymentCONFIRMING— Funds received, waiting for 20 confirmationsCONFIRMED— 20+ confirmations — balance creditedUNDERPAID— Less than expected receivedOVERPAID— More than expected receivedEXPIRED— Address expired without paymentList Payins
Returns a paginated list of payins, newest first.
/v1/payin| Parameter | Type | Required | Description |
|---|---|---|---|
page | integer | optional | Page number. Default: 1. |
limit | integer | optional | Results per page. Default: 20. Max: 100. |
status | string | optional | Filter by status: PENDING, CONFIRMING, CONFIRMED, EXPIRED, UNDERPAID, or OVERPAID. |
currency | string | optional | Filter by currency (e.g. USDT_TRC20). |
{
"success": true,
"data": [ ... ],
"meta": {
"page": 1,
"limit": 20,
"total": 134,
"totalPages": 7
}
}Payouts
Create a Payout
Payouts let you send USDT from your ZyndPay balance to any TRON address — pay vendors, refund customers, or distribute affiliate commissions. A flat fee of $2 USDT is deducted from your balance on top of the payout amount.
any third-party address (vendors, customers, affiliates). Withdrawals move funds to your own pre-saved wallet addresses./v1/payoutIdempotency-Key header (UUID recommended) to safely retry failed requests without creating duplicates.Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
amount | string | required | Amount in USDT to send (e.g. "50.00"). Minimum: "20.00". The $2 USDT processing fee is charged separately. |
destinationAddress | string | required | TRON (TRC20) wallet address of the recipient. Must match the format T followed by 33 base58 characters. |
currency | string | optional | Currency to send. Default: USDT_TRC20. |
chain | string | optional | Blockchain network. Default: TRON. |
externalRef | string | optional | Your internal reference ID for this payout (e.g. vendor invoice number). |
metadata | object | optional | Arbitrary key-value pairs stored with the payout. |
curl -X POST https://api.zyndpay.com/v1/payout \
-H "X-Api-Key: zyp_live_sk_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-d '{
"amount": "50.00",
"destinationAddress": "TXYZabc123def456ghi789jkl012mno345",
"externalRef": "payout_vendor_456"
}'{
"success": true,
"data": {
"transactionId": "cma2abc9g0002yz6l0def5678",
"status": "PROCESSING",
"processingFee": "2.00",
"requiresManualApproval": false,
"currentPayinFee": "3.0%",
"currentTier": "STARTER"
}
}Get a Payout
Retrieve a single payout by its ID, including its current status and transaction details.
/v1/payout/:idPayout statuses
PENDING— Awaiting manual approval (amount > $50K)PROCESSING— Approved, preparing broadcastBROADCAST— Submitted to TRON networkCONFIRMED— Confirmed on-chainFAILED— Broadcast failed — balance refundedCANCELLED— Cancelled before broadcastList Payouts
Returns a paginated list of payouts, newest first.
/v1/payout| Parameter | Type | Required | Description |
|---|---|---|---|
page | integer | optional | Page number. Default: 1. |
limit | integer | optional | Results per page. Default: 20. Max: 100. |
{
"success": true,
"data": {
"items": [ ... ],
"total": 42
}
}Withdrawals
Request a Withdrawal
Withdrawals let merchants move their settled USDT balance out of ZyndPay to one of their pre-saved wallet addresses. A flat fee of $2 USDT is deducted from the amount.
addressId is omitted, ZyndPay uses your account's primary withdrawal address./v1/withdrawalsIdempotency-Key header (UUID recommended) to safely retry failed requests without creating duplicates.| Parameter | Type | Required | Description |
|---|---|---|---|
amount | string | required | Amount in USDT to withdraw (e.g. "500.00"). The $2 USDT fee is deducted from this amount. |
addressId | UUID | optional | ID of the saved withdrawal address to use. Defaults to your primary address if omitted. |
curl -X POST https://api.zyndpay.com/v1/withdrawals \
-H "X-Api-Key: zyp_live_sk_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-d '{
"amount": "500.00",
"addressId": "addr_cma1xyz8f0001yx5k"
}'{
"success": true,
"data": {
"id": "wdr_cma1xyz8f0001yx5k",
"amount": "500.00",
"fee": "2.00",
"netAmount": "498.00",
"destinationAddress": "TRXabc123def456ghi789jkl",
"status": "PENDING",
"createdAt": "2026-03-06T11:00:00.000Z"
}
}Get a Withdrawal
Retrieve a single withdrawal request by its ID.
/v1/withdrawals/:idPENDING_REVIEW— Awaiting admin reviewAPPROVED— Approved, queued for processingBROADCAST— Submitted to TRON networkCONFIRMED— Confirmed on-chainREJECTED— Rejected by admin reviewFAILED— Broadcast failed — balance refundedCANCELLED— Cancelled before broadcastCancel a Withdrawal
Cancels a withdrawal request that is still in PENDING_REVIEW status (before it is approved and broadcast to the TRON network). The amount is immediately returned to your balance.
/v1/withdrawals/:idWebhooks
Overview
ZyndPay sends HTTP POST requests to your registered endpoint URLs when payment events occur. Register endpoints from the Webhooks page in your merchant dashboard.
2xx status within 10 seconds. Failed deliveries are retried with exponential backoff up to 5 times over ~2 hours.{
"id": "wh_cma1xyz8f0001yx5k",
"type": "payin.confirmed",
"data": {
"id": "cma1xyz8f0001yx5k9abc1234",
"amount": "100.00",
"fee": "3.00",
"netAmount": "97.00",
"status": "CONFIRMED",
"externalRef": "order_123",
"txHash": "abc123def456...",
"confirmedAt": "2026-03-06T11:01:02.000Z"
},
"createdAt": "2026-03-06T11:01:03.000Z"
}Signature Verification
Every webhook delivery includes an X-ZyndPay-Signature header. Always verify it before processing the event.
X-ZyndPay-Signature: t=1741258862,v1=abc123def456...The signature is HMAC-SHA256(timestamp + "." + raw_body, webhook_secret). Always use the raw request body — parsing JSON first will cause verification failures.
const crypto = require('crypto');
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-zyndpay-signature'];
const [tPart, v1Part] = sig.split(',');
const timestamp = tPart.split('=')[1];
const received = v1Part.split('=')[1];
// Reject events older than 5 minutes
const age = Math.abs(Date.now() / 1000 - parseInt(timestamp));
if (age > 300) return res.status(400).send('Webhook too old');
const expected = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(`${timestamp}.${req.body}`)
.digest('hex');
// Use timing-safe comparison to prevent timing attacks
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received))) {
return res.status(400).send('Invalid signature');
}
const event = JSON.parse(req.body);
// Handle event...
res.json({ received: true });
});import hmac, hashlib, time
def verify_webhook(payload: bytes, sig_header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in sig_header.split(","))
timestamp = parts["t"]
received = parts["v1"]
# Reject events older than 5 minutes
if abs(time.time() - int(timestamp)) > 300:
return False
expected = hmac.new(
secret.encode(),
f"{timestamp}.{payload.decode()}".encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, received)Events
| Parameter | Type | Required | Description |
|---|---|---|---|
payin.created | event | optional | Fired when a new payin is created and a deposit address is generated. |
payin.confirming | event | optional | Fired when funds are received and awaiting 20 block confirmations. |
payin.confirmed | event | optional | Fired when a payin reaches 20 on-chain confirmations. Merchant balance is credited. |
payin.expired | event | optional | Fired when a payin address expires without receiving the expected amount. |
payin.underpaid | event | optional | Fired when a payin receives less than the requested amount. |
payin.overpaid | event | optional | Fired when a payin receives more than the requested amount. |
withdrawal.requested | event | optional | Fired when a new withdrawal is requested and queued for review. |
withdrawal.confirmed | event | optional | Fired when a withdrawal is confirmed on-chain. |
withdrawal.failed | event | optional | Fired when a withdrawal broadcast fails. Amount is refunded to balance. |
SDKs & Plugins
Node.js SDK
The official TypeScript SDK with full type safety and built-in webhook verification.
npm install @zyndpay/sdkconst { ZyndPay } = require('@zyndpay/sdk');
const client = new ZyndPay({
apiKey: 'zyp_live_sk_...',
webhookSecret: process.env.ZYNDPAY_WEBHOOK_SECRET,
});
// Create a payment
const payment = await client.payins.create({
amount: '100.00',
externalRef: 'order_123',
});
console.log(payment.depositAddress);
// Verify a webhook (uses raw body, not parsed JSON)
const event = client.webhooks.verify(
rawBody,
req.headers['x-zyndpay-signature']
);Python SDK
The official Python SDK built on httpx with sync and async support.
pip install zyndpayfrom zyndpay import ZyndPay
client = ZyndPay(
api_key="zyp_live_sk_...",
webhook_secret=os.environ.get("ZYNDPAY_WEBHOOK_SECRET"),
)
payment = client.payins.create(
amount="100.00",
external_ref="order_123"
)
print(payment["depositAddress"])WooCommerce Plugin
Accept USDT payments in your WordPress / WooCommerce store without writing a single line of code. The plugin generates deposit addresses, polls confirmation status, and auto-fulfills orders.
Installation
- 1.Download the plugin ZIP from the ZyndPay GitHub repository.
- 2.In WordPress admin go to Plugins → Add New → Upload Plugin.
- 3.Upload the ZIP and click Activate.
- 4.Navigate to WooCommerce → Settings → Payments → ZyndPay.
- 5.Enter your API key and webhook secret, then save.
Sandbox
Overview
The sandbox lets you test your entire integration without touching real funds or the TRON network. Use your zyp_test_sk_... key — the API is identical to production.
Isolated balance
Sandbox funds are completely separate from your live balance.
Identical API
Same endpoints, same response shapes, same webhook events.
Instant confirm
Simulate payment confirmation in one API call — no waiting.
Free forever
Available to all merchants from day one, no approval needed.
Simulate Payment
Instantly confirms a sandbox payin, fires the payin.confirmed webhook, and credits the sandbox balance — exactly like a real on-chain confirmation.
/v1/sandbox/payin/:id/simulatecurl -X POST \
https://api.zyndpay.com/v1/sandbox/payin/{id}/simulate \
-H "X-Api-Key: zyp_test_sk_..."{
"success": true,
"data": {
"id": "cma1xyz8f0001yx5k9abc1234",
"status": "CONFIRMED",
"amountReceived": "100.00",
"txHash": "sandbox_tx_abc123",
"confirmedAt": "2026-03-06T11:00:01.000Z"
}
}Reference
Error Codes
All errors return the same JSON envelope. Use the error field to handle errors programmatically — the message is human-readable and may change.
{
"success": false,
"error": "AMOUNT_TOO_LOW",
"message": "Minimum payin amount is 20 USDT",
"statusCode": 400,
"requestId": "req_abc123"
}| Parameter | Type | Required | Description |
|---|---|---|---|
UNAUTHORIZED | 401 | optional | API key is missing or invalid. |
FORBIDDEN | 403 | optional | API key does not have the required scope. |
NOT_FOUND | 404 | optional | The requested resource does not exist. |
AMOUNT_TOO_LOW | 400 | optional | Payin amount is below the 20 USDT minimum. |
EXTERNAL_REF_TAKEN | 409 | optional | A payin with this externalRef already exists for your account. |
INSUFFICIENT_BALANCE | 402 | optional | Your balance is too low for the requested withdrawal amount. |
ADDRESS_NOT_FOUND | 404 | optional | The addressId does not match any saved withdrawal address on your account. |
IDEMPOTENCY_CONFLICT | 409 | optional | Idempotency key reused with different request parameters. |
RATE_LIMITED | 429 | optional | Too many requests. Retry after the Retry-After header value. |
INTERNAL_ERROR | 500 | optional | Unexpected server error. Contact support if it persists. |
Rate Limits
Limits are enforced per API key. Exceeded requests receive a 429 response with a Retry-After header.
| Parameter | Type | Required | Description |
|---|---|---|---|
Default | 100 req / 60s | optional | Applies to all endpoints per API key. |
POST /v1/payin | 60 req / 60s | optional | Payin creation is additionally rate-limited. |
POST /v1/payout | 20 req / 60s | optional | Payout creation has a stricter limit. |
[email protected].