Home/Developers
Developers

Plug in, or build on it.

Install the WooCommerce plugin and you are done. If you run your own stack, create a checkout session with one API call, send the buyer to the hosted page, and let signed webhooks tell your system what happened.

Create a checkout session
curl https://api.zonipay.com/v1/checkout_sessions \
  -H "Authorization: Bearer zp_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "line_items": [
      { "name": "Reference peptide, 5 mg vial",
        "unit_amount": 4800, "quantity": 2 }
    ],
    "customer_email": "lab@example.edu",
    "success_url": "https://yourstore.com/thanks",
    "cancel_url": "https://yourstore.com/cart",
    "metadata": { "order_id": "1042" }
  }'

# {"id": "cs_test_8Hq2...", "url": "https://checkout.yourstore.com/c/cs_test_8Hq2..."}
Integration options

Four ways in. Same checkout.

Whichever you choose, the buyer lands on the same hosted checkout on your subdomain, with the research-use attestation and the receipt from Zonipay.

No code

WooCommerce plugin

Adds Zonipay as the payment method, sends the cart to the hosted checkout and updates the order status when payment lands.

FitsStores already running on WooCommerce.
No code

Hosted checkout link

A payment link created in your dashboard for a fixed basket, to send by email or put behind a button.

FitsQuotes, repeat lab orders, a small catalog.
Code

REST API and Node.js SDK

Create a checkout session from your server with the cart you already hold, then redirect the buyer to its URL.

FitsCustom storefronts and headless builds.
Code

Signed webhooks

Events for paid orders, refunds, disputes, payouts and compliance findings, posted to your endpoint and signed.

FitsEvery integration that keeps its own records.
Checkout sessions

One call, one URL.

Send the line items, the buyer's email and where to return them. You get back a session id and the url of the hosted page. Amounts are integers in the smallest unit of your account currency.

  • Base URL https://api.zonipay.com, versioned in the path
  • Authenticate with your secret key; never ship it to a browser
  • Put your own order reference in metadata: it comes back on every event
  • Treat the redirect to success_url as a courtesy, and the order.paid webhook as the record

The API is still being designed. Examples on this page show its intended shape; we confirm the exact surface with you during onboarding.

server.js
import Zonipay from "@zonipay/node";

const zonipay = new Zonipay(process.env.ZONIPAY_SECRET_KEY);

const session = await zonipay.checkoutSessions.create({
  line_items: [
    { name: "Reference peptide, 5 mg vial", unit_amount: 4800, quantity: 2 },
  ],
  customer_email: "lab@example.edu",
  success_url: "https://yourstore.com/thanks",
  cancel_url: "https://yourstore.com/cart",
  metadata: { order_id: "1042" },
});

// Send the buyer to the hosted checkout
res.redirect(303, session.url);
200{ id, url }The URL lives on your checkout subdomain.
verify.js
import crypto from "node:crypto";

// header: "t=1767225600,v1=5f2b...", rawBody: the exact bytes received
export function verifySignature(rawBody, header, secret, toleranceSec = 300) {
  const parts = Object.fromEntries(
    header.split(",").map((p) => p.trim().split("="))
  );
  const t = Number(parts.t);
  if (!t || !parts.v1) return false;
  if (Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${t}.${rawBody}`)
    .digest("hex");

  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(parts.v1, "hex");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Webhooks

Check the signature before you trust it.

Every event is posted to your HTTPS endpoint with a Zonipay-Signature header. Verify it with the signing secret shown for that endpoint, then act.

Zonipay-Signature: t=<unix time>,v1=<hex HMAC-SHA256 of "t.body">
01Read the raw request body before any JSON parser touches it.
02Rebuild the signed string as the timestamp, a full stop, then the body.
03Compare with a timing-safe function and reject stale timestamps to block replays.
04Answer with a 2xx quickly and do slow work afterwards. Events can arrive more than once, so key your handling on the event id.
Events

Six events cover the lifecycle.

EventSent whenTypical action
order.paidA checkout session is paid and the attestation is recorded.Mark the order paid, start fulfilment.
order.refundedAll or part of an order is refunded to the buyer.Update the order and stock.
dispute.openedA buyer's bank opens a chargeback on an order.Gather tracking and proof of delivery for our request.
dispute.closedThe dispute is decided, won or lost.Record the outcome on the order.
payout.paidA payout is sent to your bank account.Reconcile it against the orders it lists.
compliance.findingA storefront scan raises a finding, with severity, page and change needed.Route it to whoever edits the store.

Sandbox and test mode

Your account comes with a sandbox. Test keys start with zp_test_ and live keys with zp_live_; nothing made with a test key moves money. Use the test cards listed in your sandbox to try successful payments, declines and 3-D Secure challenges, and send test events to your webhook endpoint.

Sandboxzp_test_...
Productionzp_live_...

Versioning

The version is in the path: /v1. Within a version we only make additive changes, such as new optional fields or new event types, so your code should ignore fields it does not know. A change that could break an integration ships as a new version, announced by email in advance, and the previous version keeps working while you move.

The full walkthrough.

Keys, sessions, webhooks, fulfilment and the go-live checklist, in order.