DevelopersAPI referenceSupport

Hosted QR checkout

Create a checkout

One server call per order. The answer is the link the customer pays on.

The request

POST https://api.antpay.network/api/v1/checkouts with your X-Api-Key.

FieldRequiredWhat to send
referenceyesYour order id. Unique per API key; a repeat returns 400. 1 to 120 characters from letters, digits, ., _ and -, and no ... The API accepts other characters when you create, but a reference with /, ;, \ or .. in it cannot be read back with a status call, so the samples refuse it.
amountyesPKR, whole rupees, minimum 300: 1500, not 1500.50.
paymentMethodyesAlways "qr".
paymentTypeyesAlways "onetime".
returnUrlyesWhere the page sends the customer after a final result. Use https. No parameters are added, so that page must read the order status from your own system.
expiresInnoMinutes the customer has to submit their details. Default 15, at most 1380.
usernoAny object of your own. It comes back in the postback as user.
accountNumber, walletNamenoThe customer's wallet number and wallet, if you already know them. See Two ways the page opens.
successUrlnoUsed as the postback URL only when none is set on your key or your account.

cURL

# Step 1: create the checkout. Put the request in a file, so the JSON is exactly what you wrote, for example
#   {"reference":"ORD-10021","amount":1500,"paymentMethod":"qr","paymentType":"onetime","returnUrl":"https://shop.example.com/orders/ORD-10021"}
# The answer carries checkoutUrl: send the customer there.
antpay_create_checkout() {   # antpay_create_checkout FILE_WITH_JSON_BODY
  antpay_call POST /api/v1/checkouts "$(cat "$1")"
}

Part of antpay.sh, the complete tested file

Node.js

// Step 1: create the checkout on your server, store the checkoutId, redirect the customer.
async function createCheckout(order) {
  if (!REFERENCE.test(order.id)) throw new Error('reference: use letters, digits, ".", "_" and "-" only (1 to 120 characters)');
  const { http: status, json } = await call('POST', '/api/v1/checkouts', {
    reference: order.id,                 // your order id, unique per API key
    amount: order.total,                 // whole rupees, minimum 300
    paymentMethod: 'qr',
    paymentType: 'onetime',
    returnUrl: `https://shop.example.com/orders/${encodeURIComponent(order.id)}`,
  });
  if (status === 200 && json && json.success) {
    return { checkoutId: json.checkoutId, checkoutUrl: json.checkoutUrl, expiresAt: json.expiresAt };
  }
  // 4xx: nothing was created (a duplicate reference already exists: look it up).
  // 5xx or no answer: the checkout may exist. Ask by reference before you retry.
  const error = new Error(`checkout not created (HTTP ${status})`);
  error.status = status;
  error.body = json;
  throw error;
}

Part of antpay.js, the complete tested file

PHP

// Step 1: create the checkout on your server, store the checkoutId, redirect the customer.
function antpay_create_checkout(string $orderId, int $total): array {
    if (!preg_match(ANTPAY_REFERENCE, $orderId)) throw new InvalidArgumentException('reference: use letters, digits, ".", "_" and "-" only (1 to 120 characters)');
    [$status, $data] = antpay_call('POST', '/api/v1/checkouts', [
        'reference' => $orderId,            // your order id, unique per API key
        'amount' => $total,                 // whole rupees, minimum 300
        'paymentMethod' => 'qr',
        'paymentType' => 'onetime',
        'returnUrl' => 'https://shop.example.com/orders/' . rawurlencode($orderId),
    ]);
    if ($status === 200 && is_array($data) && !empty($data['success'])) {
        return ['checkoutId' => $data['checkoutId'], 'checkoutUrl' => $data['checkoutUrl'], 'expiresAt' => $data['expiresAt']];
    }
    // 4xx: nothing was created (a duplicate reference already exists: look it up).
    // 5xx or no answer: the checkout may exist. Ask by reference before you retry.
    throw new RuntimeException("checkout not created (HTTP $status): " . json_encode($data));
}

Part of antpay.php, the complete tested file

Python

