Overview & Conventions
This guide is for merchants integrating with XPay. It covers Collection (pay-in), Payout (pay-out) and the various query APIs. Please read the conventions below first.
| Item | Description |
| Gateway | https://api.xpay24.net |
| Merchant ID / Secret | Issued by XPay on account setup. merchantId (numeric) + secretKey (signing key). |
| Method | All endpoints are POST, Content-Type: application/json;charset=UTF-8 |
| Amount unit | Paise (1/100 rupee), integer. e.g. INR 100.00 → send 10000. |
| timestamp | Millisecond epoch (long). Accepted within ±5 minutes, otherwise rejected. |
| Encoding | UTF-8 |
A successful response (code=200) does NOT mean the payment succeeded. For create endpoints, 200 only means the request was accepted. The final payment/payout result is determined by the async callback or the query API.
Signature
Every request requires a sign field (MD5, 32-char lowercase hex). The formula depends on whether the endpoint carries an order id:
① Endpoints with an order id (create, complete, order query)
SIGN sign = md5( orderId + timestamp + secretKey )
② Endpoints without an order id (UPI / UTR / balance query)
SIGN sign = md5( merchantId + timestamp + secretKey )
Concatenate the values in order into a single string with no separators, then MD5. The timestamp used for signing must be identical to the one in the request body.
Example
Given orderId=ORDER20260720001, timestamp=1785000000000, secretKey=your_secret_key:
signStr = "ORDER202607200011785000000000your_secret_key"
sign = md5(signStr) // 32-char lowercase
Verifying a callback signature: the sign field itself is excluded; recompute the signature from the remaining parameters with the same formula and compare against sign.
Create Order (Collection)
Create a pay-in order. On success a payment link paymentUrl is returned to direct the customer to pay.
POST /v1/orders/create
Request
| Field | Type | Required | Description |
| merchantId | long | Yes | Merchant ID |
| amount | long | Yes | Amount in paise, integer |
| currency | string | No | Default INR |
| orderId | string | Yes | Merchant order id (≤50, unique per merchant) |
| notifyUrl | string | Yes | URL to receive the async callback for this order (≤255) |
| idempotencyKey | string | No | Idempotency key; the same key returns the same order |
| timestamp | long | Yes | Millisecond epoch |
| sign | string | Yes | md5(orderId + timestamp + secretKey) |
Request example
POST /v1/orders/create
{
"merchantId": 101,
"amount": 10000,
"currency": "INR",
"orderId": "ORDER20260720001",
"notifyUrl": "https://your-site.com/xpay/collect/notify",
"timestamp": 1785000000000,
"sign": "e10adc3949ba59abbe56e057f20f883e"
}
Response
| Field | Type | Description |
| code | int | 200 = accepted |
| msg | string | Message / error code |
| payOrderId | string | XPay platform order number |
| orderNo | string | Platform order number (same as payOrderId) |
| orderId | string | Merchant order id (echoed back) |
| amount | long | Amount in paise |
| currency | string | Currency |
| status | string | Order status; on creation INITIATED (awaiting payment) |
| paymentUrl | string | Payment link (pass-through): returned by the upstream provider and passed through by XPay as-is; the merchant uses it directly to open the cashier. Returned only on success. |
Response example
{
"code": 200,
"msg": "Success",
"payOrderId": "X_PAYIN2607201230482KQ",
"orderNo": "X_PAYIN2607201230482KQ",
"orderId": "ORDER20260720001",
"amount": 10000,
"currency": "INR",
"status": "INITIATED",
"paymentUrl": "https://"
}
Complete with UTR
If the customer has paid but the order is still not marked successful, the merchant may submit the bank UTR to complete it manually. XPay verifies the UTR with the upstream; if valid, the order is marked successful.
POST /v1/orders/complete-with-utr
Request
| Field | Type | Required | Description |
| merchantId | long | Yes | Merchant ID |
| orderId | string | Yes | Merchant order id |
| utr | string | Yes | Bank UTR (≤50) |
| remark | string | No | Remark |
| timestamp | long | Yes | Millisecond epoch |
| sign | string | Yes | md5(orderId + timestamp + secretKey) |
Response example
{
"code": 200,
"msg": "Success",
"orderNo": "X_PAYIN2607201230482KQ",
"orderId": "ORDER20260720001",
"amount": 10000,
"currency": "INR",
"status": "SUCCESS"
}
If the UTR is already used by another order, or upstream verification fails, the endpoint returns an error (code≠200) and the order status is unchanged.
Query Order (Collection)
Query a collection order's current status by merchant order id.
POST /v1/query/collect/status
Request
| Field | Type | Required | Description |
| merchantId | long | Yes | Merchant ID |
| orderId | string | Yes | Merchant order id |
| timestamp | long | Yes | Millisecond epoch |
| sign | string | Yes | md5(orderId + timestamp + secretKey) |
Response
| Field | Type | Description |
| code | int | 200 = success, 404 = not found |
| msg | string | Message |
| status | string | INITIATED awaitingSUCCESSREFUNDED |
| amount | long | Amount in paise |
| orderNo | string | Platform order number |
| utr | string | Bank UTR (when successful and recorded) |
Response example
{
"code": 200,
"msg": "Success",
"status": "SUCCESS",
"amount": 10000,
"orderNo": "X_PAYIN2607201230482KQ",
"utr": "123456789012"
}
Async Callback (Collection)
When a collection order succeeds or fails, XPay actively sends a POST (JSON) to the notifyUrl you provided at order creation. Pushed only on success/failure.
Callback parameters
| Field | Type | Description |
| amount | long | Amount in paise |
| merchantId | long | Merchant ID |
| orderId | string | Merchant order id |
| payOrderId | string | Platform order number |
| status | int | 1 = paid2 = failed |
| utr | string | Bank UTR (may be present on success, else null) |
| timestamp | long | Millisecond epoch |
| sign | string | md5(orderId + timestamp + secretKey), for verification |
Callback example
POST https://your-site.com/xpay/collect/notify
Content-Type: application/json
X-Signature: <HmacSHA256(body, secretKey) hex, extra integrity header>
{
"amount": 10000,
"merchantId": 101,
"orderId": "ORDER20260720001",
"payOrderId": "X_PAYIN2607201230482KQ",
"status": 1,
"utr": "123456789012",
"timestamp": 1785000000000,
"sign": "a1b2c3d4e5f6..."
}
Verify: recompute md5(orderId + timestamp + secretKey) from the body (excluding sign) and compare. The X-Signature header is an HmacSHA256 (hex) of the whole body, an optional extra integrity check.
You must return the string SUCCESS or OK within 5 seconds (HTTP 2xx). Otherwise it is treated as failed and XPay retries roughly every 5 minutes, up to 5 times. Always verify the signature first, then process, and handle it idempotently (an order may be pushed more than once).
Create Payout
Initiate a payout (pay-out) order. Once accepted it moves to processing; the final result is determined by the async callback or query.
POST /v1/payouts/create
IP whitelist: the payout endpoint requires the source IP to be whitelisted for the merchant, otherwise it returns IP error. Send your payout server IP to XPay in advance to be whitelisted.
Request
| Field | Type | Required | Description |
| merchantId | long | Yes | Merchant ID |
| amount | long | Yes | Amount in paise |
| currency | string | No | Default INR |
| orderId | string | Yes | Merchant order id (≤50) |
| notifyUrl | string | Yes | Payout result callback URL (≤255) |
| payoutType | string | Yes | Payout type: IMPS or UPI |
| accountHolder | string | Yes | Payee name (letters and spaces only, 3–50) |
| accountNumber | string | Yes | Payee account (5–35); for UPI, put the payee UPI address here |
| ifsc | string | Cond. | Required for IMPS; optional for UPI |
| idempotencyKey | string | No | Idempotency key |
| timestamp | long | Yes | Millisecond epoch |
| sign | string | Yes | md5(orderId + timestamp + secretKey) |
Request example
POST /v1/payouts/create
{
"merchantId": 101,
"amount": 50000,
"currency": "INR",
"orderId": "PAYOUT20260720001",
"notifyUrl": "https://your-site.com/xpay/payout/notify",
"payoutType": "IMPS",
"accountHolder": "RAHUL KUMAR",
"accountNumber": "1234567890",
"ifsc": "SBIN0001234",
"timestamp": 1785000000000,
"sign": "5f4dcc3b5aa765d61d8327deb882cf99"
}
Response example
{
"code": 200,
"msg": "Success",
"payoutNo": "X_PAYOUT2607201230477QN",
"orderId": "PAYOUT20260720001",
"amount": 50000,
"currency": "INR",
"status": "PROCESSING"
}
Once accepted the order enters PROCESSING; if the upstream rejects it synchronously it goes to manual handling with the rejection reason. Final result via async callback.
Query Payout
POST /v1/query/payout/status
Request
| Field | Type | Required | Description |
| merchantId | long | Yes | Merchant ID |
| orderId | string | Yes | Merchant order id |
| timestamp | long | Yes | Millisecond epoch |
| sign | string | Yes | md5(orderId + timestamp + secretKey) |
Response
| Field | Type | Description |
| code | int | 200 = success, 404 = not found |
| msg | string | Message |
| status | string | INITIATEDPROCESSINGSUCCESSFAILED |
| amount | long | Amount in paise |
| orderNo | string | Platform payout number |
| utr | string | Bank UTR (on success) |
Async Callback (Payout)
When a payout succeeds or fails, XPay actively sends a POST (JSON) to the notifyUrl.
Callback parameters
| Field | Type | Description |
| amount | long | Amount in paise |
| merchantId | long | Merchant ID |
| orderId | string | Merchant order id |
| payOrderId | string | Platform payout number |
| status | int | 1 = success2 = failed |
| statusDesc | string | Status description: success / failed |
| utr | string | Bank UTR (on success, else null) |
| timestamp | long | Millisecond epoch |
| sign | string | md5(orderId + timestamp + secretKey) |
Callback example
{
"amount": 50000,
"merchantId": 101,
"orderId": "PAYOUT20260720001",
"payOrderId": "X_PAYOUT2607201230477QN",
"status": 1,
"statusDesc": "success",
"utr": "998877665544",
"timestamp": 1785000000000,
"sign": "a1b2c3d4e5f6..."
}
Same as the collection callback: return SUCCESS or OK within 5 seconds, otherwise retried roughly every 5 minutes, up to 5 times. Verify first, then process idempotently. On payout failure the amount is refunded to the merchant balance.
UPI Lookup
Check whether a UPI address belongs to the platform's channels.
POST /v1/query/upi/query
Request
| Field | Type | Required | Description |
| merchantId | long | Yes | Merchant ID |
| upi | string | Yes | UPI address to look up |
| timestamp | long | Yes | Millisecond epoch |
| sign | string | Yes | md5(merchantId + timestamp + secretKey) |
Response
| Field | Type | Description |
| belongsToUs | bool | true = belongs to the platform, false = not |
| message | string | Message |
Response example
{
"belongsToUs": true,
"message": "UPI belongs to current platform"
}
UTR Lookup
Look up by bank UTR whether the funds have arrived and whether they are matched to an order.
POST /v1/query/utr
Request
| Field | Type | Required | Description |
| merchantId | long | Yes | Merchant ID |
| utr | string | Yes | Bank UTR |
| timestamp | long | Yes | Millisecond epoch |
| sign | string | Yes | md5(merchantId + timestamp + secretKey) |
Response
| Field | Type | Description |
| received | bool | Whether the funds have arrived |
| matched | bool | Whether it is matched to an order |
| matchedOrderId | string | Matched merchant order id (when matched=true) |
| amount | long | Amount in paise (if any) |
| message | string | Message. If not yet received, the UTR is auto-enqueued for polling. |
Balance
POST /v1/query/balance
Equivalent endpoint: POST /v1/balance/query
Request
| Field | Type | Required | Description |
| merchantId | long | Yes | Merchant ID |
| timestamp | long | Yes | Millisecond epoch |
| sign | string | Yes | md5(merchantId + timestamp + secretKey) |
Response example
{
"balance": 1234500
}
| Field | Type | Description |
| balance | long | Available balance in paise |
Error Codes
Failures are returned via code / msg. Common errors:
| msg | Meaning |
| Signature verification failed | Invalid signature |
| Timestamp expired | Timestamp outside ±5 minutes |
| Invalid merchantId | Merchant does not exist |
| Merchant is currently disabled | Merchant disabled |
| IP not whitelisted | Payout source IP not whitelisted |
| Amount out of range | Amount outside limits |
| Duplicate orderId | Duplicate order id |
| Insufficient balance | Insufficient merchant balance (payout) |
| Order not found | Order does not exist |
Integration Steps
Collection
1. Call /v1/orders/create to create the order;
2. Open the returned paymentUrl (passed through from upstream) to let the customer pay;
3. Receive the async callback at notifyUrl, verify the signature, process by status, and return SUCCESS;
4. Optionally use /v1/query/collect/status to poll as a fallback.
Payout
1. Provide your payout server IP to XPay for whitelisting;
2. Call /v1/payouts/create to initiate the payout;
3. Receive the async callback, verify and process;
4. Optionally use /v1/query/payout/status.
Remember: amounts are in paise; use the current millisecond timestamp on each request; always verify callback signatures before processing and be idempotent; the payment result is always determined by the async callback / query API.
XPay Merchant Integration Guide · v1 · Contact XPay technical support for questions