Checkout
Use Checkout to take a one-off crypto payment. You create a checkout, send your customer to a ready-made ZyraPay payment page, and we tell you the moment they pay. Best for online stores and one-time purchases.
How hosted checkout works
A checkout is a single, short-lived payment session for one purchase. You create it server-side with your API key, then send the customer to the checkoutUrl ZyraPay returns. The customer pays in Pi on a page ZyraPay hosts and verifies — you never handle keys, on-chain state, or the Pi SDK yourself. When the payment settles, ZyraPay redirects the customer back to your successUrl and delivers a signed webhook so your backend can fulfill the order.
- 1
Create the checkout
CallPOST /v1/checkoutfrom your server with your lineitemsand your redirect URLs. ZyraPay works out the total and returns acheckoutId, a ready-madecheckoutUrl, and anexpiresAttime (when it stops accepting payment). - 2
Redirect the customer
Send the customer tocheckoutUrl. The hosted page shows your items and a Pi payment prompt, and opens a live WebSocket so the page updates the moment payment is detected. - 3
Customer pays in Pi
The customer pays. ZyraPay sees the payment on-chain, checks it, records it once, and updates the checkout'spaidAmountandstatus. - 4
Receive the result
The customer is redirected to yoursuccessUrl(orcancelUrl), and ZyraPay sends a signedcheckout.paidwebhook to yourwebhookUrl. Treat the webhook — not the redirect — as the source of truth and fulfill the order there.
Always create checkouts server-side
Your API key is a secret. CallPOST /v1/checkout from your backend only — never ship the x-api-key header to the browser.Create a checkout
/v1/checkoutx-api-keyAuthenticate with your project API key in the x-api-key header. The total is derived from items — you never send it. To make a create safely retryable, pass an Idempotency-Key header: a replay returns the original response, and an in-flight duplicate returns 409.
curl https://api.zyrapay.net/v1/checkout \
-H "x-api-key: $ZYRA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order_1042" \
-d '{
"items": [
{ "name": "Pro plan", "amount": 5, "quantity": 1 },
{ "name": "Add-on seats", "amount": 2, "quantity": 4 }
],
"successUrl": "https://yourapp.com/success",
"cancelUrl": "https://yourapp.com/cancel",
"webhookUrl": "https://yourapp.com/webhooks/zyra",
"expiresInMinutes": 30,
"metadata": { "orderId": "order_1042" }
}'Request body
| Field | Type | Description |
|---|---|---|
itemsrequired | object[] | Non-empty array of line items. The checkout total is the sum of amount × quantity across all items. |
items[].namerequired | string | Display name of the line item, shown on the hosted page. |
items[].amountrequired | number | Unit price (positive). In the checkout's currency. |
items[].quantityrequired | number | Quantity of the item (positive integer). |
items[].description | string | Optional longer description for the line item. |
items[].image | string | Optional image URL shown alongside the item. |
currency | "PI" | "USD" | "USDT" | "DOLLAR" | Defaults to PI. If a fiat/stable currency is given, ZyraPay converts the total to Pi at the live price at creation time; the customer always pays in Pi. |
successUrl | string | URL the customer is redirected to after a successful payment. |
cancelUrl | string | URL the customer is redirected to if they abandon or cancel. |
redirectUrl | string | Optional fallback redirect used when a dedicated success/cancel URL is not set. |
webhookUrl | string | URL to receive signed webhooks for this checkout. If set, ZyraPay sends checkout.created immediately and a payment event later. |
expiresInMinutes | number | Minutes until the checkout expires. Positive; defaults to 30. |
metadata | object | Arbitrary key/value data echoed back on reads and webhooks. Use it to attach your own order or customer IDs. |
Response
On success you get 201 with the hosted URL and expiry. Redirect the customer to checkoutUrl.
{
"checkoutId": "chk_8f2c1a...",
"checkoutUrl": "https://checkout.zyrapay.net/chk_8f2c1a...",
"expiresAt": "2026-06-14T12:30:00.000Z",
"totalAmount": 13,
"items": [
{ "name": "Pro plan", "amount": 5, "quantity": 1 },
{ "name": "Add-on seats", "amount": 2, "quantity": 4 }
]
}Invalid input returns 400 with { "success": false, "message": "Invalid request data", "errors": [...] }.
Checkout statuses
A checkout begins pending and moves to a terminal state as ZyraPay verifies payment on-chain and compares paidAmount to totalAmount.
| Field | Type | Description |
|---|---|---|
pending | status | Created and awaiting payment. The default starting state. |
completed | status | Paid in full — paidAmount equals totalAmount. Fulfill the order. |
partial | status | Some Pi received, but less than the total. The shortfall is tracked as underpaidAmount. |
overpaid | status | More than the total received. The excess is tracked as overpaidAmount. |
expired | status | Reached expiresAt without being paid in full. Expiry is enforced automatically. |
cancelled | status | Cancelled by you via DELETE while still pending. |
Fulfill on completed
Treatcompleted as paid. For partial and overpaid, reconcile against paidAmount, underpaidAmount, and overpaidAmount before deciding to fulfill or refund.Check a checkout's status
/v1/checkout/{checkoutId}publicRetrieve a single checkout and its current status. This route is public (it powers the hosted page), and the response is populated with the project name and the checkout's deposit address.
curl https://api.zyrapay.net/v1/checkout/chk_8f2c1a...{
"success": true,
"data": {
"checkoutId": "chk_8f2c1a...",
"status": "completed",
"totalAmount": 13,
"paidAmount": 13,
"overpaidAmount": 0,
"underpaidAmount": 0,
"currency": "PI",
"items": [ ... ],
"metadata": { "orderId": "order_1042" },
"expiresAt": "2026-06-14T12:30:00.000Z"
}
}A missing or unknown checkout returns 404 with { "success": false, "message": "Checkout session not found" }.
List checkouts
/v1/checkoutx-api-keyList checkouts for the project the API key belongs to, with filtering, sorting, and pagination. Useful for dashboards and reconciliation.
curl "https://api.zyrapay.net/v1/checkout?status=completed&limit=20&page=1&sort=createdAt&order=desc" \
-H "x-api-key: $ZYRA_API_KEY"Query parameters
| Field | Type | Description |
|---|---|---|
page | int | 1-based page number. Defaults to 1. |
limit | int | Page size, capped at 100. Defaults to 20. |
sort | string | One of createdAt, totalAmount, status. Defaults to createdAt. |
order | "asc" | "desc" | Sort direction. Defaults to desc. |
status | string | Filter by status (pending | completed | partial | overpaid | expired | cancelled). Use all for no filter. |
dateFrom | ISO date | Only checkouts created on or after this date. |
dateTo | ISO date | Only checkouts created on or before this date (inclusive). |
minAmount | number | Only checkouts with totalAmount ≥ this value. |
maxAmount | number | Only checkouts with totalAmount ≤ this value. |
q | string | Free-text search over the checkout ID and item names (case-insensitive). |
{
"success": true,
"data": {
"checkouts": [ ... ],
"total": 42,
"pages": 3
}
}Cancel a checkout
/v1/checkout/{checkoutId}x-api-keyCancel a checkout that belongs to your project. Only a pending checkout can be cancelled — once it has been paid or has expired, cancellation is rejected.
curl -X DELETE https://api.zyrapay.net/v1/checkout/chk_8f2c1a... \
-H "x-api-key: $ZYRA_API_KEY"{ "success": true, "message": "Checkout cancelled successfully" }Redirects, metadata, and expiry
After payment ZyraPay redirects the customer to successUrl, or to cancelUrl if they back out (redirectUrl is the fallback). Treat redirects as cosmetic — confirm fulfillment from the webhook or a status read, since the customer may close the tab first.
Whatever you put in metadata (top-level or per-item) is echoed back on status reads and webhooks — the cleanest way to tie a checkout to your own order. Every checkout expires at expiresAt (default 30 min via expiresInMinutes) and then becomes expired; create a fresh one rather than reusing it.