Developer documentation

OPB API v1

Accept JazzCash, EasyPaisa and Bank QR with one REST API. Create a payment on your server, redirect the customer to the hosted checkout, and receive a signed webhook when it completes.

Base URL   https://zaod-opb.lovable.app/api/public/v1
Format     JSON over HTTPS
Currency   PKR

Authentication

Send your secret key as a Bearer token. sk_test_… keys create sandbox payments; sk_live_… keys create real payments. Find both in Dashboard → Developer. Secret keys must only live on your server.

Authorization: Bearer sk_test_xxxxxxxxxxxxxxxx

Create a payment

POST /payments

FieldTypeDescription
amountnumberRequired. PKR, up to 2 decimals
order_idstringRequired. Your order reference (≤100)
descriptionstringOptional. Shown on checkout
customer_namestringOptional
customer_phonestringOptional
return_urlurlOptional. Customer is redirected here with ?payment_id&order_id&status. Falls back to the dashboard default.
const res = await fetch("https://zaod-opb.lovable.app/api/public/v1/payments", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.OPB_SECRET_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    amount: 2500,
    order_id: "ORDER-1001",
    description: "Blue T-shirt",
    return_url: "https://yourshop.pk/thank-you",
  }),
});
const payment = await res.json();
res.redirect(payment.checkout_url); // Express

Response · 201

{
  "id": "pay_3f9c1a2b4d5e6f708192a3b4",
  "mode": "sandbox",
  "amount": 2500,
  "currency": "PKR",
  "order_id": "ORDER-1001",
  "status": "created",
  "reference": "OPB8A1C2D3E4F",
  "channel": null,
  "created_at": "2026-10-08T10:00:00Z",
  "paid_at": null,
  "expires_at": "2026-10-08T10:15:00Z",
  "checkout_url": "https://zaod-opb.lovable.app/checkout/pay_3f9c1a2b4d5e6f708192a3b4"
}

Check payment status

GET /payments/:id

Statuses: created · pending · success · failed · expired. Always confirm with this endpoint or a verified webhook before shipping an order — never trust the return URL alone.

const r = await fetch(`https://zaod-opb.lovable.app/api/public/v1/payments/${paymentId}`, {
  headers: { Authorization: `Bearer ${process.env.OPB_SECRET_KEY}` },
});
const { status } = await r.json();
HTTPError
401invalid_api_key
404not_found
422validation_error
400invalid_json

Hosted checkout

Redirect the customer to checkout_url. They pick JazzCash (Mobile Account, Card or Voucher), EasyPaisa or Bank QR, follow instructions with a 15-minute timer, and are sent back to your return URL after confirmation.

Webhooks

OPB sends a POST to your webhook URL for payment.successful, payment.failed and payment.pending. Respond with 2xx within 8 seconds.

POST /opb/webhook
Content-Type: application/json
X-OPB-Event: payment.successful
X-OPB-Signature: t=1791453600,v1=5f2b…c9e1

{
  "id": "evt_9b1d…",
  "event": "payment.successful",
  "created_at": "2026-10-08T10:03:12Z",
  "data": {
    "id": "pay_3f9c1a2b4d5e6f708192a3b4",
    "order_id": "ORDER-1001",
    "reference": "OPB8A1C2D3E4F",
    "amount": 2500,
    "currency": "PKR",
    "status": "success",
    "channel": "jazzcash",
    "method": "mwallet",
    "mode": "sandbox"
  }
}
{
  "event": "payment.failed",
  "data": {
    "id": "pay_…",
    "order_id": "ORDER-1001",
    "status": "failed",
    "channel": "easypaisa"
  }
}

Verify signatures (HMAC SHA-256)

  1. Read the raw request body (before JSON parsing).
  2. Split X-OPB-Signature into t and v1.
  3. Compute HMAC_SHA256(secret_key, t + "." + raw_body) as hex.
  4. Compare with v1 in constant time and reject timestamps older than 5 minutes.

Sandbox events are signed with your sk_test_ key; live events with sk_live_.

import crypto from "node:crypto";
// Express: app.post("/opb/webhook", express.raw({ type: "application/json" }), handler)
function handler(req, res) {
  const raw = req.body.toString("utf8");
  const parts = Object.fromEntries(req.get("x-opb-signature").split(",").map((p) => p.split("=")));
  const expected = crypto.createHmac("sha256", process.env.OPB_SECRET_KEY)
    .update(`${parts.t}.${raw}`).digest("hex");
  const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 ?? ""));
  if (!ok || Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return res.sendStatus(401);
  const event = JSON.parse(raw);
  if (event.event === "payment.successful") { /* mark order paid */ }
  res.sendStatus(200);
}

Sandbox

Use sandbox keys to build without moving money. Sandbox checkouts show "Simulate success" and "Simulate failure" buttons, fire real signed webhooks, and never touch your wallet balance. Use the Webhook Tester in Dashboard → Developer to send sample events and inspect your endpoint's response.