Accept payments by QR

One call creates a QR any Pakistani bank or wallet app can pay. We mint it, host the payment page, detect the payment and tell you. You send an amount and your own reference.

Quickstart

Get a key from the merchant portal, then:

curl -X POST https://api.qrix.uno/api/v1/qr \
  -H "Authorization: Bearer qrix_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1001" \
  -d '{
    "amount": "1500.00",
    "reference": "ORDER-1001",
    "email": "customer@example.com"
  }'

You get back a QR payload, an image URL and a hosted payment page:

{
  "id": "01K…",
  "reference": "ORDER-1001",
  "amount": "1500.00",
  "status": "pending",
  "qrCode": "000201010211…6304A1B2",
  "qrImageUrl": "https://pay.qrix.uno/4z91qk6t/qr.png",
  "payUrl": "https://pay.qrix.uno/4z91qk6t",
  "expiresAt": "2026-09-12T09:00:00.000Z"
}

Render qrCode yourself, use qrImageUrl, or just send the customer payUrl. All three are the same payment.

Authentication

Every request carries your secret key as a bearer token.

Authorization: Bearer qrix_live_a1b2c3d4_…
Livehttps://api.qrix.uno
Sandboxhttps://sbx-api.qrix.uno
Keys are shown once We store only a hash, so a key cannot be recovered — if you lose it, revoke it and make another. Keep it server-side: anyone holding it can create payment requests on your account.

Create a QR

POST /api/v1/qr
FieldRequiredNotes
amountyesRupees as a string, up to 2 decimals — "1500.00"
referenceyesYour own identifier. Echoed back everywhere, including webhooks
descriptionnoShown to the payer on the payment page
emailnoRecords who the QR is for. We do not contact them — see below
mobilenoPakistani mobile, 03… or +923…
expiresInDaysnoDefaults to 15. 1–365
submerchantnoorganisation, userId, name, city, country
metadatanoYour own key/value pairs, returned unchanged
Always send an Idempotency-Key If your request times out and you retry without one, you create a second real payable QR. With one, the retry returns the original response instead. Reusing a key with different parameters returns 409 rather than the wrong answer.
You notify your customer, we notify you QRIX never emails or messages the payer. email and mobile are stored against the payment for your own reporting and reconciliation, nothing more. Send them payUrl, qrImageUrl or the raw qrCode however you already reach your customers — your templates, your sending domain, your reputation. We tell you the moment it is paid, by webhook.

Sub-merchant fields

If you have branches, tenants or agents, populate submerchant now — we store and return it unchanged today, so your integration is ready when it starts driving behaviour.

"submerchant": {
  "organisation": "Branch 12",
  "userId": "agent-88",
  "name": "Gulberg Outlet",
  "city": "Lahore",
  "country": "PK"
}

Check status

GET /api/v1/qr/:id
GET /api/v1/qr?reference=ORDER-1001

Both answer from our records — fast, and reflecting everything we have detected. Poll these.

POST /api/v1/qr/:id/inquire

Forces a live re-check with the bank, right now. Use it when you believe a payment happened and our record disagrees. Rate-limited, because it is a synchronous call to a third party.

statusMeaning
pendingNot paid yet
paidMoney received. paidAt is set
expiredPast its expiry and never paid
failedThe payment attempt failed

Webhooks

Set your URL in the portal and we POST when a payment is paid. We retry with backoff for about fifteen hours if your server is unreachable.

POST https://yourserver.com/qrix/webhook
QRIX-Signature: t=1787304178,v1=9f8e7d…
QRIX-Event-Id:  evt_01K…_paid
QRIX-Event-Type: payment.paid

{
  "type": "payment.paid",
  "id": "evt_01K…_paid",
  "createdAt": "2026-08-28T06:37:12.081Z",
  "data": {
    "id": "01K…",
    "reference": "ORDER-1001",
    "amount": "1500.00",
    "status": "paid",
    "paidAt": "2026-08-28T06:37:12.081Z"
  }
}

Verify the signature

The header is t=<unix>,v1=<hmac>, where the HMAC is SHA-256 over "<t>.<raw body>" using your signing secret. The timestamp is inside the signed material, so a captured delivery cannot be replayed later.

const [t, v1] = header.split(',').map(p => p.split('=')[1])
const expected = crypto.createHmac('sha256', secret)
  .update(t + '.' + rawBody).digest('hex')

const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1))
  && Math.abs(Date.now()/1000 - Number(t)) < 300
Verify before you trust it Your endpoint is public. Check the signature and the timestamp, respond 2xx quickly, and do your work afterwards. Deduplicate on QRIX-Event-Id — it is stable across retries.

Sandbox

A complete copy at https://sbx-api.qrix.uno with its own data and its own keys. Nothing there is real and nothing there can move money.

Errors

{ "error": { "code": "validation_failed", "message": "Please check the highlighted fields",
             "fields": { "amount": ["Amount must be a number with up to 2 decimals"] },
             "requestId": "req_a1b2c3" } }
StatusMeaning
400Something in your request is invalid — fields says what
401Missing, malformed or revoked API key
404No such QR on your account
409Idempotency key reused with different parameters
429Too many requests — back off and retry

Quote requestId when you contact us; it identifies the exact request in our logs.