Skip to content
ZeroKYC Pay

Quickstart

Create your first invoice with payment_currency=any, send the buyer to hosted checkout and receive a signed webhook.

Create your first invoice in one REST call, send the buyer to hosted checkout and receive a signed webhook when the payment confirms.

1. Get your test keys

  1. Register — email, password, 2FA. No KYC.
  2. Choose a checkout type: transit (paste your payout address) or direct (connect your wallet per asset).
  3. Copy the pk_test_… key from API keys.

Sandbox invoices are simulated: no blockchain transaction happens, the mock chain confirms them instantly, and the API contract is identical to production — same endpoints, same webhook payloads, same signature scheme. Differences: sandbox payments confirm in seconds (production waits for real network confirmations), and you can trigger underpaid/expired states from the console to test your handling. Testnet endpoints exist for adapter testing but are not part of the sandbox flow.

2. Create an invoice

Use payment_currency: "any" to let the buyer pick the coin at checkout — one invoice covers every enabled asset:

curl -X POST https://api.zerokyc-payments.com/v1/invoices \
-H "Authorization: Bearer pk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1042" \
-d '{
  "amount": 49.00,
  "base_currency": "USD",
  "payment_currency": "any",
  "order_id": "order-1042",
  "ttl_minutes": 360,
  "success_url": "https://yourapp.com/paid",
  "webhook_url": "https://yourapp.com/webhooks/zkp"
}'

The response contains one payment option per enabled asset, plus a hosted checkout URL:

{
"id": "inv_9f2k...",
"status": "pending",
"order_id": "order-1042",
"amount": "49.00",
"base_currency": "USD",
"expires_at": "2026-09-06T18:20:11Z",
"checkout_url": "https://pay.zerokyc-payments.com/pay/inv_9f2k...",
"options": [
  { "asset": "USDT", "network": "tron", "amount_crypto": "49.00", "rate": "1.0", "status": "open" },
  { "asset": "BTC",  "network": "bitcoin", "amount_crypto": "0.000524", "rate": "93457.91", "status": "open" },
  { "asset": "TON",  "network": "ton", "amount_crypto": "9.81", "rate": "4.99", "status": "open" }
]
}

Notes:

  • amount is fiat (USD, EUR or RUB) or the asset itself ("base_currency": "BTC").
  • Send an Idempotency-Key header: retrying the same creation returns the same invoice instead of a duplicate.
  • ttl_minutes defaults to 360 (6 hours); the allowed range is 10 minutes to 72 hours.

3. Send the buyer to checkout

Redirect to checkout_url. The hosted page shows every option with its crypto amount, the address, a QR code and a live TTL timer; it polls status every 5 seconds. White-label and RU/EN locales are built in.

4. Receive the webhook

When a payment is detected and confirmed you get a payment.confirmed POST:

{
"id": "evt_5c1a...",
"type": "payment.confirmed",
"created_at": "2026-09-06T14:02:33Z",
"data": {
  "invoice_id": "inv_9f2k...",
  "order_id": "order-1042",
  "asset": "TON",
  "network": "ton",
  "amount_paid": "9.81",
  "tx_hash": "9d24...",
  "status": "confirmed"
}
}

Respond with any 2xx within a few seconds. Every delivery is signed — verify it before trusting the payload (see HMAC verification); failed deliveries retry on a 1m → 5m → 30m → 2h → 12h ladder.

5. Verify the signature

import { createHmac, timingSafeEqual } from "node:crypto";

const expected = createHmac("sha256", WEBHOOK_SECRET)
  .update(`${req.headers["x-zkp-timestamp"]}.${req.rawBody}`)
  .digest("hex");

const ok = timingSafeEqual(
  Buffer.from(expected, "hex"),
  Buffer.from(req.headers["x-zkp-signature"], "hex"),
);
if (!ok) return res.status(400).end();

Full snippets for Node, PHP and Python: HMAC verification.

What's next