@paychainly/sdk
v1.0.14
Published
TypeScript SDK for the Paychainly crypto payment platform
Downloads
199
Maintainers
Readme
@paychainly/sdk
TypeScript SDK for the Paychainly crypto payment platform. Accept USDT on BNB Smart Chain — manage customers, deposit addresses, payment links, invoices, and withdrawals without writing raw HTTP calls.
Table of contents
- Installation
- Environment variables
- Setup
- Quick start
- Pattern 1 — Wallet / deposit system
- Pattern 2 — Per-order checkout
- Receiving payments
- Customer lookup reference
- Error handling
- API reference
What's new in 1.0.14
- Retry with exponential backoff —
new Paychainly({ apiKey, retries: 3, retryDelay: 500 })automatically retries failed requests with exponential back-off. - Per-call timeout —
paychainly.transactions.list({}, { timeout: 5000 })lets you override the default request timeout on any individual call. - Auto-pagination —
await paychainly.customers.listAll()fetches every page automatically and returns a flat array. - Webhook sign —
Paychainly.webhooks.sign(payload, secret)generates an HMAC-SHA256 signature for outgoing webhook payloads. - Webhook verifyBrowser —
await Paychainly.webhooks.verifyBrowser(body, sig, secret)verifies webhook signatures using the Web Crypto API (no Node.jscryptodependency). - Extended tx filters —
paychainly.transactions.list({ dateFrom, dateTo, minAmount, maxAmount })allows filtering transactions by date range and amount range. - revokeByAddress —
paychainly.addresses.revokeByAddress('0xABC...')revokes a deposit address directly by its on-chain address without needing the internal ID. - Sandbox credit —
paychainly.sandbox.credit('0xDEPOSIT...', '50.00')simulates a USDT deposit in sandbox mode for testing your integration end-to-end.
Installation
npm install @paychainly/sdk
# or
yarn add @paychainly/sdkEnvironment variables
Add these to your .env file:
PAYCHAINLY_API_KEY=your_api_key_here
WEBHOOK_SECRET=your_webhook_secret_hereGet both from your Paychainly dashboard.
Setup
Create one instance and export it — import it everywhere else.
// lib/paychainly.ts
import { Paychainly } from '@paychainly/sdk';
export const paychainly = new Paychainly({
apiKey: process.env.PAYCHAINLY_API_KEY!,
baseUrl: 'https://api.paychainly.com', // optional — this is the default
timeout: 30_000, // optional — ms, default 30s
});Quick start
Not sure which pattern to use? Pick based on your use case:
| Use case | Pattern | |---|---| | User wallet / balance top-up | Pattern 1 — Wallet | | E-commerce order / SaaS subscription | Pattern 2 — Checkout (logged-in) | | One-off payment, no account needed | Pattern 2 — Guest checkout |
Pattern 1 — Wallet / deposit system
Each user gets one permanent deposit address. They send USDT to it — you credit their balance. Best for top-up flows, wallets, and balance-based systems.
Step 1 — Get or create the customer
customers.create() throws a 409 if the customer already exists. Always wrap it with a fallback to getByIdentifier() so it's safe to call on every login or page load — it will never create duplicates.
import { Paychainly, ApiError } from '@paychainly/sdk';
import { paychainly } from './lib/paychainly';
/**
* Returns existing customer if already created, creates one if not.
* Safe to call on every login / page load — never creates duplicates.
*/
async function getOrCreateCustomer(user: {
id: string; // your unique user ID — required
email?: string;
name?: string;
}) {
try {
return await paychainly.customers.create({
identifier: user.id,
email: user.email,
name: user.name,
});
} catch (err) {
if (err instanceof ApiError && err.status === 409)
return await paychainly.customers.getByIdentifier(user.id);
throw err;
}
}The returned customer object:
// {
// id: 42,
// customerUid: "dd8693ec-8b5f-43ef-b4e5-1d0a088df1a3", ← Paychainly UUID
// identifier: "user_abc123", ← your userId
// email: "[email protected]",
// name: "John Doe",
// metadata: {},
// createdAt: "2026-06-02T10:00:00.000Z",
// updatedAt: "2026-06-02T10:00:00.000Z"
// }Step 2 — Get the wallet address
mode: 'reuse' returns the same address every time for this customer — call it as many times as you want.
async function getWalletAddress(userId: string) {
const wallet = await paychainly.addresses.generate({
tokenSymbol: 'USDT',
network: 'BNB',
mode: 'reuse',
customer: { identifier: userId },
});
return {
address: wallet.address, // 0xABC... — show this to the user
network: wallet.network, // "BNB Smart Chain"
tokenSymbol: wallet.tokenSymbol, // "USDT"
};
}Step 3 — Put it together
async function setupUserWallet(user: { id: string; email?: string; name?: string }) {
const customer = await getOrCreateCustomer(user);
const wallet = await getWalletAddress(user.id);
return {
customerId: customer.id,
customerUid: customer.customerUid,
address: wallet.address,
network: wallet.network,
tokenSymbol: wallet.tokenSymbol,
};
}
// Call this whenever the user opens their wallet page
const wallet = await setupUserWallet({ id: 'user_abc123', email: '[email protected]' });
console.log('Deposit address:', wallet.address);
// → "Send USDT (BEP-20) to 0xABC... on BNB Smart Chain"Step 4 — How users send USDT
After you show wallet.address to your user, they have three options:
Option A — Copy and paste (works with any wallet app) Show the address as text with a copy button. User opens Binance, Trust Wallet, MetaMask etc., pastes the address, and sends USDT.
Option B — QR code (mobile-friendly)
Encode wallet.address as a QR code. User scans it with their mobile wallet app.
Option C — Connect wallet (MetaMask / Web3)
Use ethers.js or wagmi on the frontend to trigger a direct USDT transfer. No copy-paste needed — the user clicks "Pay" in MetaMask.
Pattern 2 — Per-order checkout
Each order gets its own checkout: a fresh deposit address + an optional hosted payment page. Works with or without a logged-in user.
paymentLinks.create() does everything in one call: assigns a deposit address, creates the hosted page, and returns all the information you need.
Step 1 — Create the checkout link
import { paychainly } from './lib/paychainly';
async function createCheckout(params: {
orderId: string;
amount: string; // e.g. "49.99"
memo?: string; // shown to the payer on the checkout page
note?: string; // internal — not shown to payer
userId?: string; // omit for guest checkout
metadata?: Record<string, unknown>;
}) {
const link = await paychainly.paymentLinks.create({
// Required
uniqueId: params.orderId, // idempotency key — safe to retry
tokenSymbol: 'USDT',
network: 'BNB',
// Optional
amount: params.amount, // omit for open-amount link
memo: params.memo ?? `Order #${params.orderId}`,
note: params.note,
expiryHours: 24, // omit for no expiry
// expiresAt: '2026-12-31T23:59:59Z', // alternative: exact ISO 8601 datetime
metadata: { orderId: params.orderId, ...params.metadata },
// Customer — omit entirely for guest checkout
...(params.userId ? { customer: { identifier: params.userId } } : {}),
});
return link;
}The returned link object:
// {
// id: 77,
// slug: "abc123xyz",
// uniqueId: "order_001",
// address: "0xDEPOSIT...",
// payUrl: "https://paychainly.com/pay/abc123xyz",
// amount: "49.99",
// tokenSymbol: "USDT",
// network: "BNB",
// status: "active",
// expiresAt: "2026-06-03T10:00:00.000Z",
// metadata: { orderId: "order_001" },
// createdAt: "2026-06-02T10:00:00.000Z"
// }Step 2 — Use the checkout
You have three options from the same API response — pick one:
const link = await createCheckout({
orderId: 'order_001',
amount: '49.99',
memo: 'Premium Plan',
userId: 'user_abc123',
});
// Option 1 — redirect to Paychainly's hosted checkout page
// Includes QR code, countdown timer, MetaMask connect, and payment confirmation
window.location.href = link.payUrl!;
// → https://paychainly.com/pay/abc123xyz
// Option 2 — show in your own custom UI
console.log(`Send ${link.amount} ${link.tokenSymbol} to: ${link.address}`);
console.log(`Expires at: ${link.expiresAt}`);
// Option 3 — QR-encode link.address yourself
// (any QR library works — encode link.address as the value)Guest checkout (no user account needed)
const guestLink = await createCheckout({
orderId: 'order_002',
amount: '29.99',
// no userId — no customer record created
});
window.location.href = guestLink.payUrl!;Receiving payments
Webhooks (recommended)
Set your webhook URL in the dashboard. Paychainly calls your endpoint the moment USDT arrives.
Important: Use
express.raw(), notexpress.json(). Signature verification requires the raw request body.
Wallet pattern — credit user balance:
import express from 'express';
import { Paychainly } from '@paychainly/sdk';
import { paychainly } from './lib/paychainly';
app.post('/webhooks/paychainly', express.raw({ type: '*/*' }), async (req, res) => {
// 1. Verify the signature — rejects anything fake
let event;
try {
event = Paychainly.webhooks.verify(
req.body,
req.headers['x-paychainly-signature'] as string,
process.env.WEBHOOK_SECRET!,
);
} catch {
return res.sendStatus(400);
}
if (event.event === 'deposit_detected') {
// 2. Find which user owns this deposit address
const customer = await paychainly.customers.getByDepositAddress(event.toAddress);
// 3. Credit their balance in YOUR database
await creditUserBalance(customer.identifier, event.amount);
console.log(`+${event.amount} USDT credited to ${customer.identifier}`);
console.log(`TX hash: ${event.txHash} — block: ${event.blockNumber}`);
}
res.sendStatus(200); // always 200 — prevents retries
});Checkout pattern — fulfil an order:
if (event.event === 'deposit_detected') {
// Fetch the payment link to get your metadata back
const [link] = await paychainly.paymentLinks.getByAddress(event.toAddress);
if (link) {
const orderId = link.metadata?.orderId as string;
await fulfillOrder(orderId, event.amount, event.txHash);
console.log(`Order ${orderId} paid — ${event.amount} USDT (tx: ${event.txHash})`);
}
}The webhook event object:
// {
// event: "deposit_detected",
// txHash: "0xabc123...",
// fromAddress: "0xUSER_WALLET...", ← who sent the USDT
// toAddress: "0xDEPOSIT...", ← your user's deposit address
// amount: "50.00",
// blockNumber: 48123456,
// timestamp: 1748862000,
// userId: "user_abc123" ← the identifier you set on the customer
// }Polling (no webhook needed)
Useful during development or when webhooks aren't set up yet. Not recommended for production.
async function checkForDeposit(address: string) {
const { data, total } = await paychainly.transactions.listByAddress(address, { limit: 5 });
if (total > 0) {
const tx = data[0];
// status: "pending" | "confirmed" | "swept" | "failed"
console.log(`${tx.amount} USDT — ${tx.status} — ${tx.txHash}`);
return tx;
}
return null;
}Fetch a transaction by hash
Available at any time, not just inside webhooks:
const tx = await paychainly.transactions.getByHash('0xabc123...');
console.log(tx.amount, tx.status, tx.blockNumber);
// Full transaction object:
// {
// id: 123,
// txHash: "0xabc123...",
// fromAddress: "0xUSER_WALLET...",
// toAddress: "0xDEPOSIT...",
// amount: "50.00",
// tokenSymbol: "USDT",
// network: "BNB",
// blockNumber: 48123456,
// status: "swept",
// mode: "production",
// createdAt: "2026-06-02T10:00:00.000Z"
// }Customer lookup reference
identifier is required when creating a customer. When referencing them later, use whichever you have:
// Using your own userId — recommended
customer: { identifier: 'user_abc123' }
// Using Paychainly's UUID (stored from create())
customer: { customerUid: 'dd8693ec-8b5f-43ef-b4e5-1d0a088df1a3' }
// Using email
customer: { email: '[email protected]' }| You have | Use |
|---|---|
| Your own user ID | { identifier: userId } ← recommended |
| Paychainly UUID from create() | { customerUid: "dd8693ec-..." } |
| User's email | { email: "[email protected]" } |
Error handling
import { ApiError } from '@paychainly/sdk';
try {
await paychainly.customers.create({ identifier: 'user_123' });
} catch (err) {
if (err instanceof ApiError) {
console.log(err.status); // HTTP status code, e.g. 409
console.log(err.code); // machine-readable code, e.g. "DUPLICATE_IDENTIFIER"
console.log(err.message); // human-readable description
}
}Common error codes:
| Status | Code | Cause |
|---|---|---|
| 409 | DUPLICATE_IDENTIFIER | Customer already exists. Use getByIdentifier() instead. |
| 400 | INVALID_SIGNATURE | Webhook signature mismatch. Check WEBHOOK_SECRET. |
| 401 | — | Missing or invalid API key. Check PAYCHAINLY_API_KEY. |
| 404 | — | Resource not found — double-check identifiers or addresses. |
API reference
paychainly.customers
| Method | Description |
|---|---|
| create(data) | Create a new customer. Throws 409 if identifier already exists. |
| list(options?) | List all customers with optional pagination. |
| get(id) | Get customer by Paychainly numeric ID. |
| getByIdentifier(identifier) | Get customer by your own user ID. |
| getByEmail(email) | Get customer by email address. |
| getByUid(uid) | Get customer by Paychainly UUID. |
| getByDepositAddress(address) | Look up which customer owns a deposit address. |
| updateByIdentifier(identifier, data) | Update customer fields by your user ID. |
| updateByEmail(email, data) | Update customer fields by email. |
paychainly.addresses
| Method | Description |
|---|---|
| generate(params) | Generate a deposit address. Use mode: 'reuse' for permanent wallets. |
| list(options?) | List all deposit addresses. |
| get(id) | Get address by numeric ID. |
| getByAddress(address) | Get address record by on-chain address string. |
| revoke(id) | Revoke a deposit address. |
paychainly.transactions
| Method | Description |
|---|---|
| list(options?) | List all transactions. |
| listByAddress(address, options?) | List transactions for a specific deposit address. |
| get(id) | Get transaction by numeric ID. |
| getByHash(txHash) | Get transaction by on-chain hash. |
paychainly.paymentLinks
| Method | Description |
|---|---|
| create(params) | Create a payment link with a fresh deposit address and hosted page. |
| list(options?) | List all payment links. |
| get(id) | Get payment link by numeric ID. |
| getBySlug(slug) | Get payment link by URL slug. |
| getByAddress(address) | Get payment links associated with a deposit address. |
| createForAddress(address, params?) | Create a payment link for an existing deposit address. |
paychainly.withdrawals
| Method | Description |
|---|---|
| create(params) | Initiate a USDT withdrawal. |
| list(options?) | List all withdrawals. |
| listByAddress(address, options?) | List withdrawals for a specific address. |
| get(id) | Get withdrawal by numeric ID. |
| cancel(id) | Cancel a pending withdrawal. |
paychainly.invoices
| Method | Description |
|---|---|
| get(id) | Get invoice by numeric ID. |
| getById(invoiceId) | Get invoice by invoice identifier. |
| getByHash(txHash) | Get invoice by on-chain transaction hash. |
paychainly.system
| Method | Description |
|---|---|
| getHealth() | Check API health status. |
Paychainly.webhooks (static)
| Method | Description |
|---|---|
| verify(body, signature, secret) | Verify webhook signature. Throws if invalid. |
Pattern comparison
| # | Pattern | Customer | Address mode | Payment link | Best for |
|---|---|---|---|---|---|
| 1 | Wallet — address only | Required | reuse — permanent | Not needed | Open-ended deposits, balance top-ups |
| 2 | Wallet — with payment link | Required | reuse — same address | createForAddress() per top-up | Fixed-amount top-ups with hosted page |
| 3 | Order checkout — logged-in user | Required | generate_new per order | paymentLinks.create() per order | E-commerce, invoices, subscriptions |
| 4 | Guest checkout | Not needed | generate_new per order | paymentLinks.create() per order | One-off payments, anonymous checkout |
