@zyndpay/sdk
v1.7.3
Published
Official ZyndPay Node.js/TypeScript SDK — accept USDT payments with a few lines of code
Maintainers
Readme
@zyndpay/sdk
Official ZyndPay Node.js / TypeScript SDK — accept USDT TRC20 payments with a few lines of code.
Requirements
- Node.js 18+
- A ZyndPay account and API key
Installation
npm install @zyndpay/sdk
# or
pnpm add @zyndpay/sdk
# or
yarn add @zyndpay/sdkQuickstart
import { ZyndPay } from '@zyndpay/sdk';
const zyndpay = new ZyndPay({ apiKey: 'zyp_live_sk_...' });
// Create a payment request
const payin = await zyndpay.payins.create({ amount: '100' });
console.log(payin.address); // Send USDT TRC20 here
console.log(payin.paymentUrl); // Redirect your customer here
// Check your balance
const balance = await zyndpay.balances.get();
console.log(balance.balance); // e.g. "97.00"Configuration
const zyndpay = new ZyndPay({
apiKey: 'zyp_live_sk_...', // required
webhookSecret: 'whsec_...', // optional — needed for webhook verification
baseUrl: 'https://api.zyndpay.io/v1', // optional — override for self-hosted
timeout: 30_000, // optional — request timeout in ms (default: 30000)
maxRetries: 2, // optional — retries on network errors (default: 2)
});API key types
| Prefix | Type |
|---|---|
| zyp_live_sk_ | Live secret key |
| zyp_live_pk_ | Live publishable key |
| zyp_test_sk_ | Sandbox secret key |
| zyp_test_pk_ | Sandbox publishable key |
Payins
Create a payin
const payin = await zyndpay.payins.create({
amount: '100', // USDT amount (minimum 1)
externalRef: 'order_9f8e7d', // your internal order ID (optional)
expiresInSeconds: 3600, // 1 hour — default is 30min (optional)
metadata: { userId: 'usr_123' }, // stored as-is (optional)
successUrl: 'https://yoursite.com/success',
cancelUrl: 'https://yoursite.com/cancel',
});
console.log(payin.transactionId); // "uuid"
console.log(payin.address); // TRC20 deposit address
console.log(payin.paymentUrl); // hosted payment page URL
console.log(payin.qrCodeUrl); // QR code data URL
console.log(payin.amount); // "100"
console.log(payin.status); // "AWAITING_PAYMENT"
console.log(payin.expiresAt); // ISO timestampGet a payin
const payin = await zyndpay.payins.get('pay_abc123');List payins
const { items, total } = await zyndpay.payins.list({
status: 'CONFIRMED', // optional filter
page: 1,
limit: 20,
});Card payments (Visa / Mastercard)
Redirect the customer to a hosted checkout page. Amount is in fiat (XOF). Fee: 5%.
const payin = await zyndpay.payins.create({
amount: '65000', // fiat amount in XOF
currency: 'XOF',
paymentMethod: 'CARD',
externalRef: 'order_card_123',
successUrl: 'https://yoursite.com/success',
cancelUrl: 'https://yoursite.com/cancel',
});
// Redirect the customer to the hosted checkout
window.location.href = payin.hostedPaymentUrl!;Mobile Money payins (Orange BF / Moov BF)
Amount is in fiat (XOF). The customer stays on your page — no redirect. Fee: 3.5%.
const payin = await zyndpay.payins.create({
amount: '65000', // XOF amount
currency: 'XOF',
paymentMethod: 'MOBILE_MONEY',
customerPhone: '+22670000000', // customer phone in E.164 format (required)
operatorCode: 'ORANGE_BF', // optional — auto-detected from the phone prefix
externalRef: 'order_momo_456',
});
if (payin.nextStep === 'otp') {
// Prompt the customer for the OTP they received by SMS
const confirmed = await zyndpay.payins.submitOtp(payin.transactionId, '123456');
console.log(confirmed.status); // "CONFIRMING" → "CONFIRMED"
} else {
// nextStep === 'wait' — display instruction and wait for the webhook
console.log(payin.instruction); // e.g. "Confirm payment in your Orange Money app"
}Supported operatorCode values
| Code | Network | Country |
|---|---|---|
| ORANGE_BF | Orange | Burkina Faso |
| MOOV_BF | Moov | Burkina Faso |
Payin statuses
| Status | Description |
|---|---|
| PENDING | Just created |
| AWAITING_PAYMENT | Deposit address assigned, waiting for funds |
| CONFIRMING | Payment detected, waiting for confirmations |
| CONFIRMED | Payment confirmed — balance credited |
| EXPIRED | Payment window elapsed |
| OVERPAID | More than expected was sent |
| UNDERPAID | Less than expected was sent |
| FAILED | Processing failed |
Wallets, conversions, and FCFA payouts (multi-wallet API)
The multi-wallet API exposes one balance per (currency, rail) pair — for
example a USDT_TRC20 wallet plus an XOF mobile-money wallet.
// 1. List wallets
const wallets = await zyndpay.wallets.list();
const usdt = wallets.find((w) => w.currency === 'USDT_TRC20')!;
const xof = wallets.find((w) => w.currency === 'XOF')!;
// 2. Whitelist an FCFA mobile-money destination
const dest = await zyndpay.fiatDestinations.create({
kind: 'MOMO',
label: 'My Orange',
momoOperator: 'ORANGE',
momoPhone: '22670000000',
isPrimary: true,
});
// 3. Convert USDT → XOF (synchronous wallet-to-wallet)
await zyndpay.conversions.convertBetweenWallets({
fromWalletId: usdt.id,
toWalletId: xof.id,
fromAmount: '100',
});
// 4. Pay the FCFA balance out to the whitelisted destination
await zyndpay.withdrawals.create({
amount: '60000',
walletId: xof.id,
fiatDestinationId: dest.id,
});The legacy
conversions.create({ amount, channel, ... })is deprecated (sunset 2026-07-25). New integrations should use the two-stepconvertBetweenWallets+withdrawals.createflow above.
Paylinks
Payment links you can share with customers — fixed-price, variable-price, or recurring.
Create a paylink
const paylink = await zyndpay.paylinks.create({
title: 'Premium Plan',
type: 'FIXED', // 'FIXED' | 'VARIABLE' | 'RECURRING'
amount: '25', // USDT — omit for VARIABLE
currency: 'USD',
description: 'Monthly subscription',
successUrl: 'https://yoursite.com/thank-you',
cancelUrl: 'https://yoursite.com/cancel',
});
console.log(paylink.id); // "plk_abc123"
console.log(paylink.url); // shareable payment URL
console.log(paylink.status); // "ACTIVE"Get / list / update / delete
const paylink = await zyndpay.paylinks.get('plk_abc123');
const { items, total } = await zyndpay.paylinks.list({ status: 'ACTIVE', page: 1, limit: 20 });
await zyndpay.paylinks.update('plk_abc123', { title: 'New Title' });
await zyndpay.paylinks.delete('plk_abc123');Stats and orders
const stats = await zyndpay.paylinks.getStats('plk_abc123');
console.log(stats.totalRevenue, stats.orderCount);
const dashStats = await zyndpay.paylinks.getDashboardStats();
const { items } = await zyndpay.paylinks.listOrders('plk_abc123', { page: 1, limit: 50 });
const csv = await zyndpay.paylinks.exportOrdersCsv('plk_abc123');Promo codes
const promo = await zyndpay.paylinks.createPromoCode('plk_abc123', {
code: 'SAVE10',
discountType: 'PERCENT',
discountValue: 10,
maxUses: 100,
});
const codes = await zyndpay.paylinks.listPromoCodes('plk_abc123');
await zyndpay.paylinks.togglePromoCode('plk_abc123', promo.id, false); // deactivate
await zyndpay.paylinks.deletePromoCode('plk_abc123', promo.id);Templates
const tpl = await zyndpay.paylinks.createTemplate({ name: 'My Template', config: {} });
await zyndpay.paylinks.saveAsTemplate('plk_abc123', 'Saved template');
const templates = await zyndpay.paylinks.listTemplates();
await zyndpay.paylinks.deleteTemplate(tpl.id);Subscriptions (recurring paylinks)
const subs = await zyndpay.paylinks.listSubscriptions('plk_abc123');
await zyndpay.paylinks.cancelSubscription('plk_abc123', subs[0].id);Payouts
Send USDT directly to an external wallet address.
Estimate fees before submitting
const estimate = await zyndpay.payouts.estimate({
amount: '200',
destinationAddress: 'TXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
currency: 'USDT_TRC20',
chain: 'TRON',
});
console.log(estimate.fee); // network fee in USDT
console.log(estimate.netAmount); // amount recipient receivesCreate a payout
const payout = await zyndpay.payouts.create(
{
amount: '200',
destinationAddress: 'TXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
currency: 'USDT_TRC20', // default
chain: 'TRON', // default
externalRef: 'payout_order_789',
metadata: { note: 'vendor payment' },
},
'idempotency-key-456', // optional
);
console.log(payout.status); // "PENDING" → "BROADCAST" → "CONFIRMED"Get / list payouts
const tx = await zyndpay.payouts.get('payout_id');
const { items, total } = await zyndpay.payouts.list({
status: ['CONFIRMED', 'BROADCAST'],
from: '2026-01-01',
to: '2026-04-30',
page: 1,
limit: 50,
});Bulk Payments
Send to hundreds of addresses in a single batch — draft → validate → execute lifecycle.
// 1. Create a draft batch
const batch = await zyndpay.bulkPayments.create({}, 'idempotency-key');
// 2. Add recipients
await zyndpay.bulkPayments.addItems(batch.id, [
{ destinationAddress: 'TXaaa...', amount: '50', externalRef: 'emp_1' },
{ destinationAddress: 'TXbbb...', amount: '75', externalRef: 'emp_2' },
]);
// Or import from a CSV/XLSX file
// const template = await zyndpay.bulkPayments.downloadTemplate('csv');
// await zyndpay.bulkPayments.importFile(batch.id, fileBuffer, 'payroll.csv');
// 3. Validate (checks balance, calculates fees)
const validated = await zyndpay.bulkPayments.validate(batch.id);
console.log(validated.totalAmount, validated.totalFee);
// 4. Execute
const executed = await zyndpay.bulkPayments.execute(batch.id);
console.log(executed.status); // "PROCESSING"
// 5. Monitor
const detail = await zyndpay.bulkPayments.get(batch.id);
console.log(detail.items); // per-recipient status
// Retry failed items / cancel
await zyndpay.bulkPayments.retry(batch.id);
await zyndpay.bulkPayments.cancel(batch.id);
// Export results as CSV
const csv = await zyndpay.bulkPayments.export(batch.id);Batch statuses
| Status | Description |
|---|---|
| DRAFT | Building the batch |
| VALIDATED | Fees calculated, ready to execute |
| PROCESSING | Items being broadcast |
| COMPLETED | All items settled |
| PARTIALLY_COMPLETED | Some items failed |
| CANCELLED | Cancelled before execution |
Sandbox / Test Mode
Use your sandbox API key (zyp_test_sk_...) and pass sandbox: true when creating a payin. Then call simulate to instantly confirm it without real funds.
const zyndpay = new ZyndPay({ apiKey: 'zyp_test_sk_...' });
// Create a sandbox payin
const payin = await zyndpay.payins.create({
amount: '100',
sandbox: true,
});
// Instantly simulate confirmation
const confirmed = await zyndpay.payins.simulate(payin.transactionId);
console.log(confirmed.status); // "CONFIRMED"Withdrawals
Request a withdrawal
const withdrawal = await zyndpay.withdrawals.create(
{ amount: '50' }, // USDT amount
'idempotency-key-123' // optional idempotency key
);
console.log(withdrawal.status); // "PENDING_REVIEW"
console.log(withdrawal.fee); // "1.50" (1% fee, min $1.50)
console.log(withdrawal.netAmount); // "48.50"Get / list withdrawals
const withdrawal = await zyndpay.withdrawals.get('wdr_abc123');
const { items, total } = await zyndpay.withdrawals.list({
status: 'CONFIRMED',
page: 1,
limit: 20,
});Cancel a withdrawal
await zyndpay.withdrawals.cancel('wdr_abc123'); // only while PENDING_REVIEWWithdrawal statuses
| Status | Description |
|---|---|
| PENDING_REVIEW | Awaiting admin approval |
| APPROVED | Approved, queued for processing |
| PROCESSING | Being broadcast to blockchain |
| BROADCAST | Transaction sent |
| CONFIRMED | On-chain confirmed |
| REJECTED | Rejected by admin |
| CANCELLED | Cancelled by merchant |
| FAILED | Broadcast failed |
Transactions
// Get a single transaction
const tx = await zyndpay.transactions.get('txn_abc123');
// List with filters
const { items, total } = await zyndpay.transactions.list({
type: 'PAYIN', // 'PAYIN' | 'PAYOUT'
status: 'CONFIRMED',
fromDate: '2026-01-01',
toDate: '2026-03-31',
page: 1,
limit: 50,
});Balances
const balance = await zyndpay.balances.get();
console.log(balance.currency); // "USDT_TRC20"
console.log(balance.balance); // current balanceWebhooks
ZyndPay sends signed webhook events to your endpoint. Always verify the signature before processing.
Verify a webhook
import express from 'express';
import { ZyndPay } from '@zyndpay/sdk';
const zyndpay = new ZyndPay({
apiKey: 'zyp_live_sk_...',
webhookSecret: 'whsec_...',
});
const app = express();
// IMPORTANT: use raw body — do not parse JSON before verifying
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 (err) {
return res.status(400).send('Webhook signature verification failed');
}
// All payin events include: transactionId, status, currency, chain, externalRef
switch (event.event) {
case 'payin.confirmed':
// event.data also has: amount, amountRequested, txHash, confirmedAt
console.log('Payment confirmed:', event.data.externalRef, event.data.amount);
break;
case 'payin.expired':
console.log('Payment expired:', event.data.externalRef);
break;
case 'withdrawal.confirmed':
console.log('Withdrawal confirmed:', event.data);
break;
}
res.status(200).json({ received: true });
});Webhook event types
| Event | Trigger |
|---|---|
| payin.created | Payin created |
| payin.confirming | Payment detected on-chain |
| payin.confirmed | Payment fully confirmed |
| payin.expired | Payin expired before payment |
| payin.overpaid | More than expected received |
| payin.underpaid | Less than expected received |
| payin.failed | Processing error |
| payout.created | Payout created |
| payout.broadcast | Sent to blockchain |
| payout.confirmed | On-chain confirmed |
| payout.failed | Processing failed |
| withdrawal.requested | Withdrawal created |
| withdrawal.approved | Approved by admin |
| withdrawal.rejected | Rejected by admin |
| withdrawal.broadcast | Sent to blockchain |
| withdrawal.confirmed | On-chain confirmed |
| withdrawal.failed | Broadcast failed |
| merchant.kyb_approved | KYB review approved |
| merchant.kyb_rejected | KYB review rejected |
| merchant.live_activated | Account activated to live |
| merchant.suspended | Account suspended |
| api_key.rotated | API key rotated |
| api_key.revoked | API key revoked |
| balance.low_threshold | Balance fell below threshold |
| bulk_batch.completed | Bulk batch fully settled |
| bulk_batch.partially_completed | Some bulk items failed |
| bulk_batch.failed | Bulk batch failed |
Error Handling
All SDK errors extend ZyndPayError and include a statusCode and optional requestId.
import {
ZyndPayError,
AuthenticationError,
ValidationError,
NotFoundError,
ConflictError,
RateLimitError,
} from '@zyndpay/sdk';
try {
const payin = await zyndpay.payins.create({ amount: '5' }); // below minimum
} catch (err) {
if (err instanceof ValidationError) {
console.error('Bad request:', err.message); // "amount must be >= 25"
} else if (err instanceof AuthenticationError) {
console.error('Invalid API key');
} else if (err instanceof NotFoundError) {
console.error('Resource not found');
} else if (err instanceof ConflictError) {
console.error('Conflict (duplicate or state error):', err.message);
} else if (err instanceof RateLimitError) {
console.error('Rate limited, retry after', err.retryAfter, 'seconds');
} else if (err instanceof ZyndPayError) {
console.error('API error', err.statusCode, err.message);
}
}TypeScript Support
The SDK is written in TypeScript and ships full type declarations. No @types package needed.
import type { Payin, Withdrawal, Transaction, Balance, WebhookEvent } from '@zyndpay/sdk';License
MIT — see LICENSE