# Step 1: create the checkout on your server, store the checkoutId, redirect the customer.
def create_checkout(order_id, total):
    if not REFERENCE.match(order_id):
        raise ValueError('reference: use letters, digits, ".", "_" and "-" only (1 to 120 characters)')
    status, data = call("POST", "/api/v1/checkouts", {
        "reference": order_id,              # your order id, unique per API key
        "amount": total,                    # whole rupees, minimum 300
        "paymentMethod": "qr",
        "paymentType": "onetime",
        "returnUrl": "https://shop.example.com/orders/" + urllib.parse.quote(order_id, safe=""),
    })
    if status == 200 and data and data.get("success"):
        return {"checkoutId": data["checkoutId"], "checkoutUrl": data["checkoutUrl"], "expiresAt": data["expiresAt"]}
    # 4xx: nothing was created (a duplicate reference already exists: look it up).
    # 5xx or no answer: the checkout may exist. Ask by reference before you retry.
    raise RuntimeError(f"checkout not created (HTTP {status}): {data}")

Part of antpay.py, the complete tested file

The answer

{
  "success": true,
  "checkoutUrl": "https://pay.antpay.network/qr/3f6c1d2e-8a4b-4c1e-9d7a-2b5e6f7a8c90",
  "checkoutId": "3f6c1d2e-8a4b-4c1e-9d7a-2b5e6f7a8c90",
  "expiresAt": "2026-11-01T10:15:00.000Z"
}

The answer has no transaction id. That arrives in the postback as transactionId, and in the status calls as txnId.

Redirect the customer

Use a full-page redirect (HTTP 303) or a link. Do not put the page inside an iframe. Create a new checkout for each order; never reuse a link for another order or customer. You never call the payment page's own addresses.

Two ways the page opens

You send accountNumber + walletNameYou do not send them
The page opens straight on the QR code, for example "Complete your payment using Easypaisa". No wallet choice, no form. The page opens on the form: the customer picks the wallet, enters the mobile number and the account name, then sees the QR.
FieldAllowed values
accountNumberMobile wallet number: 03XXXXXXXXX or +923XXXXXXXXX. Spaces and dashes are ignored.
walletNameeasypaisa, jazzcash, sadapay, nayapay (any letter case)
{
  "reference": "ORD-10021",
  "amount": 1500,
  "paymentMethod": "qr",
  "paymentType": "onetime",
  "returnUrl": "https://shop.example.com/orders/ORD-10021",
  "expiresIn": 15,
  "user": {
    "customerId": "C-778"
  },
  "accountNumber": "03121234567",
  "walletName": "easypaisa"
}

Amounts: whole rupees only

amount must be whole rupees and at least 300. 1500.00 is accepted; 1500.50 returns 400 with DECIMAL_VALUES_NOT_SUPPORTED, and an amount under 300 returns 400 with AMOUNT_NOT_SUPPORTED. Nothing is created in either case.

What the customer sees

ScreenWhenWhat it shows
Enter detailsThe link is open and no QR exists yetThe order amount; the wallet choice (Easypaisa, JazzCash, NayaPay, SadaPay); the wallet's mobile number and account name. Each link allows 5 submissions. Skipped when you send accountNumber + walletName.
Scan and payRight after they submitThe QR, "Pay exactly Rs amount", the wallet to pay with, a 10:00 timer and a button to save the QR as an image.
Payment receivedPaidThe amount, the payment reference and the time in Pakistan time. If the customer watched the payment finish, the page returns them to your returnUrl after about 3 seconds; otherwise it shows a Return button.
Payment not completed / expiredFailed, or not paid in timeThe amount, the reference and the time, and a Return button.
This payment link has expiredThe link expired before the customer submitted detailsA message to go back and start again, and a Return button.

The page carries the AntPay name. It never shows the customer's name back to them, and it shows the mobile number only when you sent accountNumber, masked.

Duplicate references

A second POST /api/v1/checkouts with a reference you already used returns 400, and that answer carries no link. Read the existing payment with GET /api/v1/transactions/{reference} and rebuild the link from its checkoutId.

Do not rely on this check to stop a double create. It compares against payments that already exist. Two creates for the same reference that overlap (the usual cause: your first call was still running when you gave up and sent it again) can both be accepted, and you then have two checkouts for one order. Three habits keep that from hurting you:

When the create call fails or times out

You gotWhat it meansWhat to do
4xxNothing was created (except a duplicate, which already exists).Fix the cause shown in Errors, then send it again.
5xx, a timeout, no answerThe checkout may or may not exist.Ask GET /api/v1/transactions/{reference} first. Found: store its checkoutId and use that checkout. 404 with "error": "Transaction not found": nothing was created, create again with the same reference, and store the new checkoutId. Any other answer (another 404, a 5xx, a timeout): you do not know yet; ask again later, and do not create again until you get one of the first two.