Static QR

One QR printed once, used many times. Best for small merchants and any acceptance setup where you can't display a fresh QR per transaction.

A static QR is one code printed once and presented over and over. Customers scan the same code for every transaction. You either let the customer enter the amount on their phone, or you update the amount on the code from your backend before each sale.

This is the cheapest pattern to deploy — no screen, no terminal, just a printed sticker. It's the most common acceptance method for informal merchants, market stalls, food trucks, hair salons, and small retail.


When to use a static QR

✅ Good fit❌ Wrong fit
Counter or wall poster at a single merchantMultiple amounts displayed simultaneously
One terminal, one cashierHigh-volume tills where amounts vary in seconds
Outdoor signage or sticker on equipmentCustomer expects a per-transaction QR on a screen
Cash-business augmentationE-commerce checkouts (use Bluebox hosted checkout instead)

For high-volume terminals where the amount changes per transaction, use a Dynamic QR instead — fresh code per sale, amount embedded.


Setup — once per device

You create the static code one time and use it forever.

curl -X POST "https://qa.scantopay.io/pluto/code/create" \
  -u "$USERNAME:$PASSWORD" \
  -H 'Content-Type: application/json' \
  -d '{
    "merchantReference": "static-counter-01",
    "amount": 0,
    "useOnce": false,
    "shortDescription": "Pay Acme Coffee"
  }'

Response:

{
  "code": "0123456789",
  "expiryDate": 0,
  "codeUrl": null
}

Two important fields:

  • amount: 0 — zero means variable amount. The customer enters the amount they want to pay on their phone.
  • useOnce: false — re-usable across transactions. With true, the code would invalidate after the first successful payment.

Print the 10-digit code as a QR — see Rendering the QR.

📘

Default expiry. Static codes with expiryDate: 0 never expire. If you omit expiryDate, the platform applies the default 30-minute expiry which isn't what you want for a printed sticker. Always set expiryDate: 0 for printed codes.


Per-transaction — variant A: variable amount

If you created the code with amount: 0, the customer enters the amount on their phone. There's nothing else for you to do on the backend until the payment notification arrives.

The flow is:

  1. Customer scans the QR.
  2. The Scan to Pay app prompts for the amount.
  3. Customer enters the amount and authorises with PIN or 3DS.
  4. Scan to Pay POSTs the webhook to your notification URL.
  5. Your handler returns HTTP 200 within 45 seconds.

Per-transaction — variant B: server-updated amount

If you'd prefer not to let the customer type the amount — for accuracy, audit, or display purposes — you can update the amount on the existing code from your backend before each sale:

curl -X PUT "https://qa.scantopay.io/pluto/code/0123456789/amount" \
  -u "$USERNAME:$PASSWORD" \
  -H 'Content-Type: application/json' \
  -d '{
    "amount": 75.00,
    "merchantReference": "static-counter-01-sale-2025-05-14-001"
  }'

This updates the code to a fixed amount of R75. The next scan presents the customer with R75 to confirm; they can't change it. After the payment completes, the code returns to its previous state and is ready to be updated again with a new amount.

📘

Each amount update should include a fresh merchantReference to keep your reconciliation clean. The original merchantReference from the create call stays on the code; the update reference flows through to the transaction record.

See Managing QR codes for the full update-amount API.


Receiving the payment notification

Both variants result in a standard webhook delivery to your notification URL. The payload contains the transaction outcome, amount, and bank response. See Webhooks.

The reference field on the webhook payload is the merchantReference you passed (either on create for variant A, or on update-amount for variant B), which is how you reconcile to your sale record.


What's next