A session is a single checkout: a fixed ZKZ amount, a dedicated receiving wallet, and a 5-minute window to pay. The API runs on your treasury node. Create sessions server-side with your site key — it must never reach the browser.
POST/v1/sessions — site-key auth. An Idempotency-Key header makes retries safe: the same key + same body replays the original response; a different body returns 409.
const res = await fetch("https://<your-node-host>/v1/sessions", { method: "POST", headers: { "Authorization": `Bearer ${SITE_KEY}`, // sk_… — server-side only "Content-Type": "application/json", "Idempotency-Key": "order_8842-create-1", }, body: JSON.stringify({ amount: "125", // decimal STRING — never a number order_id: "order_8842", mode: "redirect", // "redirect" | "embed" success_url: "https://shop.example/thanks", cancel_url: "https://shop.example/cart", }), }); const session = await res.json();
The response carries a session_id (sess_…, your durable order reference), a token (cs_…), the checkout_url, the receiver_pubkey, and expires_at. Status is always open on creation.
A session's status is always exactly one of four values, and once it leaves open it is terminal:
| Status | Meaning |
|---|---|
open | Created, not yet paid (includes queued-at-capacity sessions). |
paid | Settlement confirmed — the payment object is present. |
expired | The 5-minute window elapsed with no valid payment. |
cancelled | Cancelled via the API before payment. |
GET/v1/sessions/:session_id — site-key auth. For fulfilment, prefer webhooks over polling — payment.succeeded is the reliable trigger.
parseFloat/Number them. There is no sandbox: every session moves real ZKZ.