Quickstart

Take your first crypto payment in about five minutes. You'll get your API key, create a checkout, send a test customer to pay, and see the payment land on your server.

How a ZyraPay payment flows
Customer
pays in Pi
ZyraPay
verifies on-chain
Your server
signed webhook
Order
fulfilled
A customer pays in Pi → ZyraPay verifies on-chain → your server receives a signed webhook.

Every ZyraPay request goes to https://api.zyrapay.net/v1 and authenticates with your project's x-api-key. You create a project and get that key from the dashboard — the four steps below take you all the way from key to a verified crypto payment.

Before you begin

You'll need three things:

  • A ZyraPay account with a payment project — create one in the dashboard; that's where your API key comes from.
  • A server endpoint that can receive an HTTPS POST for the webhook — for local development, a tunnel works (see Test locally below).
  • Node.js or curl to make the requests below.

Take a payment in 4 steps

  1. 1

    Get your API key

    Sign in to the ZyraPay dashboard and open (or create) a payment project. Each project has its own API key — copy it and store it on your server as ZYRA_API_KEY. You'll send it as the x-api-key header on every request below.

    Your server (.env)
    ZYRA_API_KEY=4f3c…e9      # from your project in the dashboard
    ZYRA_WEBHOOK_SECRET=…       # from the project's webhook settings

    Treat your API key like a password

    The key is shown in full only once when the project is created (ZyraPay stores just a SHA-256 hash). Keep it server-side — never ship it in browser or mobile code. If it leaks, rotate it from the dashboard. See Authentication.
  2. 2

    Create a checkout

    Checkouts are created with your API key on the /v1/checkout route. Each item needs a name, an amount, and a quantity; the total is the sum of amount × quantity. All checkouts are charged in Pi.

    POST/v1/checkoutx-api-key
    curl -X POST https://api.zyrapay.net/v1/checkout \
      -H "x-api-key: $ZYRA_API_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: order-ORD-1001" \
      -d '{
        "items": [
          { "name": "T-Shirt", "amount": 5, "quantity": 2 },
          { "name": "Sticker", "amount": 1, "quantity": 3 }
        ],
        "expiresInMinutes": 30,
        "successUrl": "https://my-store.example.com/success",
        "cancelUrl": "https://my-store.example.com/cancel",
        "webhookUrl": "https://my-store.example.com/zyra/webhook",
        "metadata": { "orderId": "ORD-1001" }
      }'

    The response gives you the hosted checkout URL:

    Response (201)
    {
      "success": true,
      "checkoutId": "chk_9f2a1b3c4d5e6f70",
      "checkoutUrl": "https://checkout.zyrapay.net/chk_9f2a1b3c4d5e6f70",
      "expiresAt": "2026-06-14T12:30:00.000Z",
      "totalAmount": 13,
      "items": [ /* … */ ]
    }

    Pricing details

    Checkouts expire after 30 minutes by default. If you send an item currency of USD, USDT, or DOLLAR, the total is converted to Pi at a live rate at creation time — but the checkout is always stored and charged in Pi.
  3. 3

    Redirect the customer to the checkout

    Send the buyer to checkoutUrl. ZyraPay mints a dedicated wallet for the checkout, and the hosted page collects the payment and updates live over a WebSocket as funds land.

    JavaScript
    // Browser redirect after creating the checkout
    window.location.href = checkout.checkoutUrl;
    
    // …or in Next.js / Express, send a 303 to the same URL
    res.redirect(303, checkout.checkoutUrl);

    On completion (or cancellation) the hosted page returns the customer to the successUrl / cancelUrl you provided.

  4. 4

    Receive and verify the webhook

    When the payment is observed on-chain, ZyraPay POSTs an HMAC-signed webhook to the checkout's webhookUrl (falling back to the project's). Every payment webhook carries these headers:

    Headers
    Content-Type: application/json
    User-Agent: Zyra-BN/1.0
    X-Project-ID: <projectId>
    X-Event-Type: checkout.paid
    X-Timestamp: <epoch-millis>
    X-Zyra-Signature: sha256=<hex>

    A checkout.paid body looks like this:

    Body
    {
      "type": "checkout.paid",
      "data": {
        "projectId": "665f…",
        "amount": 13,
        "checkoutId": "chk_9f2a1b3c4d5e6f70",
        "from": "G…payer",
        "transactionId": "<pi-payment-id>",
        "metadata": { "orderId": "ORD-1001" }
      },
      "timestamp": 1750000000000,
      "projectId": "665f…",
      "signature": "<hex>"
    }

    Always verify the signature before trusting the payload. It is an HMAC-SHA256 of the JSON body with the signature field excluded, keyed on your WEBHOOK_SECRET:

    server.js — Express
    const crypto = require("crypto");
    const express = require("express");
    const app = express();
    
    app.use(express.json());
    
    function verifyZyraWebhook(req, secret) {
      const header = req.get("X-Zyra-Signature") || ""; // "sha256=<hex>"
      const received = header.replace("sha256=", "");
    
      // The server signs the payload object WITHOUT the `signature` field.
      const { signature, ...payload } = req.body;
      const expected = crypto
        .createHmac("sha256", secret)
        .update(JSON.stringify(payload))
        .digest("hex");
    
      return crypto.timingSafeEqual(
        Buffer.from(expected, "hex"),
        Buffer.from(received, "hex")
      );
    }
    
    app.post("/zyra/webhook", (req, res) => {
      if (!verifyZyraWebhook(req, process.env.ZYRA_WEBHOOK_SECRET)) {
        return res.status(400).send("invalid signature");
      }
    
      const { type, data } = req.body;
      if (type === "checkout.paid") {
        // Fulfill the order — data.metadata.orderId, data.amount, etc.
        fulfillOrder(data.metadata.orderId);
      }
    
      // Acknowledge quickly with a 2xx so ZyraPay stops retrying.
      res.sendStatus(200);
    });

    That's the full loop

    You created a project, opened a checkout, redirected the customer, and verified the payment from a signed webhook. Everything else in these docs builds on this same pattern.

Test locally

There is no separate sandbox base URL — every request goes to https://api.zyrapay.net/v1. You isolate development by using a dedicated test project (with its own API key and webhook URL) and pointing its webhooks at your machine through a tunnel. Keep test amounts small — payments are real.

  1. 1

    Run your webhook handler and expose it

    Start the handler from step 4 locally, then open a public HTTPS tunnel to its port so ZyraPay can reach it:

    ngrok http 4000
    # → https://random-id.ngrok.app -> http://localhost:4000
  2. 2

    Point the test checkout at the tunnel

    Pass the tunnel URL as the checkout's webhookUrl (a per-checkout URL overrides the project default):

    cURL
    curl -X POST https://api.zyrapay.net/v1/checkout \
      -H "x-api-key: $ZYRA_API_KEY_TEST" \
      -H "Content-Type: application/json" \
      -d '{
        "items": [{ "name": "Test item", "amount": 1, "quantity": 1 }],
        "webhookUrl": "https://random-id.ngrok.app/zyra/webhook"
      }'
  3. 3

    Pay and watch it arrive

    Open the returned checkoutUrl, pay with your Pi wallet, and confirm your handler verifies X-Zyra-Signature and returns 2xx. Run the same signature verification in test that you run in production — testing that path locally is the point. If an expected webhook never arrives, reconcile with GET /v1/checkout/{checkoutId} rather than waiting.

Next steps

  • Authentication — API keys, rotation, and the Idempotency-Key header.
  • Advanced — safe retries, refunds, errors, and rate limits.
  • Going Live — the production checklist.

Search docs

Search the documentation

Ask the AI assistant