Headless payments
In a headless integration you keep the payer on your own site or app. You choose (or let the payer choose) a token and network, request a deposit address, and display it.
From your server, in one call
Section titled “From your server, in one call”Pass token and network when you create the payment. The response then includes
deposit_address:
curl https://api.unizpay.com/v1/payments \ -H "Authorization: Bearer $UNIZPAY_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": "25.00", "payment_id": "order_1001", "token": "USDC", "network": "polygon" }'{ "object": "payment", "id": "pay_01K…", "status": "awaiting_payment", "deposit_address": { "address": "0x5b1C…9e40", "network": "polygon", "chain_id": "eip155:137", "token": "USDC", "pay_amount": "25", "qr": "ethereum:0x3c49…3359@137/transfer?address=0x5b1C…9e40&uint256=25000000", "quote_expires_at": "2026-10-31T13:00:00.000Z", "price": "25.00", "processing_fee": "0.00", "total": "25.00", "price_locked": false, "window_seconds": 3600, "route": "forwarder", "due": null }}From the payer’s browser
Section titled “From the payer’s browser”To let the payer pick, hand the payment’s checkout_token to your page and call the
payment endpoints from the browser. They accept requests from any origin.
-
List the methods you accept for this payment:
GET /v1/payments/{id}/methods→[{ "token": "USDC", "network": "polygon", "network_name": "Polygon" }, …] -
Request an address for the method the payer picks:
POST /v1/payments/{id}/addresswith{ "token": "USDC", "network": "polygon" }. -
Show
address,pay_amountand a QR code ofqr(a wallet payment link that includes the token and amount). -
Poll
GET /v1/payments/{id}every few seconds to show progress, and stop at a final status. Your server still fulfils the order on thepayment.settledwebhook.
const api = "https://api.unizpay.com/v1";const headers = { Authorization: `Bearer ${checkoutToken}`, "Content-Type": "application/json" };
const methods = await fetch(`${api}/payments/${paymentId}/methods`, { headers }).then((r) => r.json());
const address = await fetch(`${api}/payments/${paymentId}/address`, { method: "POST", headers, body: JSON.stringify({ token: "USDC", network: "polygon" }),}).then((r) => r.json());
// Show address.address, address.pay_amount and a QR code of address.qrRules to follow
Section titled “Rules to follow”- Show
pay_amountexactly. It is the amount of the token to send, already including the processing fee when the payer pays the fees. - Respect the window.
quote_expires_atends the window to pay at this address: 60 minutes for stablecoins, 15 minutes for tokens whose price moves (price_locked: true). When it passes, request the address again for a fresh quote. - Reuse is automatic. Requesting the same method again while its quote is valid returns the same address.
- One network per address. Tell the payer which network to send on. Funds sent on another network or in another token are still recovered, but not converted.
Underpaid payments
Section titled “Underpaid payments”If the payer sends less than the amount due, the payment becomes underpaid. With a checkout
token, each address in GET /v1/payments/{id} then carries due: the remaining amount, with
its own qr. The payer can send the rest to the same address.