Hosted QR checkout
Payment status
Two read-only calls tell you where a payment stands. Use them as a fallback to the postback, not instead of it.
The three statuses
status | Meaning | What you do |
|---|---|---|
pending | Not final yet. The customer has not paid, or has not even submitted their details. | Wait. Do not ship, do not cancel. |
completed | Paid. | Fulfil the order (once). |
failed | Not paid: expired or failed. | Offer the customer a new payment, with a new reference. |
success: true in an answer only means the call worked. It is true for a failed payment too. Always read status.
By your reference
GET https://api.antpay.network/api/v1/transactions/{reference}. URL-encode the reference. This is the call to use after a create that timed out, because you always know your own reference. Once you have a checkoutId, prefer the call by checkout id below: it names exactly one checkout.
The lookup is across your whole merchant account, not per API key. If you use more than one key, keep your references unique across all of them.
cURL
# Ask for the payment by YOUR reference, or by checkoutId. A 404 whose "error" is "Transaction not found" means there
# is no such payment. Any other 404 means the address itself was refused: check the reference, do not create again.
antpay_get_by_reference() { antpay_call GET "/api/v1/transactions/$(antpay_urlencode "$1")"; }
antpay_get_checkout() { antpay_call GET "/api/v1/checkouts/$(antpay_urlencode "$1")"; }Part of antpay.sh, the complete tested file
Node.js
// Ask for the payment by YOUR reference. Use it after an unclear create, and as a fallback to the postback.
// Returns null ONLY when the API itself says there is no such payment. Any other 404 means the address was refused
// (a wrong path, or a reference with characters that cannot travel in a URL): that is an error, not "no payment".
async function getByReference(reference) {
const { http: status, json } = await call('GET', '/api/v1/transactions/' + encodeURIComponent(reference));
if (status === 200 && json && json.success) return json.transaction; // .status is pending | completed | failed
if (status === 404 && json && json.error === 'Transaction not found') return null;
throw new Error(`status call failed (HTTP ${status}${json && json.error ? ', ' + JSON.stringify(json.error) : ''})`);
}
async function getCheckout(checkoutId) {
const { http: status, json } = await call('GET', '/api/v1/checkouts/' + encodeURIComponent(checkoutId));
if (status === 200 && json && json.success) return json.checkout;
if (status === 404 && json && json.error === 'Checkout session not found') return null;
throw new Error(`status call failed (HTTP ${status}${json && json.error ? ', ' + JSON.stringify(json.error) : ''})`);
}Part of antpay.js, the complete tested file
PHP
// Ask for the payment by YOUR reference. Use it after an unclear create, and as a fallback to the postback.
// Returns null ONLY when the API itself says there is no such payment. Any other 404 means the address was refused
// (a wrong path, or a reference with characters that cannot travel in a URL): that is an error, not "no payment".
function antpay_get_by_reference(string $reference): ?array {
[$status, $data] = antpay_call('GET', '/api/v1/transactions/' . rawurlencode($reference));
if ($status === 200 && is_array($data) && !empty($data['success'])) return $data['transaction']; // ['status'] is pending | completed | failed
if ($status === 404 && is_array($data) && ($data['error'] ?? null) === 'Transaction not found') return null;
throw new RuntimeException("status call failed (HTTP $status, " . json_encode($data['error'] ?? null) . ')');
}
function antpay_get_checkout(string $checkoutId): ?array {
[$status, $data] = antpay_call('GET', '/api/v1/checkouts/' . rawurlencode($checkoutId));
if ($status === 200 && is_array($data) && !empty($data['success'])) return $data['checkout'];
if ($status === 404 && is_array($data) && ($data['error'] ?? null) === 'Checkout session not found') return null;
throw new RuntimeException("status call failed (HTTP $status, " . json_encode($data['error'] ?? null) . ')');
}Part of antpay.php, the complete tested file
Python
# Ask for the payment by YOUR reference. Use it after an unclear create, and as a fallback to the postback.
# Returns None ONLY when the API itself says there is no such payment. Any other 404 means the address was refused
# (a wrong path, or a reference with characters that cannot travel in a URL): that is an error, not "no payment".
def get_by_reference(reference):
status, data = call("GET", "/api/v1/transactions/" + urllib.parse.quote(reference, safe=""))
if status == 200 and data and data.get("success"):
return data["transaction"] # ["status"] is pending | completed | failed
if status == 404 and data and data.get("error") == "Transaction not found":
return None
raise RuntimeError(f"status call failed (HTTP {status}, {data.get('error') if data else None})")
def get_checkout(checkout_id):
status, data = call("GET", "/api/v1/checkouts/" + urllib.parse.quote(checkout_id, safe=""))
if status == 200 and data and data.get("success"):
return data["checkout"]
if status == 404 and data and data.get("error") == "Checkout session not found":
return None
raise RuntimeError(f"status call failed (HTTP {status}, {data.get('error') if data else None})")Part of antpay.py, the complete tested file
{
"success": true,
"transaction": {
"reference": "ORD-10021",
"txnId": "FDX-1A2B3C4D",
"checkoutId": "3f6c1d2e-8a4b-4c1e-9d7a-2b5e6f7a8c90",
"amount": 1500,
"paymentMethod": "qr",
"status": "completed",
"statusCode": "SUCCESS",
"createdAt": "2026-11-01T10:00:00.000Z",
"updatedAt": "2026-11-01T10:04:12.000Z",
"walletName": "easypaisa",
"accountNumber": "0312•••4567"
}
}
statusCode is null while the payment is pending.
Two different 404s. 404 with "error": "Transaction not found" comes from the API: there is no payment with that reference. 404 with "error": "NOT_FOUND" means the address itself was refused before it reached the API (a wrong path, or a reference containing characters other than letters, digits, ., _ and -): it says nothing about whether the payment exists. The samples return "none" only for the first and raise an error for the second.
By checkout id
GET https://api.antpay.network/api/v1/checkouts/{checkoutId}. Use the checkoutId you stored for the order. This is the reliable way to ask about the checkout your customer was sent to.
{
"success": true,
"checkout": {
"checkoutId": "3f6c1d2e-8a4b-4c1e-9d7a-2b5e6f7a8c90",
"txnId": "FDX-1A2B3C4D",
"reference": "ORD-10021",
"amount": 1500,
"paymentMethod": "qr",
"status": "pending",
"createdAt": "2026-11-01T10:00:00.000Z",
"walletName": "easypaisa",
"accountNumber": "0312•••4567"
}
}
When to ask
| Situation | What to do |
|---|---|
| Normal flow | Do not poll. Wait for the postback. |
The customer is back on your returnUrl page and your order is still unpaid | One status call, then show "we are confirming your payment" if it is still pending. The postback will follow. |
| No postback an hour after you created the checkout | Ask by reference. If it is final, record it exactly as you would a postback. (An unpaid payment is not final until at least 30 minutes after the customer got the QR, and they may take up to expiresIn to ask for it.) |
The create call timed out or returned 5xx | Ask by reference before you create again. |
| Your server was down for a while | Ask by reference for every order still unpaid on your side. |
If you do poll one payment, leave at least 10 seconds between calls. Status calls count against your rate limit.
The link is the customer's status page
- The same
checkoutUrlalways opens and shows the payment as it stands, today or months later. - Once a QR exists, or once the link has expired, the page never shows the form again and never makes a new QR.
- After the payment window the QR is never shown again.
- Opening a link that already has a final result shows that result with a Return button; it does not redirect.
You can show checkoutUrl on your order page or in your order email as "Check payment status".