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 payment is 1 USDT. Merchant fee: 1% per transaction. Customers pay a $1.00 network fee on external payments. Withdrawal fee: 1% (min $1.50). Payout fee: 1% (min $1.50, max $5).
https://api.zyndpay.io/v1{
"success": true,
"data": { ... },
"meta": { "page": 1, "limit": 20, "total": 100 }
}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.io/v1/payments \
-H "X-Api-Key: zyp_live_sk_..."| Parameter | Type | Required | Description |
|---|---|---|---|
live_sk_* | string | required | Live secret key — full API access. Never expose client-side. |
live_pk_* | string | optional | Live publishable key — limited read access, safe for browsers. |
test_sk_* | string | required | Sandbox secret key — identical to live but no real transactions. |
test_pk_* | string | optional | Sandbox publishable key. |
rk_* | string | optional | Restricted key — scoped to specific operations only. |
Quick Start
Accept your first USDT payment in under 5 minutes. Select your language below — each example shows install, create a pay-in, handle the webhook, and check status.
// 1. Install
// npm install @zyndpay/sdk
// 2. Initialize
import { ZyndPay } from '@zyndpay/sdk';
const zyndpay = new ZyndPay({
apiKey: process.env.ZYNDPAY_API_KEY!,
webhookSecret: process.env.ZYNDPAY_WEBHOOK_SECRET,
});
// 3. Create a pay-in and get a payment URL
const payin = await zyndpay.payins.create({
amount: '100',
externalRef: 'order_' + Date.now(), // your order ID
successUrl: 'https://yoursite.com/success',
cancelUrl: 'https://yoursite.com/cancel',
});
console.log('Redirect customer to:', payin.paymentUrl);
// Or show payin.address if you want to display the TRON address directly
// 4. Handle the webhook (Express.js)
import express from 'express';
const app = express();
app.post('/webhooks/zyndpay', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-zyndpay-signature'] as string;
let event;
try {
event = zyndpay.webhooks.verify(req.body, signature);
} catch {
return res.status(400).send('Invalid signature');
}
switch (event.event) {
case 'payin.confirmed':
// Payment received — fulfill the order
console.log('Paid:', event.data.externalRef, event.data.amount, 'USDT');
break;
case 'payin.expired':
console.log('Expired:', event.data.externalRef);
break;
case 'payin.underpaid':
console.log('Underpaid:', event.data.externalRef,
'got', event.data.amount, 'expected', event.data.amountRequested);
break;
}
res.json({ received: true });
});
// 5. Poll / check status programmatically
const tx = await zyndpay.payins.get(payin.transactionId);
console.log('Status:', tx.status); // "CONFIRMED"Going Live
Before accepting real payments, work through this checklist. Most items take under 5 minutes each.
Create a Payin
/v1/paymentsdocs.createPayinP
| Parameter | Type | Required | Description |
|---|---|---|---|
amount | string | required | Amount in USDT as a decimal string (e.g. "100.00"). Minimum: "1.00". |
externalRef | string | optional | Your order or reference ID (optional). Must be unique per merchant if provided. |
expiresInSeconds | number | optional | Seconds until the address expires. Minimum: 900. Default: 1800 (30 minutes). |
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.io/v1/payments \
-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": {
"transactionId": "pay_abc123...",
"address": "TRXabc123def456ghi789jkl",
"paymentUrl": "https://checkout.zyndpay.io/pay_abc123...",
"qrCodeUrl": "data:image/png;base64,iVBOR...",
"amount": "100.00",
"amountExpected": "101.00",
"networkFee": "1",
"currency": "USDT_TRC20",
"chain": "TRON",
"status": "AWAITING_PAYMENT",
"expiresAt": "2026-03-06T12:00:00.000Z"
}
}Get a Payin
/v1/payments/:id{
"success": true,
"data": {
"id": "f4b2cb0f-08ce-4408-88d4-0a678ca0aae2",
"address": "TRXabc123def456ghi789jkl",
"amount": "100",
"amountReceived": "100.00",
"zyndpayFee": "1.00",
"status": "CONFIRMED",
"txHash": "abc123def456...",
"externalRef": "order_123",
"currency": "USDT_TRC20",
"chain": "TRON",
"paymentUrl": "https://checkout.zyndpay.io/f4b2cb0f...",
"isSandbox": false,
"createdAt": "2026-03-06T11:00:00.000Z",
"updatedAt": "2026-03-06T11:01:02.000Z"
}
}PENDINGAwaiting paymentCONFIRMINGFunds received, waiting for 5 confirmationsCONFIRMED5+ confirmations — balance creditedUNDERPAIDLess than expected receivedOVERPAIDMore than expected receivedEXPIREDAddress expired without paymentList Payins
/v1/payments| Parameter | Type | Required | Description |
|---|---|---|---|
page | number | optional | Page number. Default: 1. |
limit | number | 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": {
"items": [ ... ],
"meta": {
"page": 1,
"limit": 20,
"total": 134,
"totalPages": 7,
"hasNext": true,
"hasPrev": false
}
}
}Create a Paylink
/v1/paylinksdocs.createPaylinkP
| Parameter | Type | Required | Description |
|---|---|---|---|
type | string | required | Payment link type: FIXED (default), VARIABLE, or RECURRING. |
products | array | required | Array of products. At least one product is required. |
products[].name | string | required | Product name displayed to the customer. |
products[].price | string | required | Price in USDT as a decimal string. |
products[].productType | string | required | PHYSICAL (default) or DIGITAL. |
products[].digitalUrl | string | optional | External URL for digital product delivery (Google Drive, Mega, etc.). |
collectEmail | string | optional | Customer email collection: HIDDEN (default), OPTIONAL, or REQUIRED. |
collectName | string | optional | Customer name collection: HIDDEN, OPTIONAL, or REQUIRED. |
brandColor | string | optional | Hex color for branded checkout page (e.g. #635BFF). |
successUrl | string | optional | URL to redirect the customer after a successful payment. |
recurringInterval | string | optional | Billing interval for recurring paylinks: WEEKLY, MONTHLY, or YEARLY. |
curl -X POST https://api.zyndpay.io/v1/paylinks \
-H "X-Api-Key: zyp_live_sk_..." \
-H "Content-Type: application/json" \
-d '{
"type": "FIXED",
"products": [
{
"name": "Premium Course",
"price": "49.99",
"productType": "DIGITAL",
"digitalUrl": "https://drive.google.com/file/d/abc123"
}
],
"collectEmail": "REQUIRED",
"brandColor": "#635BFF"
}'{
"success": true,
"data": {
"id": "pl_cma1xyz8f0001yx5k",
"slug": "aB3dE6fG7hI9",
"type": "FIXED",
"status": "ACTIVE",
"paymentUrl": "https://dashboard.zyndpay.io/pay/link/aB3dE6fG7hI9",
"products": [
{
"id": "prod_abc123",
"name": "Premium Course",
"price": "49.990000000000000000",
"productType": "DIGITAL",
"digitalUrl": "https://drive.google.com/file/d/abc123"
}
],
"createdAt": "2026-03-08T10:00:00.000Z"
}
}Get a Paylink
/v1/paylinks/:id{
"success": true,
"data": {
"id": "pl_cma1xyz8f0001yx5k",
"slug": "aB3dE6fG7hI9",
"type": "FIXED",
"status": "ACTIVE",
"paymentUrl": "https://dashboard.zyndpay.io/pay/link/aB3dE6fG7hI9",
"products": [
{
"id": "prod_abc123",
"name": "Premium Course",
"price": "49.990000000000000000",
"productType": "DIGITAL",
"digitalUrl": "https://drive.google.com/file/d/abc123"
}
],
"createdAt": "2026-03-08T10:00:00.000Z"
}
}List Paylinks
/v1/paylinks| Parameter | Type | Required | Description |
|---|---|---|---|
page | number | optional | Page number. Default: 1. |
limit | number | optional | Results per page. Default: 20. Max: 100. |
{
"success": true,
"data": {
"items": [ ... ],
"meta": {
"page": 1,
"limit": 20,
"total": 134,
"totalPages": 7,
"hasNext": true,
"hasPrev": false
}
}
}Checkout
/v1/pay/link/:slug/checkoutPublic endpoint (no auth required). Creates an order and payin from a payment link. The customer receives a TRON deposit address to send USDT to.
| Parameter | Type | Required | Description |
|---|---|---|---|
items | array | required | Array of product IDs and quantities to purchase. |
customerEmail | string | optional | Customer email address (required for RECURRING and digital products). |
customerName | string | optional | Customer name (optional). |
curl -X POST https://api.zyndpay.io/v1/pay/link/aB3dE6fG7hI9/checkout \
-H "Content-Type: application/json" \
-d '{
"items": [
{ "productId": "prod_abc123", "quantity": 1 }
],
"customerEmail": "[email protected]",
"customerName": "John Doe"
}'{
"success": true,
"data": {
"orderId": "ord_xyz789",
"transactionId": "cma2abc9g0002yz6l0def",
"address": "TRXabc123def456ghi789jkl",
"amount": "49.99",
"expiresAt": "2026-03-08T11:00:00.000Z"
}
}Create a Payout
/v1/payoutdocs.createPayoutP
| Parameter | Type | Required | Description |
|---|---|---|---|
amount | string | required | Amount in USDT to send (e.g. "50.00"). Minimum: "5.00". A fee of 1% (min $1.50, max $5) is deducted from your balance on top of the payout amount. |
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.io/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": "1.50",
"requiresManualApproval": false,
"currentPayinFee": "1%",
"currentTier": "flat"
}
}Get a Payout
/v1/payout/:id{
"success": true,
"data": {
"transactionId": "cma2abc9g0002yz6l0def5678",
"status": "PROCESSING",
"processingFee": "1.50",
"requiresManualApproval": false,
"currentPayinFee": "1%",
"currentTier": "flat"
}
}PENDINGAwaiting manual approval (amount > $50K)PROCESSINGApproved, preparing broadcastBROADCASTSubmitted to TRON networkCONFIRMEDConfirmed on-chainFAILEDBroadcast failed — balance refundedCANCELLEDCancelled before broadcastList Payouts
/v1/payout{
"success": true,
"data": {
"items": [ ... ],
"meta": {
"page": 1,
"limit": 20,
"total": 42,
"totalPages": 3,
"hasNext": true,
"hasPrev": false
}
}
}Request a Withdrawal
/v1/withdrawalsdocs.requestWithdrawalP
| Parameter | Type | Required | Description |
|---|---|---|---|
amount | string | required | Amount in USDT to withdraw (e.g. "500.00"). Minimum: "5.00". A fee of 1% (min $1.50) is deducted from this amount. |
whitelistAddressId | string | optional | ID of the saved withdrawal address to use. Defaults to your primary address if omitted. |
curl -X POST https://api.zyndpay.io/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",
"whitelistAddressId": "addr_cma1xyz8f0001yx5k"
}'{
"success": true,
"data": {
"id": "wdr_cma1xyz8f0001yx5k",
"amount": "500.00",
"fee": "5.00",
"netAmount": "495.00",
"destinationAddress": "TRXabc123def456ghi789jkl",
"status": "PENDING",
"createdAt": "2026-03-06T11:00:00.000Z"
}
}Get a Withdrawal
/v1/withdrawals/:id{
"success": true,
"data": {
"id": "wdr_cma1xyz8f0001yx5k",
"amount": "500.00",
"fee": "5.00",
"netAmount": "495.00",
"destinationAddress": "TRXabc123def456ghi789jkl",
"status": "PENDING",
"createdAt": "2026-03-06T11:00:00.000Z"
}
}PENDING_REVIEWAwaiting admin reviewAPPROVEDApproved, queued for processingBROADCASTSubmitted to TRON networkCONFIRMEDConfirmed on-chainREJECTEDRejected by admin reviewFAILEDBroadcast failed — balance refundedCANCELLEDCancelled before broadcastCancel a Withdrawal
/v1/withdrawals/:iddocs.cancelWithdrawalP
Overview
docs.webhooksOverviewP
{
"event": "payin.confirmed",
"data": {
"transactionId": "cma1xyz8f0001yx5k9abc1234",
"status": "CONFIRMED",
"currency": "USDT_TRC20",
"chain": "TRON",
"externalRef": "order_123",
"amount": "100.00",
"amountRequested": "100.00",
"txHash": "abc123def456...",
"confirmedAt": "2026-03-06T11:01:02.000Z"
},
"createdAt": "2026-03-06T11:01:03.000Z"
}Signature Verification
docs.webhookVerifyP1
t=1680000000,v1=5257a869e7ecebeda32af...docs.webhookSignatureNote
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 });
});Events
| Event | Type | Required | Description |
|---|---|---|---|
payin.created | event | required | Fired when a new payin is created and a deposit address is generated. |
payin.confirming | event | required | Fired when funds are received and awaiting 5 block confirmations. |
payin.confirmed | event | required | Fired when a payin reaches 5 on-chain confirmations. Merchant balance is credited. |
payin.expired | event | required | Fired when a payin address expires without receiving the expected amount. |
payin.failed | event | required | Fired when a payin fails due to a network or processing error. |
payin.underpaid | event | required | Fired when a payin receives less than the requested amount. |
payin.overpaid | event | required | Fired when a payin receives more than the requested amount. |
withdrawal.requested | event | required | Fired when a new withdrawal is requested and queued for review. |
withdrawal.approved | event | required | Fired when a withdrawal passes review and is approved for on-chain broadcast. |
withdrawal.confirmed | event | required | Fired when a withdrawal is confirmed on-chain. |
withdrawal.failed | event | required | Fired when a withdrawal broadcast fails. Amount is refunded to balance. |
aml.flagged | event | required | Fired when a transaction is flagged by the AML screening engine for manual review. |
Payload Schemas
Every webhook is a JSON POST with the same envelope: event, data, createdAt. The shape of data depends on the event type. Click an event below to see its exact payload.
{
"event": "payin.confirmed",
"data": {
"transactionId": "cma1xyz8f0001yx5k9abc1234",
"status": "CONFIRMED",
"currency": "USDT_TRC20",
"chain": "TRON",
"externalRef": "order_123",
"amount": "100.00",
"amountRequested": "100.00",
"txHash": "abc123def456...",
"confirmedAt": "2026-03-06T11:01:02.000Z"
},
"createdAt": "2026-03-06T11:01:03.000Z"
}{
"event": "payout.confirmed",
"data": {
"transactionId": "tx_sample_payout123",
"status": "CONFIRMED",
"currency": "USDT_TRC20",
"chain": "TRON",
"amount": "50.00",
"fee": "1.50",
"destinationAddress": "TXyz1234567890AbCdEfGhIjKlMnOpQrSt",
"txHash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
"externalRef": "vendor_invoice_456",
"confirmedAt": "2026-01-15T12:01:00.000Z"
},
"createdAt": "2026-01-15T12:01:05.000Z"
}{
"event": "withdrawal.confirmed",
"data": {
"transactionId": "wdr_sample_abc123",
"status": "CONFIRMED",
"currency": "USDT_TRC20",
"chain": "TRON",
"amount": "500.00",
"fee": "5.00",
"netAmount": "495.00",
"toAddress": "TXyz1234567890AbCdEfGhIjKlMnOpQrSt",
"txHash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
"confirmedAt": "2026-01-15T12:01:00.000Z"
},
"createdAt": "2026-01-15T12:01:05.000Z"
}Test Webhook
Send a test event to one of your registered webhook endpoints. Useful for verifying your endpoint is reachable and your signature verification code works correctly.
/v1/webhooks/test| Parameter | Type | Required | Description |
|---|---|---|---|
endpointId | string | required | The ID of the webhook endpoint to send the test event to. Get this from GET /webhooks/endpoints. |
eventType | string | required | The event type to simulate (e.g. payin.confirmed, payout.confirmed). Must be a valid event name. |
curl -X POST https://api.zyndpay.io/v1/webhooks/test \
-H "X-Api-Key: zyp_live_sk_..." \
-H "Content-Type: application/json" \
-d '{
"endpointId": "your-endpoint-id",
"eventType": "payin.confirmed"
}'{
"success": true,
"data": {
"message": "Test event sent",
"deliveryId": "9270e4aa-8391-4e3f-a70b-23c431714b84"
}
}Payload Schemas
Every webhook delivery shares the same envelope: an event string, a data object with event-specific fields, and a createdAt timestamp. Examples below show exact field names and types.
payin.createdFired immediately when a new payin is created and a TRON deposit address is assigned.
{
"event": "payin.created",
"data": {
"transactionId": "cma1xyz8f0001yx5k9abc1234",
"address": "TRXabc123def456ghi789jkl",
"amount": "100.00",
"currency": "USDT_TRC20",
"chain": "TRON",
"externalRef": "order_123",
"status": "AWAITING_PAYMENT",
"expiresAt": "2026-03-06T12:00:00.000Z",
"paymentUrl": "https://checkout.zyndpay.io/cma1xyz8f0001yx5k9abc1234"
},
"createdAt": "2026-03-06T11:00:00.000Z"
}payin.confirmedFired when a payin reaches 5 on-chain confirmations. Merchant balance is credited at this point.
{
"event": "payin.confirmed",
"data": {
"transactionId": "cma1xyz8f0001yx5k9abc1234",
"address": "TRXabc123def456ghi789jkl",
"amount": "100.00",
"amountReceived": "100.00",
"currency": "USDT_TRC20",
"chain": "TRON",
"externalRef": "order_123",
"txHash": "abc123def456...",
"status": "CONFIRMED",
"confirmedAt": "2026-03-06T11:01:02.000Z"
},
"createdAt": "2026-03-06T11:01:03.000Z"
}payin.expiredFired when a payin address expires without receiving the expected USDT amount.
{
"event": "payin.expired",
"data": {
"transactionId": "cma1xyz8f0001yx5k9abc1234",
"address": "TRXabc123def456ghi789jkl",
"amount": "100.00",
"currency": "USDT_TRC20",
"chain": "TRON",
"externalRef": "order_123",
"status": "EXPIRED",
"expiredAt": "2026-03-06T12:00:00.000Z"
},
"createdAt": "2026-03-06T12:00:01.000Z"
}payin.failedFired when a payin fails due to a network or processing error. No funds were received.
{
"event": "payin.failed",
"data": {
"transactionId": "cma1xyz8f0001yx5k9abc1234",
"address": "TRXabc123def456ghi789jkl",
"amount": "100.00",
"currency": "USDT_TRC20",
"chain": "TRON",
"externalRef": "order_123",
"status": "FAILED",
"reason": "Network processing error"
},
"createdAt": "2026-03-06T11:05:00.000Z"
}payin.underpaidFired when the customer sends less USDT than the requested amount. The shortfall field shows how much is missing.
{
"event": "payin.underpaid",
"data": {
"transactionId": "cma1xyz8f0001yx5k9abc1234",
"address": "TRXabc123def456ghi789jkl",
"amount": "100.00",
"amountReceived": "90.00",
"shortfall": "10.00",
"currency": "USDT_TRC20",
"chain": "TRON",
"externalRef": "order_123",
"txHash": "abc123def456...",
"status": "UNDERPAID",
"confirmedAt": "2026-03-06T11:01:02.000Z"
},
"createdAt": "2026-03-06T11:01:03.000Z"
}payin.overpaidFired when the customer sends more USDT than the requested amount. The surplus field shows the excess.
{
"event": "payin.overpaid",
"data": {
"transactionId": "cma1xyz8f0001yx5k9abc1234",
"address": "TRXabc123def456ghi789jkl",
"amount": "100.00",
"amountReceived": "110.00",
"surplus": "10.00",
"currency": "USDT_TRC20",
"chain": "TRON",
"externalRef": "order_123",
"txHash": "abc123def456...",
"status": "OVERPAID",
"confirmedAt": "2026-03-06T11:01:02.000Z"
},
"createdAt": "2026-03-06T11:01:03.000Z"
}payout.confirmedFired when an outbound payout is confirmed on-chain. The txHash is the on-chain transaction ID.
{
"event": "payout.confirmed",
"data": {
"transactionId": "cma2abc9g0002yz6l0def5678",
"destinationAddress": "TXYZabc123def456ghi789jkl012mno345",
"amount": "50.00",
"fee": "1.50",
"currency": "USDT_TRC20",
"chain": "TRON",
"externalRef": "payout_vendor_456",
"txHash": "def456abc123...",
"status": "CONFIRMED",
"confirmedAt": "2026-03-06T11:05:00.000Z"
},
"createdAt": "2026-03-06T11:05:01.000Z"
}payout.failedFired when a payout broadcast fails. The amount is automatically refunded to your merchant balance.
{
"event": "payout.failed",
"data": {
"transactionId": "cma2abc9g0002yz6l0def5678",
"destinationAddress": "TXYZabc123def456ghi789jkl012mno345",
"amount": "50.00",
"currency": "USDT_TRC20",
"chain": "TRON",
"externalRef": "payout_vendor_456",
"status": "FAILED",
"reason": "Broadcast failed — balance refunded",
"failedAt": "2026-03-06T11:05:00.000Z"
},
"createdAt": "2026-03-06T11:05:01.000Z"
}withdrawal.approvedFired when a withdrawal request passes admin review and is queued for on-chain broadcast.
{
"event": "withdrawal.approved",
"data": {
"id": "wdr_cma1xyz8f0001yx5k",
"amount": "500.00",
"fee": "5.00",
"netAmount": "495.00",
"destinationAddress": "TRXabc123def456ghi789jkl",
"currency": "USDT_TRC20",
"chain": "TRON",
"status": "APPROVED",
"approvedAt": "2026-03-06T12:00:00.000Z"
},
"createdAt": "2026-03-06T12:00:01.000Z"
}withdrawal.confirmedFired when a withdrawal is confirmed on-chain. netAmount is what your wallet actually received after fees.
{
"event": "withdrawal.confirmed",
"data": {
"id": "wdr_cma1xyz8f0001yx5k",
"amount": "500.00",
"fee": "5.00",
"netAmount": "495.00",
"destinationAddress": "TRXabc123def456ghi789jkl",
"currency": "USDT_TRC20",
"chain": "TRON",
"txHash": "ghi789abc123...",
"status": "CONFIRMED",
"confirmedAt": "2026-03-06T12:30:00.000Z"
},
"createdAt": "2026-03-06T12:30:01.000Z"
}withdrawal.failedFired when a withdrawal broadcast fails. The amount is automatically refunded to your balance.
{
"event": "withdrawal.failed",
"data": {
"id": "wdr_cma1xyz8f0001yx5k",
"amount": "500.00",
"destinationAddress": "TRXabc123def456ghi789jkl",
"currency": "USDT_TRC20",
"chain": "TRON",
"status": "FAILED",
"reason": "Broadcast failed — balance refunded",
"failedAt": "2026-03-06T12:30:00.000Z"
},
"createdAt": "2026-03-06T12:30:01.000Z"
}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.address);
// Verify a webhook (uses raw body, not parsed JSON)
const event = client.webhooks.verify(
rawBody,
req.headers['x-zyndpay-signature']
);TypeScript Quickstart
Accept your first USDT payment in under 5 minutes. This example uses Express — swap in your framework of choice.
import { ZyndPay } from '@zyndpay/sdk';
import express from 'express';
// 1. Initialize the SDK
const client = new ZyndPay({
apiKey: process.env.ZYNDPAY_API_KEY, // zyp_live_sk_...
webhookSecret: process.env.WEBHOOK_SECRET,
});
// 2. Create a pay-in (generates a TRON deposit address for your customer)
const payment = await client.payins.create({
amount: '100.00',
externalRef: 'order_123',
expiresInSeconds: 3600,
successUrl: 'https://yoursite.com/payment/success',
});
// Send payment.paymentUrl or payment.address to your customer
console.log('Deposit address:', payment.address);
console.log('Payment page:', payment.paymentUrl);
// 3. Handle webhook events
const app = express();
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
// Always verify using the raw body — never parsed JSON
const event = client.webhooks.verify(
req.body,
req.headers['x-zyndpay-signature'] as string,
);
switch (event.event) {
case 'payin.confirmed':
console.log(`Payment confirmed: ${event.data.amount} USDT — ${event.data.externalRef}`);
// Fulfill the order, unlock access, send receipt, etc.
break;
case 'payin.expired':
console.log(`Payment expired: ${event.data.externalRef}`);
break;
}
res.json({ received: true });
});
// 4. Check payment status anytime
const status = await client.payins.get(payment.transactionId);
console.log('Status:', status.status); // AWAITING_PAYMENT → CONFIRMING → CONFIRMEDPython SDK
docs.pythonSdkP
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["address"])Python Quickstart
Accept your first USDT payment in under 5 minutes. This example uses Flask — swap in your framework of choice.
import os
from zyndpay import ZyndPay
from flask import Flask, request, jsonify
# 1. Initialize the SDK
client = ZyndPay(
api_key=os.environ["ZYNDPAY_API_KEY"], # zyp_live_sk_...
webhook_secret=os.environ["WEBHOOK_SECRET"],
)
# 2. Create a pay-in (generates a TRON deposit address for your customer)
payment = client.payins.create(
amount="100.00",
external_ref="order_123",
expires_in_seconds=3600,
success_url="https://yoursite.com/payment/success",
)
# Send payment["paymentUrl"] or payment["address"] to your customer
print("Deposit address:", payment["address"])
print("Payment page:", payment["paymentUrl"])
# 3. Handle webhook events
app = Flask(__name__)
@app.post("/webhook")
def handle_webhook():
# Always verify using the raw body — never parsed JSON
event = client.webhooks.verify(
request.get_data(),
request.headers.get("X-ZyndPay-Signature"),
)
if event["event"] == "payin.confirmed":
data = event["data"]
print(f"Payment confirmed: {data['amount']} USDT — {data['externalRef']}")
# Fulfill the order, unlock access, send receipt, etc.
elif event["event"] == "payin.expired":
print("Payment expired:", event["data"]["externalRef"])
return jsonify(received=True)
# 4. Check payment status anytime
status = client.payins.get(payment["transactionId"])
print("Status:", status["status"]) # AWAITING_PAYMENT → CONFIRMING → CONFIRMEDPHP SDK
The official PHP SDK with cURL-based HTTP client and webhook verification. Requires PHP 8.0+.
composer require zyndpay/zyndpay-php<?php
require_once 'vendor/autoload.php';
$client = new ZyndPay\ZyndPay('zyp_live_sk_...', [
'webhook_secret' => getenv('ZYNDPAY_WEBHOOK_SECRET'),
]);
// Create a payment
$payment = $client->payins->create([
'amount' => '100.00',
'externalRef' => 'order_123',
]);
echo $payment['address'];
// Verify a webhook
$event = $client->webhooks->verify(
$rawBody,
$_SERVER['HTTP_X_ZYNDPAY_SIGNATURE']
);PHP Quickstart
Accept your first USDT payment in under 5 minutes. This example uses plain PHP — adapt the webhook handler to your framework.
<?php
require_once 'vendor/autoload.php';
// 1. Initialize the SDK
$client = new ZyndPay\ZyndPay(
getenv('ZYNDPAY_API_KEY'), // zyp_live_sk_...
['webhook_secret' => getenv('WEBHOOK_SECRET')]
);
// 2. Create a pay-in (generates a TRON deposit address for your customer)
$payment = $client->payins->create([
'amount' => '100.00',
'externalRef' => 'order_123',
'expiresInSeconds' => 3600,
'successUrl' => 'https://yoursite.com/payment/success',
]);
// Send $payment['paymentUrl'] or $payment['address'] to your customer
echo "Deposit address: " . $payment['address'] . "\n";
echo "Payment page: " . $payment['paymentUrl'] . "\n";
// 3. Handle webhook events (in your webhook endpoint file)
$rawBody = file_get_contents('php://input');
$sigHeader = $_SERVER['HTTP_X_ZYNDPAY_SIGNATURE'];
$event = $client->webhooks->verify($rawBody, $sigHeader);
switch ($event['event']) {
case 'payin.confirmed':
$data = $event['data'];
echo "Payment confirmed: {$data['amount']} USDT — {$data['externalRef']}\n";
// Fulfill the order, unlock access, send receipt, etc.
break;
case 'payin.expired':
echo "Payment expired: " . $event['data']['externalRef'] . "\n";
break;
}
http_response_code(200);
echo json_encode(['received' => true]);
// 4. Check payment status anytime
$status = $client->payins->get($payment['transactionId']);
echo "Status: " . $status['status'] . "\n"; // AWAITING_PAYMENT → CONFIRMING → CONFIRMEDRaw PHP implementation without the SDK. Produces the same HMAC-SHA256 check as the Node.js and Python examples above.
<?php
// IMPORTANT: read raw body before any JSON parsing
$payload = file_get_contents('php://input');
$sigHeader = $_SERVER['HTTP_X_ZYNDPAY_SIGNATURE'] ?? '';
function verifyWebhook(string $payload, string $sigHeader, string $secret): bool {
$parts = [];
foreach (explode(',', $sigHeader) as $part) {
[$k, $v] = explode('=', $part, 2);
$parts[$k] = $v;
}
if (empty($parts['t']) || empty($parts['v1'])) {
return false;
}
// Reject events older than 5 minutes
if (abs(time() - (int) $parts['t']) > 300) {
return false;
}
$expected = hash_hmac('sha256', $parts['t'] . '.' . $payload, $secret);
// Timing-safe comparison to prevent timing attacks
return hash_equals($expected, $parts['v1']);
}
if (!verifyWebhook($payload, $sigHeader, getenv('ZYNDPAY_WEBHOOK_SECRET'))) {
http_response_code(400);
echo 'Invalid signature';
exit;
}
$event = json_decode($payload, true);
// Handle $event['event'] ...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.
Download the plugin ZIP from the ZyndPay GitHub repository.
In WordPress admin go to Plugins → Add New → Upload Plugin.
Upload the ZIP and click Activate.
Navigate to WooCommerce → Settings → Payments → ZyndPay.
Enter your API key and webhook secret, then save.
Overview
docs.sandboxOverviewP
Testing Guide
Follow these steps to test your full payment flow end-to-end before going live.
# Using the sandbox API key (zyp_test_sk_...)
curl -X POST https://api.zyndpay.io/v1/payments \
-H "X-Api-Key: zyp_test_sk_..." \
-H "Content-Type: application/json" \
-d '{
"amount": "100",
"externalRef": "test_order_001",
"sandbox": true
}'import { ZyndPay } from '@zyndpay/sdk';
const zyndpay = new ZyndPay({ apiKey: 'zyp_test_sk_...' });
// 1. Create a sandbox payin
const payin = await zyndpay.payins.create({
amount: '100',
sandbox: true,
externalRef: 'test_order_001',
});
// 2. Instantly simulate confirmation (fires the payin.confirmed webhook)
const result = await zyndpay.payins.simulate(payin.transactionId);
console.log('Simulated status:', result.status); // "CONFIRMED"
// 3. Confirm the status
const tx = await zyndpay.payins.get(payin.transactionId);
console.log('Final status:', tx.status); // "CONFIRMED"# Reset all sandbox transactions (useful for clean test runs)
curl -X POST https://api.zyndpay.io/v1/sandbox/reset \
-H "X-Api-Key: zyp_test_sk_..."Simulate Payment
/v1/sandbox/payments/:id/simulatedocs.sandboxSimulateP
curl -X POST \
https://api.zyndpay.io/v1/sandbox/payments/{id}/simulate \
-H "X-Api-Key: zyp_test_sk_..."{
"success": true,
"data": {
"message": "Payin simulation triggered",
"transactionId": "f4b2cb0f-08ce-4408-88d4-0a678ca0aae2"
}
}Overview
docs.subsOverviewP
Setup
Get your channel monetized in 60 seconds:
Bot Commands
Available commands for channel owners:
| Command | Type | Required | Description |
|---|---|---|---|
/setup | command | required | Configure subscription price, billing interval, and channel settings. |
/plan | command | required | View your current plan and earnings. |
/withdraw | command | required | Withdraw your USDT balance to a TRON wallet address. |
/help | command | required | Show all available commands and usage information. |
/revenue | command | required | View revenue analytics — total earned, active subscribers, and monthly trends. |
/subscribers | command | required | List active subscribers with their subscription status and renewal dates. |
Error Codes
docs.errorCodesP
{
"success": false,
"error": {
"code": "AMOUNT_TOO_LOW",
"message": "Minimum payin amount is 1 USDT"
},
"statusCode": 400
}| Code | HTTP Status | Required | Description |
|---|---|---|---|
UNAUTHORIZED | 401 | required | API key is missing or invalid. Check that you are sending the X-Api-Key header and the key exists in your dashboard. |
FORBIDDEN | 403 | required | The API key does not have the required scope for this operation. Use a secret key (sk) not a publishable key (pk). |
NOT_FOUND | 404 | required | The requested resource does not exist or does not belong to your merchant account. |
VALIDATION_ERROR | 400 | required | Request body failed validation. The details field lists each invalid field and reason. |
AMOUNT_TOO_SMALL | 400 | required | Amount is below the minimum for this operation (1 USDT for payins, 5 USDT for payouts/withdrawals). |
AMOUNT_TOO_LARGE | 400 | required | Amount exceeds the maximum allowed for this operation or your current compliance tier. |
INVALID_ADDRESS | 400 | required | The TRON wallet address is malformed. Addresses must start with T and be 34 characters in base58 format. |
INVALID_TRANSACTION_STATUS | 400 | required | The operation is not allowed in the current transaction status (e.g. trying to cancel a confirmed payout). |
DUPLICATE_EXTERNAL_REF | 409 | required | A transaction with this externalRef already exists for your account. Use a unique reference per request, or omit it. |
INSUFFICIENT_BALANCE | 400 | required | Your USDT balance is too low for the requested payout or withdrawal amount. Check your balance at GET /v1/wallets/balance. |
KYB_REQUIRED | 403 | required | This operation requires completed KYB (Know Your Business) verification. Complete KYB in your dashboard to unlock full limits. |
COMPLIANCE_LIMIT_REACHED | 403 | required | Your monthly transaction volume has reached the compliance limit for your current tier. Complete KYB to increase your limits. |
AML_BLOCKED | 403 | required | This transaction was flagged by our AML screening engine and cannot be processed. Contact [email protected]. |
ADDRESS_NOT_FOUND | 404 | required | The whitelistAddressId does not match any saved withdrawal address on your account. Add addresses in your dashboard under Withdrawal Addresses. |
ADDRESS_NOT_WHITELISTED | 403 | required | The destination address is not on your merchant whitelist. Add it in your dashboard before sending funds to it. |
CANNOT_CANCEL | 400 | required | This resource can no longer be cancelled (it has already been approved, broadcast, or completed). |
MISSING_IDEMPOTENCY_KEY | 400 | required | This endpoint requires an Idempotency-Key header. Pass a unique UUID per request. |
IDEMPOTENCY_CONFLICT | 409 | required | An existing request with the same Idempotency-Key has different parameters. Use a new key for a different request. |
DUPLICATE_RESOURCE | 409 | required | A resource with this unique value already exists (e.g. duplicate email, slug, or field). |
LIVE_KEY_SANDBOX_REQUEST | 400 | required | You sent sandbox: true with a live API key. Use a zyp_test_sk_... key for sandbox requests. |
SANDBOX_KEY_LIVE_REQUEST | 400 | required | You sent a sandbox key but the resource is a live transaction. Use a zyp_live_sk_... key for production requests. |
RATE_LIMIT_EXCEEDED | 429 | required | Too many requests. Check the Retry-After response header for the number of seconds to wait before retrying. |
FREEMIUM_LIMIT_EXCEEDED | 403 | required | Monthly freemium volume limit ($14,500) reached. Complete KYB to unlock higher limits. |
INVALID_WEBHOOK_SIGNATURE | 400 | required | Webhook signature verification failed. Check your webhook secret and that you are using the raw request body. |
INTERNAL_ERROR | 500 | required | Unexpected server error. Include the requestId from the response when contacting [email protected]. |
Rate Limits
docs.rateLimitsP
| Endpoint | Rate Limit | Required | Description |
|---|---|---|---|
Default | 100/60s | required | Global rate limit per API key. |
POST /payments | 30/60s | required | Payin creation (POST /payments) per merchant. |
POST /payout | 10/60s | required | Payout creation (POST /payout) per merchant. |
POST /withdrawals | 5/60s | required | Withdrawal requests per merchant. |