Skip to content

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.

Pass token and network when you create the payment. The response then includes deposit_address:

Terminal window
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
}
}

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.

  1. List the methods you accept for this payment: GET /v1/payments/{id}/methods → [{ "token": "USDC", "network": "polygon", "network_name": "Polygon" }, …]

  2. Request an address for the method the payer picks: POST /v1/payments/{id}/address with { "token": "USDC", "network": "polygon" }.

  3. Show address, pay_amount and a QR code of qr (a wallet payment link that includes the token and amount).

  4. Poll GET /v1/payments/{id} every few seconds to show progress, and stop at a final status. Your server still fulfils the order on the payment.settled webhook.

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.qr
  • Show pay_amount exactly. 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_at ends 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.

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.