Deposit Addresses & Payment Links
One endpoint creates all three charge shapes:
| You send | You get |
|---|---|
amount (+ optional successUrl/cancelUrl/orderRef) | an order / payment link |
reusable: true | a standing "receive USDT" address (never expires) |
| nothing extra | an open deposit address |
Create POST /v1/deposit-addresses
Request body
All fields are optional at the schema level (unknown fields are ignored), but amount is required
when the effective fee mode is client.
| Field | Type | Notes |
|---|---|---|
chain | string | tron (default) |
currency | string | USDT (default) or TRX |
amount | string | Non-negative decimal ("100", "49.50"). Presence makes this an order. Required in client fee mode (else 422 EXPECTED_AMOUNT_REQUIRED). |
pricingCurrency | string | null | USD to price the order in fiat — amount is then a USD figure, converted to USDT at the live rate and locked for ~10 min (see Pricing in USD). Omit or USDT = amount is already USDT. With USD, a positive amount is required (422 USD_AMOUNT_REQUIRED). |
orderRef | string | null | Your order reference, max 120; shown on the checkout page |
label | string | null | Internal label, max 120 |
successUrl | string | null | http/https, max 2048 — checkout redirect on success |
cancelUrl | string | null | http/https, max 2048 — checkout redirect on cancel/expiry |
settlementAccountId | string (uuid) | Route funds to a specific settlement wallet (404 SETTLEMENT_ACCOUNT_NOT_FOUND if not yours) |
reusable | boolean | true → find-or-create a stable, non-expiring receive address |
apiKeyId | string (uuid) | First-party attribution only; ignored when an X-Api-Key is presented |
Response 201
| Field | Type | Notes |
|---|---|---|
id | string (uuid) | |
address | string | The on-chain address to pay |
chain / currency | string | |
status | string | ACTIVE |
expires_at | timestamp | null | null for reusable receive addresses |
fee_mode | string | merchant or client |
expected_amount | string | null | The order amount in USDT. For a USD-priced order this is the converted USDT (= fiat_amount × quote_rate) |
pay_amount | string | null | Grossed-up amount the payer must send (client fee mode); else null |
fiat_currency | string | null | USD when the order was priced in fiat; else null |
fiat_amount | string | null | The original fiat amount you sent (e.g. "100"); null for USDT-priced orders |
quote_rate | string | null | Locked exchange rate (USDT per 1 unit of fiat_currency); null otherwise |
quote_source | string | null | Rate source, e.g. coingecko; null otherwise |
quote_expires_at | timestamp | null | When the locked quote expires (created + ~10 min); null otherwise |
order_ref | string | null | |
success_url / cancel_url | string | null | |
checkout_token | string | Unguessable capability token |
checkout_url | string | null | CHECKOUT_BASE_URL + token, e.g. https://checkout.zaropay.com/c/<token> |
Example
- cURL
- Node.js
- Python
- PHP
curl -X POST https://api.zaropay.com/v1/deposit-addresses \
-H "X-Api-Key: $ZAROPAY_API_KEY" -H "Content-Type: application/json" \
-d '{ "amount": "100", "orderRef": "INV-1042", "successUrl": "https://shop.example.com/thanks" }'
const r = await fetch("https://api.zaropay.com/v1/deposit-addresses", {
method: "POST",
headers: { "X-Api-Key": process.env.ZAROPAY_API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ amount: "100", orderRef: "INV-1042", successUrl: "https://shop.example.com/thanks" }),
});
const { data } = await r.json();
data = requests.post(
"https://api.zaropay.com/v1/deposit-addresses",
headers={"X-Api-Key": os.environ["ZAROPAY_API_KEY"]},
json={"amount": "100", "orderRef": "INV-1042", "successUrl": "https://shop.example.com/thanks"},
).json()["data"]
$ch = curl_init("https://api.zaropay.com/v1/deposit-addresses");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ["X-Api-Key: " . getenv("ZAROPAY_API_KEY"), "Content-Type: application/json"],
CURLOPT_POSTFIELDS => json_encode(["amount" => "100", "orderRef" => "INV-1042"]),
]);
$data = json_decode(curl_exec($ch), true)["data"];
Pricing in USD (fiat)
Send pricingCurrency: "USD" to quote an order in dollars. ZaroPay converts to USDT at the live
rate at creation and locks it for ~10 minutes, so the customer always pays a fixed USDT amount.
Settlement and fees remain in USDT — the fiat figures are for display and reporting.
curl -X POST https://api.zaropay.com/v1/deposit-addresses \
-H "X-Api-Key: $ZAROPAY_API_KEY" -H "Content-Type: application/json" \
-d '{ "amount": "100", "pricingCurrency": "USD", "orderRef": "INV-1042" }'
// 201 — $100 converted to USDT at the locked rate
{
"expected_amount": "100.164", // USDT the customer pays (= fiat_amount × quote_rate)
"fiat_currency": "USD",
"fiat_amount": "100",
"quote_rate": "1.00164", // USDT per 1 USD, locked
"quote_source": "coingecko",
"quote_expires_at": "2026-06-29T12:34:56.000Z",
"checkout_url": "https://checkout.zaropay.com/c/<token>"
// …plus the usual id / address / status / checkout_token
}
The hosted checkout shows the USD amount, the USDT to pay, and a countdown; if the customer lingers
past the lock it automatically re-quotes. If no fresh rate is available the request fails closed with
503 RATE_UNAVAILABLE (retry shortly) rather than quoting on a stale rate.
Note Because USDT tracks the US dollar, the conversion is typically within a fraction of a cent of 1:1. The lock simply guarantees the customer the exact USDT figure they were shown.
Errors
| Code | Status | Meaning |
|---|---|---|
EXPECTED_AMOUNT_REQUIRED | 422 | amount required in client fee mode |
USD_AMOUNT_REQUIRED | 422 | pricingCurrency: "USD" sent without a positive amount |
RATE_UNAVAILABLE | 503 | No fresh USD→USDT rate available — retry shortly |
MAX_ACTIVE_ADDRESSES | 429 | Your active-address cap is reached |
SETTLEMENT_ACCOUNT_NOT_FOUND | 404 | settlementAccountId isn't yours |
NO_SETTLEMENT_ACCOUNT | 422 | reusable requested with no active settlement wallet |
List GET /v1/deposit-addresses
Query params: status (ACTIVE/EXPIRED/ARCHIVED), chain (tron), kind
(order = one-off links with expiry, receive = standing addresses), plus page / limit.
Returns an array of deposit-address rows (each with an injected checkout_url) and meta. When
kind=order, each row also carries a derived payment_status (paid, confirming, detected,
awaiting, expired).
Retrieve GET /v1/deposit-addresses/:id
:id is the address UUID, scoped to your merchant account (404 if it isn't yours). Returns a single
deposit-address row including checkout_url. Use this to poll an order's status — though the
webhook is the preferred, push-based signal.