TxRoutePay API
Accept USDT on TRON (TRC20) and BNB Smart Chain (BEP20). Each invoice gets its own deposit address. When the customer pays, a forwarder contract sends the money straight to your payout wallet and takes the platform fee in the same transaction — TxRoutePay never holds your funds.
The API is plain JSON over HTTPS. Amounts are always decimal strings (for example "49.99") so no precision is lost.
Quickstart
- Create an account and add a payout wallet (Settings → Payout wallets).
- Create a test API key in Developers.
- Create an invoice and redirect the customer to
checkout_url. - Add a webhook endpoint and mark the order paid on
invoice.paid.
const res = await fetch("https://txroutepay.com/v1/invoices", {
method: "POST",
headers: {
authorization: `Bearer ${process.env.TXROUTE_KEY}`,
"content-type": "application/json",
"idempotency-key": "order-1043",
},
body: JSON.stringify({ external_order_id: "1043", amount: "49.99", currency: "USDT", network: "tron-mainnet" }),
});
const invoice = await res.json();
// redirect the customer to invoice.checkout_urlAuthentication
Send your key in the Authorization header: Bearer pk_test_… or Bearer pk_live_…. Keys are shown once when created; we only store a hash.
Permissions (scopes)
invoices:writescope · optionalinvoices:readscope · optionalrefunds:writescope · optionalIP allowlist
Optionally restrict a key to your server IPs or CIDR ranges. Requests from other IPs get 401.
Test mode
Test keys only work with testnets: tron-nile and bsc-testnet. Live keys only work with tron-mainnet and bsc-mainnet. Test mode is available while your account is in review.
Payment links
No website? In the dashboard, open Payments → New payment link, enter the amount and a description, and share the link on WhatsApp, Facebook or email. The customer chooses the network on the checkout page.
Hosted checkout
Every invoice has a checkout_url. The page shows the amount, a QR code, the address with copy buttons, a countdown and live status, with your logo and brand color (Settings → Checkout & branding). Set a return URL to bring the customer back to your shop after payment.
Payment statuses
awaiting_paymentconfirmingpaidunderpaidoverpaidpaid_lateexpiredinvoice.paid webhook, or after fetching the invoice from your server.Create an invoice
Needs scope invoices:write. Returns 201 with the invoice (or 200 with the existing one when the same Idempotency-Key is reused).
Body parameters
external_order_idstring · requiredamountstring · required"49.99".currencystring · required"USDT" for now.networkstring · requiredtron-mainnet, bsc-mainnet (live) or tron-nile, bsc-testnet (test).expires_in_minutesinteger · optionalmetadataobject · optionaldescription is shown on the checkout; customer_email lets us send a receipt.Headers
Idempotency-Keyheader · optionalcurl https://txroutepay.com/v1/invoices \
-H "Authorization: Bearer pk_test_…" \
-H "Idempotency-Key: order-1043" \
-H "Content-Type: application/json" \
-d '{
"external_order_id": "1043",
"amount": "49.99",
"currency": "USDT",
"network": "tron-nile",
"expires_in_minutes": 60,
"metadata": { "description": "Jamdani saree", "customer_email": "rina@example.com" }
}'Response · 201
{
"id": "6f1c2a4e-…",
"external_order_id": "1043",
"status": "awaiting_payment",
"network": "tron-nile",
"currency": "USDT",
"address": "TQm1…7xLp",
"amount": "49.99",
"amount_detected": "0",
"amount_confirmed": "0",
"fee_bps": 100,
"checkout_url": "https://txroutepay.com/pay/6f1c2a4e-…",
"expires_at": "2026-10-17T11:00:00.000Z",
"paid_at": null,
"created_at": "2026-10-17T10:00:00.000Z",
"metadata": { "description": "Jamdani saree" }
}Retrieve an invoice
Needs invoices:read. Returns the invoice object above with its current status.
List invoices
statusquery · optionalnetworkquery · optionallimitquery · optionalbeforequery · optionalnext_cursor from the previous page.Returns { "data": [ … ], "next_cursor": "…" | null }, newest first.
Record a refund
Needs refunds:write. Crypto payments cannot be pulled back, so you send the refund from your own wallet, then record it here so reports and receipts stay correct. Only paid invoices can be refunded, and the total can’t exceed the amount received.
amountstring · requiredreasonstring · requiredtx_hashstring · optionalnotestring · optionalemail_customerstring · optionalcurl https://txroutepay.com/v1/invoices/6f1c2a4e-…/refunds \
-H "Authorization: Bearer pk_live_…" \
-H "Content-Type: application/json" \
-d '{ "amount": "10.00", "reason": "Item out of stock", "tx_hash": "0x…", "email_customer": "rina@example.com" }'Errors
Errors return JSON: { "error": "code", "message": "…" }.
400 invalid_requestdetails lists each problem.400 invalid_amount400 unsupported_network400 wrong_mode400 wallet_not_configured401 unauthorized403 forbidden403 missing_permission404 not_found409 duplicate_orderexternal_order_id already exists (use an Idempotency-Key to retry safely).503 network_not_readyWebhook events
Add endpoints in Developers → Webhooks. We POST JSON to your URL for each event:
{
"id": "evt_…",
"type": "invoice.paid",
"created_at": "2026-10-17T10:03:12.000Z",
"data": { "id": "6f1c2a4e-…", "external_order_id": "1043", "status": "paid", "amount_confirmed": "49.99", … }
}invoice.createdinvoice.payment_detectedinvoice.paidinvoice.underpaidinvoice.overpaidinvoice.paid_lateinvoice.expiredinvoice.payment_reorgedtest.pingUse the X-Gateway-Event-Id header (same as id) to ignore duplicates.
Verify signatures
Every request has X-Gateway-Signature: t=<unix time>,v1=<hex>. v1 is HMAC-SHA256 of t + "." + raw body with your endpoint secret (whsec_…). For 24 hours after you rotate a secret, a second v1 signed with the old secret is included — accept the request if any v1 matches.
import { createHmac, timingSafeEqual } from "node:crypto";
// Header "X-Gateway-Signature: t=<unix>,v1=<hex>[,v1=<hex>]"
export function verify(rawBody, header, secret) {
const parts = header.split(",").map((p) => p.split("="));
const t = parts.find(([k]) => k === "t")?.[1];
if (!t || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; // 5 min replay window
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return parts.some(([k, v]) => k === "v1" && v.length === expected.length
&& timingSafeEqual(Buffer.from(v), Buffer.from(expected)));
}Retries
Answer with any 2xx within 10 seconds. Otherwise we retry after 10 s, 30 s, 2 min, 10 min, 30 min, 1 h, 3 h, 6 h, 12 h and 24 h, then give up and show the delivery as failed in your dashboard (you can resend it there).
SDKs & plugins
Official Node.js and PHP libraries and a WooCommerce plugin are coming soon. Until then, the plain HTTP examples above work from any language.