API Reference

Everything you need to integrate PayNow QR generation into your application.

Quickstart

1
Get an API key. Sign up for a free account. Your secret key (sk_paynow_...) is shown once on the dashboard.
2
Base URL: https://paynow.doubleam.com
3
Make your first call:
curl -X POST https://paynow.doubleam.com/api/v1/generate \
  -H "Content-Type: application/json" \
  -H "x-api-key: sk_paynow_YOUR_KEY" \
  -d '{
    "payment_type": "uen",
    "uen": "202521700C",
    "amount": "10.50",
    "merchant_name": "My Business"
  }'

The response includes a qr_string (raw EMVCo payload), card_url (branded card image), and image_url (plain QR). Use the card URL in WhatsApp templates, emails, or anywhere an image URL is needed.

Authentication

Pass your API key in the x-api-key header on every request to authenticated endpoints.

x-api-key: sk_paynow_YOUR_SECRET_KEY

Key Types

TypePrefixUseDomain Restriction
Secretsk_paynow_Server-side calls. Full access.No
Publishablepk_paynow_Client-side / browser calls.Yes — restricted to allowed domains (checked via Origin/Referer header)

Secret keys must never be exposed in client-side code. Use publishable keys with domain restrictions for browser integrations.

POST /api/v1/generate

Generate a PayNow QR code. Returns the raw QR string, image URLs, and optionally a WhatsApp link. Requires API key.

Request Parameters

ParameterTypeRequiredDescription
payment_typestringYes"uen" or "mobile"
uenstringIf UENBusiness UEN number (8-12 alphanumeric chars)
mobilestringIf mobileSG mobile number. Accepts 91234567, 6591234567, +6591234567
amountstring|numberNoAmount in SGD. Tolerant parser: "200", "$350.50", "SGD 200.50" all work. Omit for any-amount QR.
merchant_namestringNoMerchant name on the card (max 25 chars, default "NA")
referencestringNoYour reference / invoice number (max 25 chars). This is the default way to attach a reference.
generate_referencebooleanNoSet true to auto-generate a reference (opt-in only)
reference_prefixstringNoPrefix for auto-generated reference (default "PN")
whatsapp_tostringNoWhatsApp number with country code (e.g. "6591234567"). When provided, the response includes a whatsapp_url.
sizeintegerNoQR image size in pixels (default 512)
metadataobjectNoArbitrary JSON stored with the payment request record

Response

{
  "qr_string": "00020101021226370009SG.PAYNOW...",
  "image_url": "https://paynow.doubleam.com/api/v1/qr.png?...",
  "card_url": "https://paynow.doubleam.com/api/v1/card.png?...",
  "whatsapp_url": "https://wa.me/6591234567?text=...",
  "payment": {
    "paymentType": "uen",
    "proxy": "202521700C",
    "amount": "10.50",
    "editable": false,
    "merchantName": "My Business",
    "reference": "INV-001",
    "expiry": null,
    "currency": "SGD",
    "static": false
  }
}

whatsapp_url is only present when whatsapp_to is provided. static is true when no amount is set (reusable QR).

GET /api/v1/card.png

Returns a branded PayNow card as a PNG image. Public endpoint — no API key required. This is what WhatsApp, email clients, and browsers fetch.

Query Parameters

Same as /generate: payment_type, uen/mobile, amount, merchant_name, reference, size.

Response

Content-Type: image/png — raw PNG bytes. Use the URL directly in <img> tags or WhatsApp template headers.

curl "https://paynow.doubleam.com/api/v1/card.png?payment_type=uen&uen=202521700C&amount=10.50&merchant_name=TEST" \
  -o card.png

GET /api/v1/qr.png

Returns a plain QR code PNG (no branding). Public endpoint.

Same query parameters as /card.png. Use when you need a raw QR without the branded card layout.

GET /api/v1/records

List payment requests generated with your API key. Requires API key. Use this to pull records into your own system for reconciliation.

Query Parameters

ParameterTypeDefaultDescription
limitinteger20Max records to return (1–100)
offsetinteger0Pagination offset

Response

