API Documentation
Accept USDT and USDC without holding your customers' funds anywhere in between.
Deployed on BNB Smart Chain Testnet and Ethereum Sepolia. Not yet on mainnet.
Base URL: https://api.vorixpay.com
Quick Start
Five steps from sign-up to a verified payment. Step 2 is the one people miss.
1. Get your API key
Sign up at vorixpay.com/register, then generate a test key (sk_test_…) from your dashboard.
2. Register your payout wallet
In Dashboard → Payout wallet, connect the wallet you want to be paid into and sign, once per network. It is recorded on-chain and every payment is collected into it. This cannot be done with an API key, because it needs a signature from the wallet itself. Until it is done, creating an invoice on that network returns 400.
3. Create an invoice
curl -X POST https://api.vorixpay.com/api/v1/invoices \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Order #123",
"amount": 50,
"currency": "USDT",
"chain": "bsc-testnet"
}'
# Save "id" and "txRef" from the response against your order —
# they are how you look the payment up afterwards.4. Send the customer to checkout
// Use the slug from the response to build the checkout URL const checkoutUrl = "https://vorixpay.com/pay/" + invoice.slug; window.location.href = checkoutUrl; // The customer sends exactly that token, on that network, to the // invoice's deposit address. Vorixpay detects the payment on-chain. // redirectUrl (if set) is opened unchanged once the invoice completes — // put your own order id in it if you need one on return.
5. Verify payment
# After the invoice.completed webhook, or when the customer returns curl https://api.vorixpay.com/api/v1/invoices/verify/VXP_abc123 \ -H "Authorization: Bearer YOUR_SECRET_KEY" # status: "COMPLETED" = payment confirmed. Safe to fulfill.
Authentication
All API requests require your secret key in the Authorization header.
Authorization: Bearer YOUR_SECRET_KEY
API Keys
Generate your API key from Dashboard → API Keys.
Your secret key is shown only once when generated — store it securely. Never expose it in frontend code or public repositories.
sk_test_ keys work only on test networks and sk_live_ keys only on mainnet. Creating an invoice or address on the other kind of network returns 403; naming one to GET /api/v1/invoices/tokens returns 400. Every network is a testnet today, so use a test key.
Test and live
Everything in Vorixpay is either test (test networks, worthless tokens) or live (real networks, real money), decided by the network it is on. A test key only sees and creates test invoices, payments and addresses; a live key only live ones. Invoice and webhook payloads carry livemode: true | false so your code can check.
Webhook endpoints are test or live too — set by the key (or the dashboard mode) that created them — and receive only that mode's events, so a test payment never reaches your production server. The dashboard has a Test / Live switch at the top of the sidebar.
What an API key cannot do
API keys are for taking payments: invoices, addresses, payments, balances, customers and webhook endpoints. Anything that manages the account needs you signed in to the dashboard, and an API key gets 403 with error: "dashboard_only":
creating, listing or revoking API keys · editing your profile · two-factor settings · reading or regenerating the webhook secret · registering, changing, cancelling or applying a payout wallet.
So a key that leaks from a server or a log cannot create more keys, read your webhook secret, switch off 2FA or move where you are paid. It can still do real damage with what is left:
create and cancel invoices · accept an underpaid invoice as paid with settle-short, which sends a genuine invoice.completed to your server · add webhook endpoints, or change or switch off yours (PATCH /api/v1/webhooks/:id takes url and isActive), so your server stops hearing about payments · read your customer records, including emails and phone numbers.
If a key leaks, revoke it in Dashboard → API Keys straight away, then check your webhook endpoints and any invoice settled short since.
Invoices
Invoices are one-time payment requests. Each invoice gets a unique checkout page where the customer can pay.
/api/v1/invoicesCreate a new invoice. Name the token it is paid in; the customer pays in exactly that token. Each token has a minimum — see GET /api/v1/invoices/tokens — and a merchant-paid invoice below it is refused, because the fee would take the whole payment.
Request Body
{
"title": "Pro Plan", // Required
"amount": 50, // Required — in token units (e.g. 50 USDT), at least 0.01
"currency": "USDT", // Required — a token the chain accepts, e.g. "USDT" or "USDC"
"chain": "bsc-testnet", // Optional — defaults to bsc-testnet. See GET /api/v1/invoices/chains
"description": "Monthly access", // Optional
"feeBearer": "merchant", // "merchant" (default) or "customer"
"expiryMinutes": 30, // Default: 30. Set 0 for no expiry.
"toleranceBps": 0, // Optional, 0–500: how far short a payment may fall and still complete
"redirectUrl": "https://yourshop.example/success", // Opened unchanged after payment
"customerEmail": "[email protected]",
"metadata": { "orderId": "123", "plan": "pro" }
}
// callbackUrl is also accepted, but only stored and echoed back for your
// reference — nothing is ever sent to it. Register a webhook endpoint instead.Response
{
"id": "uuid",
"livemode": false,
"explorerUrl": "https://testnet.bscscan.com",
"slug": "xfmzFA9g4qXr",
"url": "/pay/xfmzFA9g4qXr",
"txRef": "VXP_abc123def456",
"title": "Pro Plan",
"description": "Monthly access",
"amount": "50.000000000000000000",
"amountReceived": "0.000000000000000000",
"currency": "USDT",
"fee": "1.000000000000000000", // 1% is 0.50, below USDT's 1.00 minimum fee
"netAmount": "49.000000000000000000",
"feeBearer": "merchant",
"status": "PENDING",
"depositAddress": "0x7a3F...4d9b",
"chain": "bsc-testnet",
"chainId": 97,
"toleranceBps": 0,
"paidLate": false,
"settledShort": false,
"redirectUrl": "https://yourshop.example/success",
"customerEmail": "[email protected]",
"metadata": { "orderId": "123", "plan": "pro" },
"expiresAt": "2025-01-01T12:30:00.000Z",
"createdAt": "2025-01-01T12:00:00.000Z"
}Fee model
The fee is the network's percentage or the token's minimum fee, whichever is larger. The percentage is set per network — on testnet today, 1% on BNB Smart Chain Testnet and 3% on Ethereum Sepolia — and is fixed into an invoice when it is created. The minimum fee is set per token and applied when the payment is collected. Read both from GET /api/v1/invoices/chains rather than hardcoding them.
When feeBearer is "merchant": the customer pays the invoice amount and you receive it minus the fee. The amount must be at least the token's minAmount, or the invoice is refused — below it the fee would take everything.
When feeBearer is "customer": the invoice is raised by the fee, so amount in the response is what the customer pays and you receive the amount you asked for.
Money fields in invoice responses are decimal strings with 18 places ("50.000000000000000000"). Parse them as decimals, not floats.
/api/v1/invoices/chainsNetworks you can invoice on, each network's fee rate, and each token it accepts with the smallest invoice that token can settle.
Response
[
{
"key": "bsc-testnet",
"name": "BNB Smart Chain Testnet",
"chainId": 97,
"family": "evm",
"nativeSymbol": "tBNB",
"isTestnet": true,
"feeBps": 100,
"tokens": [
{ "symbol": "USDC", "minAmount": "3.01" },
{ "symbol": "USDT", "minAmount": "1.01" }
],
"available": true,
"isDefault": true
}
]/api/v1/invoices/tokensTokens accepted on one network, with contract address, decimals and minAmount. Query: ?chain=bsc-testnet (omit for the default network).
/api/v1/invoicesList your invoices with pagination and optional status filter.
Request Body
Query params: ?page=1&limit=20&status=COMPLETED // limit: default 20, max 100
Response
{
"data": [
{
"id": "uuid",
"txRef": "VXP_abc123...",
"title": "Pro Plan",
"amount": "50.000000000000000000",
"amountReceived": "50.000000000000000000",
"currency": "USDT",
"status": "COMPLETED",
"createdAt": "2025-01-01T12:00:00Z"
}
],
"total": 42,
"page": 1,
"limit": 20,
"totalPages": 3
}/api/v1/invoices/:idGet a single invoice with full details.
/api/v1/invoices/:id/cancelCancel a PENDING invoice. Cannot cancel invoices that already received payments.
/api/v1/invoices/:id/settle-shortAccept an UNDERPAID invoice as settled for what it received. It becomes COMPLETED with settledShort: true, invoice.completed is sent, and the money is collected to your wallet. This is the way out of UNDERPAID — an invoice with no expiry otherwise stays open, and its money waits, indefinitely.
Request Body
{ "reason": "Customer paid the exchange fee" } // OptionalVerify Payment
After a customer pays, verify the payment server-side using the transaction reference. This is the most important step — always verify before fulfilling an order.
/api/v1/invoices/verify/:txRefVerify an invoice payment by its transaction reference. Check that status is COMPLETED before fulfilling.
Response
{
"id": "uuid",
"txRef": "VXP_abc123...",
"status": "COMPLETED",
"amount": "50.000000000000000000",
"amountReceived": "50.000000000000000000",
"currency": "USDT"
}Important
Never trust client-side data. Always call this endpoint from your server to confirm the payment actually went through before delivering goods or services.
txRef format
New invoices carry a VXP_ txRef. Invoices created before the rename from Pay3 keep their PAY3_ one, and both resolve here. Treat a txRef as an opaque string: store it and look it up, but do not validate or parse the prefix.
Permanent Addresses
A permanent address belongs to one of your customers rather than to one invoice. You name it with your own reference for them — a user id, an account number — and every payment into it is attributed back to that reference. It never expires, so a customer can save it and top up whenever they like. Issuing one is computation only: nothing is deployed on-chain until there is money to collect, so an address that is never paid costs nothing.
Payments into these addresses have no invoice, so there is no invoice.completed. Credit the customer on payment.confirmed where data.kind is "user", using data.reference — see the example under Webhooks. Unlike an invoice address, which fixes its fee rate when it is created, a permanent address is charged the network's rate at the time it is collected, because it can keep receiving indefinitely.
/api/v1/deposit-addressesIssue an address for one of your customers, or get back the one they already have. Idempotent on reference: the same reference on the same network returns the same address. Sending it again with a different label or metadata updates those. Returns 400 if you have not registered a payout wallet on that network — without one there would be nowhere for the money to go — and 403 on a network of the other mode.
Request Body
{
"reference": "user_12345", // Required — your id for this customer, 1–128 characters
"chain": "bsc-testnet", // Optional — defaults to your mode's default network
"label": "Wallet top-up — Ada", // Optional, up to 200 characters — for your own dashboard
"customerEmail": "[email protected]", // Optional — files it under a customer record; never part of the address
"metadata": { "plan": "pro" } // Optional — echoed back on every payment.* webhook for this address
}Response
{
"id": "uuid",
"chain": "bsc-testnet",
"address": "0x9c1E...b27A",
"kind": "user",
"reference": "user_12345",
"label": "Wallet top-up — Ada",
"metadata": { "plan": "pro" },
"isDeployed": false, // No contract until the first collection — normal
"createdAt": "2025-01-01T12:00:00.000Z"
}When the same reference gets a new address
An address is derived from the network's deposit contracts. When a network moves to new contracts, asking for the same reference issues a new address. The old one is not retired: it still pays your registered wallet, so a customer who saved it loses nothing. Show customers the address the API returns now rather than one you cached long ago.
/api/v1/deposit-addressesYour permanent addresses, newest first, in the caller's mode. Filter with ?chain=bsc-testnet, or ?reference=user_12345 to find one customer's address. Not paged: it returns at most the 200 most recent, so look a specific customer up by reference.
Response
{
"data": [
{ "id": "uuid", "chain": "bsc-testnet", "address": "0x9c1E...b27A", "kind": "user",
"reference": "user_12345", "label": "Wallet top-up — Ada", "metadata": { "plan": "pro" },
"isDeployed": false, "createdAt": "2025-01-01T12:00:00.000Z" }
]
}/api/v1/deposit-addresses/:idOne address, with what it has received attached as ledger. Balances are in token units and split the same way as your account balance — see Payouts.
Response
{
"id": "uuid",
"address": "0x9c1E...b27A",
"reference": "user_12345",
...
"ledger": {
"balances": [
{ "token": "USDT", "decimals": 18, "amount": "25.0",
"settled": "20.0", "pendingCollection": "5.0", "unconfirmed": "0.0", ... }
],
"paymentCount": 2,
"firstPaymentAt": "2025-01-02T09:00:00.000Z",
"lastPaymentAt": "2025-01-05T16:30:00.000Z"
}
}/api/v1/deposit-addresses/:id/paymentsEvery transfer into one address, newest first, including reversed ones (status ORPHANED). Query: ?limit — default 100, max 200. collectedBy is null while the money is still in the address.
Response
{
"data": [
{
"txHash": "0x4359...",
"logIndex": 12,
"from": "0xabc...",
"asset": "USDT",
"amount": "5.0",
"amountBaseUnits": "5000000000000000000",
"decimals": 18,
"status": "CONFIRMED",
"confirmations": 20,
"blockNumber": "48210345",
"collectedBy": null,
"receivedAt": "2025-01-05T16:30:00.000Z"
}
]
}/api/v1/deposit-addresses/balancesYour permanent addresses with their balances attached, 100 per page, newest first. Query: ?page=2, and ?chain=bsc-testnet to limit to one network. hasMore says whether there is another page. First and last payment times are only filled in on GET /api/v1/deposit-addresses/:id.
Response
{
"data": [
{ "id": "uuid", "address": "0x9c1E...b27A", "reference": "user_12345", "kind": "user",
"label": "Wallet top-up — Ada", "chain": "bsc-testnet", "chainName": "BNB Smart Chain Testnet",
"balances": [ ... ], "paymentCount": 2, "firstPaymentAt": null, "lastPaymentAt": null }
],
"page": 1,
"hasMore": false
}Customers
A customer is created automatically when an invoice or permanent address is created with a customerEmail. You can also create and manage them via the API.
/api/v1/customersCreate a customer, or update the existing one with the same email or wallet address.
Request Body
{
"email": "[email protected]", // email or walletAddress is required
"walletAddress": "0xabc...", // email or walletAddress is required
"name": "John Doe", // Optional
"phone": "+1234567890" // Optional
}Response
{
"id": "uuid",
"walletAddress": "0xabc...",
"name": "John Doe",
"email": "[email protected]",
"createdAt": "2025-01-01T00:00:00Z"
}/api/v1/customersList your customers.
Request Body
Query params: ?page=1&limit=20
/api/v1/customers/:idGet a customer with their subscriptions and payment history.
/api/v1/customers/:idUpdate customer information.
Request Body
{
"name": "John Smith",
"email": "[email protected]"
}Subscription Plans
Coming soon
Plans can be created and read, but nothing charges against them — the recurring runner is not running. Build on invoices for now.
Plans define the pricing and billing cycle for recurring payments. Create a plan, then share its checkout link with customers.
/api/v1/subscription-plansCreate a subscription plan. Coming soon — not supported yet.
Request Body
{
"name": "Pro Plan", // Required
"amount": 29.99, // Required
"currency": "USDT", // Required
"intervalDays": 30, // Required: billing cycle in days
"description": "Monthly access", // Optional
"trialDays": 7 // Optional: free trial
}Response
{
"id": "uuid",
"name": "Pro Plan",
"amount": "29.99",
"currency": "USDT",
"intervalDays": 30,
"trialDays": 7,
"isActive": true,
"createdAt": "2025-01-01T00:00:00Z"
}Subscribe link
Share this link with your customers to subscribe:
https://vorixpay.com/subscribe/{planId}
Not active yet: until subscriptions launch, the public routes this page relies on answer 404, so the link does not work.
/api/v1/subscription-plansList your subscription plans with subscriber counts.
/api/v1/subscription-plans/:idUpdate a plan's name, amount, or active status.
Request Body
{
"name": "Pro Plan v2",
"amount": 39.99,
"isActive": false
}/api/v1/subscription-plans/:idDeactivate a plan. Existing subscribers continue but no new sign-ups.
Subscriptions
Coming soon
Recurring charges are not available yet. These endpoints exist but are not part of the supported surface — one-off payments were finished first. Do not build against them.
/api/v1/subscriptionsList all subscriptions with customer info and charge stats.
Request Body
Query params: ?page=1&limit=20
Response
{
"data": [
{
"id": "uuid",
"planName": "Pro Plan",
"subscriberAddress": "0xabc...",
"amount": "29.99",
"tokenSymbol": "USDT",
"status": "ACTIVE",
"nextChargeAt": "2025-02-01T00:00:00Z",
"chargeCount": 3,
"failedChargeCount": 0,
"customerName": "John Doe",
"customerEmail": "[email protected]",
"createdAt": "2025-01-01T00:00:00Z"
}
],
"total": 12,
"page": 1,
"totalPages": 1
}/api/v1/subscriptions/:idGet full subscription details including billing period and charge history.
/api/v1/subscriptions/:id/chargesGet charge history for a subscription — every billing attempt with status and amount.
Response
[
{
"id": "uuid",
"amount": "29.99",
"fee": "0.45",
"netAmount": "29.54",
"txHash": "0xabc...",
"status": "COMPLETED",
"chargedAt": "2025-01-01T00:00:00Z"
}
]/api/v1/subscriptions/:id/cancelCancel a subscription. No further charges will be attempted.
Response
{
"message": "Subscription cancelled",
"id": "uuid"
}Webhooks
Webhooks notify your server when events happen. They are delivered only to endpoints you register here — an invoice's callbackUrl is stored for your reference and never called. Every delivery is signed with HMAC-SHA256 using your webhook secret — one secret per account, shared by all your endpoints — so you can verify it came from Vorixpay.
/api/v1/webhooksCreate a webhook endpoint. events must name events from the Webhook Events table below; an unknown name — a typo, or an event for a feature that is not live — is refused with 400 rather than saved and never sent. From the dashboard the response includes your account's webhook secret; with an API key it does not — the secret is only ever shown in the dashboard.
Request Body
{
"url": "https://yourshop.example/webhooks/vorixpay",
"events": [
"invoice.completed",
"invoice.underpaid",
"invoice.expired",
"payment.confirmed",
"payment.orphaned",
"wallet.change_requested"
]
}Response
{
"id": "uuid",
"url": "https://yourshop.example/webhooks/vorixpay",
"events": ["invoice.completed", ...],
"isActive": true,
"mode": "test", // Receives only test-network events
"createdAt": "2025-01-01T12:00:00.000Z"
}/api/v1/webhooksList your webhook endpoints in the caller's mode. Not paged.
/api/v1/webhooks/:idUpdate a webhook endpoint's url, events or isActive. Event names are checked the same way as on create.
/api/v1/webhooks/:idDelete a webhook endpoint.
/api/v1/webhooks/deliveriesWebhook delivery logs, newest first, with every attempt's response. Query: ?page=1&limit=20 (limit max 100).
/api/v1/webhooks/deliveries/:id/retrySend a failed delivery again now.
/api/v1/webhooks/secretYour webhook signing secret (whsec_…). Dashboard only — refused to API keys.
/api/v1/webhooks/secret/regenerateReplace your webhook secret. Takes effect immediately for every endpoint: deliveries after this are signed with the new secret only, so update your server first. Dashboard only.
What a delivery looks like
Every event arrives in the same envelope. The fields that describe what happened are in data, and differ by event type.
{
"id": "evt_3f9c...", // Stable across retries: your idempotency key
"type": "payment.confirmed",
"livemode": false, // null when the event names no network
"sequence": 1042, // Only ever increases for your account
"created": "2025-01-05T16:30:00.000Z", // When the event was recorded, not when this attempt was sent
"data": {
"paymentId": "uuid",
"address": "0x9c1E...b27A",
"reference": "user_12345", // Your reference, for a permanent address
"kind": "user", // "user" for a permanent address, "invoice" for an invoice's
"label": "Wallet top-up — Ada",
"metadata": { "plan": "pro" },
"chain": "bsc-testnet",
"asset": "USDT",
"amount": "5000000000000000000", // Base units: divide by 10^decimals
"decimals": 18,
"txHash": "0x4359...",
"logIndex": 12,
"blockNumber": "48210345",
"confirmations": 15,
"from": "0xabc...",
"isSupportedToken": true // false for a token Vorixpay does not accept — never credit it
}
}sweep.completed reports amount and fee in base units but carries no decimals. Match its tokenAddress against GET /api/v1/invoices/tokens?chain=… for the event's chain — the same symbol has different decimals on different networks.
Ordering and retries
Respond with any 2xx within 10 seconds. Anything else — including a 4xx — is retried: up to 12 attempts over about 16 hours (5s, 15s, 45s, 2m, 5m, 10m, 30m, 1h, 2h, 4h, 8h, with some jitter). An endpoint that fails 10 times in a row is paused for 15 minutes. Each request carries Vorixpay-Signature, Vorixpay-Event-Id, Vorixpay-Event-Type and Vorixpay-Delivery-Attempt headers.
Because failures are retried, events can reach you out of order. The common case: an invoice.underpaid delivery fails, the customer sends the rest, your endpoint accepts invoice.completed, and then the retried underpaid event arrives. Acting on arrival order would mark a paid invoice unpaid.
Every event carries a sequence — an integer that only ever increases for your account, assigned when the event is recorded rather than when it is delivered. Keep the highest you have processed for a given invoice and discard anything lower. Events are also delivered at least once, so the same id can arrive twice; the same check handles both. Nothing changes without its event: a payment, invoice or collection is saved in the same database transaction as the event that describes it, so if the event cannot be recorded neither is, and both are tried again. Webhooks are a notification, not a ledger: confirm anything that matters with GET /api/v1/invoices/verify/:txRef.
Verifying Webhook Signatures
const crypto = require('crypto');
// The raw body, not the parsed one. Re-serialising with JSON.stringify does
// not reliably reproduce the bytes that were signed — key order, spacing and
// unicode escaping can all differ — and the signature then fails for reasons
// that look random.
app.post(
'/webhooks/vorixpay',
express.raw({ type: 'application/json' }),
(req, res) => {
const header = req.headers['vorixpay-signature'];
if (typeof header !== 'string') return res.status(401).send('Missing signature');
const rawBody = req.body.toString('utf8');
// t=<unix>,v1=<hmac>
//
// Accept any v1 value that matches rather than parsing the header into an
// object, which would keep only the last one if more are ever sent.
const parts = header.split(',').map((p) => p.split('='));
const timestamp = parts.find(([k]) => k === 't')?.[1];
const signatures = parts.filter(([k]) => k === 'v1').map(([, v]) => v);
// Bound replay: reject anything older than five minutes.
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
return res.status(401).send('Stale signature');
}
const expected = crypto
.createHmac('sha256', YOUR_WEBHOOK_SECRET)
.update(timestamp + '.' + rawBody)
.digest('hex');
// Constant time, so a wrong signature cannot be discovered a byte at a
// time by measuring how long the comparison takes.
const valid = signatures.some(
(sig) =>
sig.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)),
);
if (!valid) return res.status(401).send('Invalid signature');
const event = JSON.parse(rawBody);
// The same event can arrive more than once — a delivery that timed out
// after your server processed it is retried. `id` is stable across
// retries, so record it and ignore a repeat.
if (alreadyProcessed(event.id)) return res.status(200).send('OK');
// Invoices: fulfil on invoice.completed, and only on that. It fires once
// the invoice is paid in full (or you accepted it short). payment.* events
// also fire for invoice payments — including partial ones and transfers in
// the wrong token — so fulfilling on those would give goods away.
if (event.type === 'invoice.completed') {
fulfillOrder(event.data.txRef);
}
// Permanent customer addresses (kind "user") have no invoice, so credit
// those on payment.confirmed. amount is in the token's base units:
// divide by 10^decimals, which the event carries (18 for BSC testnet
// USDT/USDC, 6 on Sepolia). Transfers of tokens Vorixpay does not accept
// are reported too, with isSupportedToken: false — never credit those.
if (
event.type === 'payment.confirmed' &&
event.data.kind === 'user' &&
event.data.isSupportedToken
) {
creditCustomer(event.data.reference, event.data.asset, event.data.amount, event.data.decimals);
}
res.status(200).send('OK');
},
);Payouts
There is no withdrawal API, because Vorixpay never holds your money. A customer pays a deposit address that can only pay out to the payout wallet you registered, less our fee, and the platform sweeps it there for you. Nothing to request, and nothing to wait for anyone to approve. Balances under the collection threshold (currently 5 USDT or USDC) are batched and collected within 24 hours.
/api/v1/deposit-addresses/balanceWhat you have been paid, one row per token per network, split by where the money currently is. Query: ?chain=bsc-testnet to limit to one network.
Response
{
"balances": [
{
"token": "USDT",
"name": "Tether USD",
"tokenAddress": "0x...",
"decimals": 18,
"iconUrl": null,
"amount": "1245.5",
"settled": "1100.0",
"pendingCollection": "145.5",
"unconfirmed": "0.0"
}
]
}settled has reached your own wallet, after our fee. pendingCollection is yours but is still sitting in a deposit address awaiting a sweep, before our fee. unconfirmed is on-chain but not yet deep enough to rely on — a reorg can still take it back.
/api/v1/walletsYour registered payout wallet on each chain.
/api/v1/sweepsCollections into your wallet, and which payments each one covered, newest first. Query: ?page=1&limit=50 (limit max 200). Returned under sweeps, not data.
/api/v1/paymentsEvery transfer into any of your addresses, newest first. The unit is the on-chain transfer rather than the invoice: an invoice can be paid by two, a permanent address receives them with no invoice at all, and an underpayment is real money that an invoice-shaped view would file under unpaid. Filter by status, chain, token, kind and date; search by transaction hash, address or your own reference.
Request Body
Query params: ?page=1&limit=25&status=CONFIRMED&kind=user&search=0x4359 // limit: default 25, max 200
/api/v1/sweeps/pendingConfirmed money not yet in your wallet, and why each balance has not moved — below the amount worth a transaction, held while its invoice is open, or waiting on a payout wallet you have not registered. Answered by the collection planner itself, so it always matches what the sweeper is about to do.
Status Reference
Invoice Statuses
PENDINGCreated, waiting for payment.
CONFIRMINGMoney has been seen on-chain but is not yet confirmed. It may be only part of the amount.
UNDERPAIDA confirmed partial payment. Still open for the rest; accept it with settle-short if you want to.
COMPLETEDPaid in full and confirmed. Safe to fulfill. Check paidLate (paid after expiry) and settledShort (you accepted less).
EXPIREDThe deadline passed without the full amount. It may hold a partial payment, and becomes COMPLETED with paidLate: true if the rest arrives within 30 days of the invoice being created — counted from creation, not from expiry.
CANCELLEDCancelled by you before any payment arrived.
Subscription Statuses
TRIALINGFree trial period. No charges yet.
ACTIVEBilling normally.
PAST_DUERecent charges failed. Retrying next cycle.
CANCELLEDCancelled. No future charges.
Webhook Events
| Event | Description |
|---|---|
payment.detected | A transfer was seen on-chain. Not yet safe to act on: a reorg can still take it back. amount is in base units; decimals is included. Sent again with reincluded: true if a reorg reversed the payment and the network then included it again. |
payment.confirmed | That transfer is buried deep enough to be final. Credit a permanent-address (kind "user") customer on this. For invoices use invoice.completed instead — this also fires for partial payments. amount is in base units; decimals is included. |
payment.orphaned | A reorg reversed a payment you were told about. Undo whatever you credited. |
invoice.completed | An invoice was paid in full, or you accepted it short (settledShort: true). paidLate: true if it arrived after expiry. The one to fulfil an order on. |
invoice.underpaid | Some money arrived but not enough, and the invoice is still open for the rest. |
invoice.overpaid | More arrived than was asked for. Sent alongside invoice.completed, not instead of it. |
invoice.expired | The deadline passed without enough arriving. |
invoice.cancelled | You cancelled the invoice before it was paid. |
sweep.completed | Money left the deposit address and reached your payout wallet, less the fee. Amounts in base units. |
wallet.registered | Your payout wallet was registered on a network. |
wallet.change_requested | Someone asked to change your payout wallet. It takes effect after 24 hours unless cancelled. If this was not you, cancel it now. |
wallet.change_applied | A payout wallet change took effect. |
wallet.change_cancelled | A pending payout wallet change was cancelled. |
Errors
All errors return JSON with a message field. For validation errors it is an array of strings, one per problem; unknown fields in a request body are rejected.
| Code | Meaning |
|---|---|
400 | Bad Request — invalid parameters, an unknown webhook event name, a token the network does not accept, an amount below the token’s minimum, or no payout wallet registered on that network. Also a network of the other mode named to GET /api/v1/invoices/tokens. |
401 | Unauthorized — invalid or missing API key (error: "invalid_token"). |
403 | Forbidden — creating an invoice or address with a test key on a live network (or a live key on a test one), or an API key used for a dashboard-only action (error: "dashboard_only"). |
404 | Not Found — resource does not exist, belongs to the other mode, or the id is malformed. |
429 | Too Many Requests — over 120 requests a minute from one IP address, counted across every key and session using it. Back off and retry. |
500 | Server Error — something went wrong on our side. |
Notes
Supported Tokens
USDT and USDC on BNB Smart Chain Testnet (18 decimals) and Ethereum Sepolia (6 decimals). An invoice is paid in exactly the token it names, on its network.
Amounts
Amounts you send are in token units (50 = 50 USDT). Responses return strings: invoice money fields with 18 decimal places, balances in token units, and webhook payment.* and sweep.* amounts in the token's base units.
Pagination
Paged lists take ?page (from 1) and ?limit. A limit above the maximum is lowered to it rather than refused, so check the limit or length you get back instead of assuming you got what you asked for.
| Endpoint | Default | Max | Rows in |
|---|---|---|---|
GET /invoices | 20 | 100 | data |
GET /customers | 20 | 100 | data |
GET /webhooks/deliveries | 20 | 100 | data |
GET /payments | 25 | 200 | data |
GET /sweeps | 50 | 200 | sweeps |
GET /deposit-addresses/:id/payments | 100 | 200 | data (no page) |
GET /deposit-addresses/balances | 100, fixed | 100 | data, with hasMore |
GET /deposit-addresses | not paged | 200 newest | data |
GET /webhooks | not paged | — | a plain array |
Paths are under /api/v1. Where there is a total, it comes back as total and totalPages beside the rows.