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.

Hosted checkout flow
Create
POST /v1/checkout
Customer
pays on hosted page
ZyraPay
verifies payment
Success
redirect + webhook
Create a checkout, redirect the customer, and get notified the moment they pay.

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. 1

    Create the checkout

    Call POST /v1/checkout from your server with your line items and your redirect URLs. ZyraPay works out the total and returns a checkoutId, a ready-made checkoutUrl, and an expiresAt time (when it stops accepting payment).
  2. 2

    Redirect the customer

    Send the customer to checkoutUrl. 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. 3

    Customer pays in Pi

    The customer pays. ZyraPay sees the payment on-chain, checks it, records it once, and updates the checkout's paidAmount and status.
  4. 4

    Receive the result

    The customer is redirected to your successUrl (or cancelUrl), and ZyraPay sends a signed checkout.paid webhook to your webhookUrl. 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. Call POST /v1/checkout from your backend only — never ship the x-api-key header to the browser.

Create a checkout

POST/v1/checkoutx-api-key

Authenticate 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

FieldTypeDescription
itemsrequiredobject[]Non-empty array of line items. The checkout total is the sum of amount × quantity across all items.
items[].namerequiredstringDisplay name of the line item, shown on the hosted page.
items[].amountrequirednumberUnit price (positive). In the checkout's currency.
items[].quantityrequirednumberQuantity of the item (positive integer).
items[].descriptionstringOptional longer description for the line item.
items[].imagestringOptional 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.
successUrlstringURL the customer is redirected to after a successful payment.
cancelUrlstringURL the customer is redirected to if they abandon or cancel.
redirectUrlstringOptional fallback redirect used when a dedicated success/cancel URL is not set.
webhookUrlstringURL to receive signed webhooks for this checkout. If set, ZyraPay sends checkout.created immediately and a payment event later.
expiresInMinutesnumberMinutes until the checkout expires. Positive; defaults to 30.
metadataobjectArbitrary 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.

201 Created
{
  "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.

FieldTypeDescription
pendingstatusCreated and awaiting payment. The default starting state.
completedstatusPaid in full — paidAmount equals totalAmount. Fulfill the order.
partialstatusSome Pi received, but less than the total. The shortfall is tracked as underpaidAmount.
overpaidstatusMore than the total received. The excess is tracked as overpaidAmount.
expiredstatusReached expiresAt without being paid in full. Expiry is enforced automatically.
cancelledstatusCancelled by you via DELETE while still pending.

Fulfill on completed

Treat completed as paid. For partial and overpaid, reconcile against paidAmount, underpaidAmount, and overpaidAmount before deciding to fulfill or refund.

Check a checkout's status

GET/v1/checkout/{checkoutId}public

Retrieve 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
curl https://api.zyrapay.net/v1/checkout/chk_8f2c1a...
200 OK
{
  "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

GET/v1/checkoutx-api-key

List checkouts for the project the API key belongs to, with filtering, sorting, and pagination. Useful for dashboards and reconciliation.

cURL
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

FieldTypeDescription
pageint1-based page number. Defaults to 1.
limitintPage size, capped at 100. Defaults to 20.
sortstringOne of createdAt, totalAmount, status. Defaults to createdAt.
order"asc" | "desc"Sort direction. Defaults to desc.
statusstringFilter by status (pending | completed | partial | overpaid | expired | cancelled). Use all for no filter.
dateFromISO dateOnly checkouts created on or after this date.
dateToISO dateOnly checkouts created on or before this date (inclusive).
minAmountnumberOnly checkouts with totalAmount ≥ this value.
maxAmountnumberOnly checkouts with totalAmount ≤ this value.
qstringFree-text search over the checkout ID and item names (case-insensitive).
200 OK
{
  "success": true,
  "data": {
    "checkouts": [ ... ],
    "total": 42,
    "pages": 3
  }
}

Cancel a checkout

DELETE/v1/checkout/{checkoutId}x-api-key

Cancel 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
curl -X DELETE https://api.zyrapay.net/v1/checkout/chk_8f2c1a... \
  -H "x-api-key: $ZYRA_API_KEY"
200 OK
{ "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.

Next step

Wire up your endpoint to receive the checkout.paid event in the Webhooks guide, or generate per-customer deposit addresses with Wallets.

Search docs

Search the documentation

Ask the AI assistant