Querying — business rules
The constraints, mutual-exclusivity rules, and reconciliation gotchas that govern transaction queries.
These rules govern every call you make to the Merchant Tx API endpoints. The biggest one — polling and webhooks are mutually exclusive — is enforced by the platform; the rest are guidelines that keep your reconciliation clean.
For the API itself, see Querying transactions.
Request rules
| Rule | Detail |
|---|---|
| Authentication required | Basic Auth or JWT Bearer with EXTERNAL role. See Authentication. |
| HTTPS only | Plain HTTP is rejected. |
| Username convention | merchant-{id} for merchants, psp-{id} for PSPs. The same credentials used for create-code and refunds. |
| Content-Type | application/json on POST requests. |
| Field lengths | Determined by the underlying parameter types — code is 10 digits, merchantReference up to 45 characters, transactionId is a signed 64-bit integer. |
The polling-vs-webhook mutex
This is the most important rule on this page.
If your merchant has an HTTP notification URL configured in the Portal,queryRefreturnsHTTP 406 Not Acceptable.
The platform forces you to pick one outcome-delivery mechanism per merchant. Either:
- HTTP webhooks, with the receiver acknowledging within 45 seconds and the platform reversing on missed acks (see Webhooks) — or
- Polling via
queryRef, with no webhook configured.
You can't combine them. The reason is that the platform's reversal-on-failure safety net depends on knowing whether you've received the outcome — if you can both poll and receive a webhook, the platform doesn't know which one's authoritative.
Exceptions
| Endpoint | Affected by the mutex? |
|---|---|
POST /code/queryRef | ✓ Yes — returns 406 if webhook is set |
POST /code/queryRef/refund | ✓ Yes — same behaviour |
GET /code/transaction/state/{transactionId} | ✗ No — works regardless of webhook configuration. Use this as your recovery endpoint when a webhook was missed. |
POST /code/transaction/stateByQr | ✗ No |
POST /code/deviceInfo/{txId} | ✗ No |
The recovery pattern: if you have webhooks configured and miss a delivery, use getTransactionState (the GET endpoint) to pull the missing notification. It returns the same shape as the webhook payload would have delivered.
Reference-uniqueness considerations
Your reconciliation depends on merchantReference being meaningful. Two failure modes the legacy docs called out specifically:
| Risk | Mitigation |
|---|---|
Same code used for multiple transactions with the same merchantReference | The platform tracks the latest state only — earlier transactions are not surfaced via queryRef. Always change the merchantReference between purchases on a re-usable code (useOnce: false). The update-amount endpoint accepts a fresh reference for each call — use it. |
| Same code paid multiple times because partial refunds are allowed | When reconciling, check amount, transactionId, date, and reference together — not just the code value. Two transactions can share a code if it's re-usable. |
Rule of thumb
For useOnce: true codes, the relationship is 1:1 — one code, one transaction. Safe to query by code + original merchantReference.
For useOnce: false codes (static QR, counter sticker), each payment should use a unique merchantReference via the update-amount endpoint. Query by that per-transaction reference, not by the original code-create reference.
Reconciliation checks before releasing goods
If you're using polling (no webhook), apply these checks before treating a transaction as paid:
status === 'SUCCESS'— never trust any other value- Amount matches what you expected for that order
transactionIdis one you haven't already processed (de-duplication)dateis within an expected window (catches replay attacks)referencematches the order record you're settling against
The same checks apply to the webhook path — see Webhooks > Best practices.
Partial-refund considerations when querying
When a transaction has been partially refunded:
queryRefon the original code + reference returns the original transaction's state — typicallySUCCESS(the original purchase is still successful).queryRefundon the same code + reference returns the refund's state —REFUNDEDfor the most recent refund.- The webhook for the refund itself uses
referenceTransactionIdto link back to the original —queryRefdoesn't give you a refund-by-refund history.
For full refund auditing, store every refund webhook you receive. For ad-hoc checks, use queryRefund to confirm the most recent refund completed.
Status of "N/A"
When the transaction doesn't yet exist in the platform (the customer hasn't paid), queryRef returns status: "N/A". This is not an error — it means "no transaction found for that code + reference yet."
Treat N/A as "keep polling" rather than as "abandoned." The customer might be mid-scan when you queried.
A genuinely failed lookup (wrong credentials, malformed request) returns a 4xx HTTP error, not N/A. See Errors.
Authentication failure
If you call any query endpoint with incorrect credentials, you receive 513 SECURITY_VIOLATION rather than 401 Unauthorized. The platform treats credential failures on the Merchant Tx API more strictly than on other endpoints — it's a higher-trust set of operations because the response can leak transaction details.
What's next
- The query endpoints themselves → Querying transactions
- Transaction state values you'll see in responses → Transaction states
- Switch to webhooks for production → Webhooks
- Idempotency model and
merchantReferenceuniqueness → Idempotency - Error codes → Errors
Updated 3 days ago