{
  "records": [
    {
      "id": "uuid",
      "payment_type": "uen",
      "proxy": "202521700C",
      "amount": "10.50",
      "merchant_name": "My Business",
      "reference": "INV-001",
      "created_at": "2026-06-27T12:00:00.000Z"
    }
  ],
  "limit": 20,
  "offset": 0
}

GET /health

Liveness check. Public, no auth.

{ "ok": true, "service": "doubleam-paynow-api", "version": "2.0.0" }

Reference Behaviour

The reference field in the QR encodes a bill/invoice number (max 25 chars) that the payer's bank app may display. Three modes:

You SendWhat Happens
"reference": "INV-001"Default. Your reference is used as-is in the QR. Ideal when you have your own invoice/order numbering.
"generate_reference": trueOpt-in auto-gen. A unique reference is generated (e.g. PN-MQWI1Y4U9RQ). Use reference_prefix to set a custom prefix.
(neither)No reference. The QR contains no bill number field.

The card displays exactly the reference value that is encoded in the QR (up to 25 characters). There is no display/encode mismatch.

Errors

All errors return JSON with error and message fields.

StatusError CodeMeaning
400bad_requestRequest body is not valid JSON
400invalid_inputMissing/invalid parameters (e.g. bad UEN, negative amount)
401unauthorizedMissing or invalid API key
403forbiddenPublishable key used from an unauthorized domain
429rate_limitedMonthly quota or per-IP rate limit exceeded

429 Rate Limit Response

{
  "error": "rate_limited",
  "message": "Monthly limit exceeded (50/50). Upgrade your plan for more requests.",
  "usage": { "used": 50, "limit": 50 }
}

Rate Limits

LimitScopeValue
Monthly quotaPer API key50 requests/month (Free plan). Only POST /generate counts.
Per-IP rate limitPublic image endpoints60 requests/minute for /card.png and /qr.png

The GET /records endpoint does not count toward the monthly quota. When the quota is exceeded, the API returns 429 with a JSON body showing current usage.

Code Examples

curl

curl -X POST https://paynow.doubleam.com/api/v1/generate \
  -H "Content-Type: application/json" \
  -H "x-api-key: sk_paynow_YOUR_KEY" \
  -d '{
    "payment_type": "uen",
    "uen": "202521700C",
    "amount": "150.00",
    "merchant_name": "Acme Pte Ltd",
    "reference": "INV-2026-042"
  }'

Node.js (fetch)

const response = await fetch("https://paynow.doubleam.com/api/v1/generate", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-api-key": "sk_paynow_YOUR_KEY",
  },
  body: JSON.stringify({
    payment_type: "uen",
    uen: "202521700C",
    amount: "150.00",
    merchant_name: "Acme Pte Ltd",
    reference: "INV-2026-042",
  }),
});

const data = await response.json();
console.log(data.card_url);   // branded card image URL
console.log(data.qr_string);  // raw EMVCo payload

Python (requests)

import requests

resp = requests.post(
    "https://paynow.doubleam.com/api/v1/generate",
    headers={"x-api-key": "sk_paynow_YOUR_KEY"},
    json={
        "payment_type": "uen",
        "uen": "202521700C",
        "amount": "150.00",
        "merchant_name": "Acme Pte Ltd",
        "reference": "INV-2026-042",
    },
)

data = resp.json()
print(data["card_url"])   # branded card image URL
print(data["qr_string"])  # raw EMVCo payload

PHP

$ch = curl_init("https://paynow.doubleam.com/api/v1/generate");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "Content-Type: application/json",
        "x-api-key: sk_paynow_YOUR_KEY",
    ],
    CURLOPT_POSTFIELDS => json_encode([
        "payment_type" => "uen",
        "uen" => "202521700C",
        "amount" => "150.00",
        "merchant_name" => "Acme Pte Ltd",
        "reference" => "INV-2026-042",
    ]),
]);

$response = curl_exec($ch);
curl_close($ch);

$data = json_decode($response, true);
echo $data["card_url"];   // branded card image URL
echo $data["qr_string"];  // raw EMVCo payload

OpenAPI Spec

The full OpenAPI 3.1 specification is available for generating client SDKs and importing into API tools like Postman or Insomnia.

Download openapi.yaml