MerchantCenter Merchant Open API Integration Guide
Every <...> value in this guide is a placeholder that must be replaced.
Before integration, obtain the platform Base URL and merchant number, generate an API Key in the merchant console, and register the fixed public egress IP used for API calls.
Credential preparation
- Open APIs cannot authenticate until an API Key has been generated.
- Generate and view the API Key after completing verification in the merchant console.
- The console normally displays a masked value; the full value is shown temporarily after verification.
- Resetting the API Key invalidates the previous key immediately.
- Console verification data is never sent with API requests and is not part of order signatures.
1. API List
| Operation | Method | Path |
|---|---|---|
| Query merchant balance | GET | /open/mc/merchantBalance/query |
| Create pay-in order | POST | /open/mc/merchantPayin/create |
| Query pay-in order | GET | /open/mc/merchantPayin/query |
| Submit pay-in UTR | POST | /open/mc/merchantPayin/submitUtr |
| Pay-in makeup | POST | /open/mc/merchantPayin/makeup |
| Check whether a UPI exists | GET | /open/mc/merchantPayin/upiQuery |
| Check whether a UTR exists | GET | /open/mc/merchantPayin/utrQuery |
| Create payout order | POST | /open/mc/merchantPayout/create |
| Query payout order | GET | /open/mc/merchantPayout/query |
2. Common Requests and Responses
2.1 Request headers
Every merchant request and platform asynchronous notification uses these five authentication headers:
| Header | Required | Description |
|---|---|---|
x-merchant-no | Yes | Merchant number assigned by the platform |
x-timestamp | Yes | Unix timestamp in seconds; the default maximum clock difference is 300 seconds |
x-nonce | Yes | Unique random string for this request, 16–64 characters; letters, digits, _, and - only |
x-sign-version | Yes | Must be v2 |
x-sign | Yes | HMAC-SHA256 signature as 64 lowercase hexadecimal characters |
POST requests also require:
Content-Type: application/json
- Generate a new timestamp, nonce, and signature for every HTTP request.
- Never reuse a nonce for create, query, repeated query, or retry requests.
- Recalculate the signature whenever the nonce, HTTP method, URL path, or payload changes.
- Keep the caller clock within 300 seconds of platform time.
2.2 Source IP policy
All open APIs require a registered fixed public egress IP. Requests from any other source IP are rejected. If a proxy or NAT is used, register the actual public egress IP and update the allowlist before it changes.
2.3 Field rules
| Rule | Description |
|---|---|
| Amount | Send a string greater than zero with two decimal places, for example "500.00" |
| Optional fields | Omit fields without a value when possible; request and notification bodies may contain null |
| GET parameters | Use URL query parameters and send each key only once |
| Order lookup | Send exactly one of orderNo or merchantOrderNo |
| Unknown fields | Fields not declared for the API are rejected |
2.4 Common response
{
"code": 1000,
"message": "success",
"data": {}
}On failure, data is normally null. If order creation fails before business acceptance, data still includes merchantOrderNo and status: FAILED. Only an order that was created successfully and returned an orderNo can include that platform order number in later queries or notifications.
3. Signing and Verification
For POST requests and notifications, hash the JSON body. For GET requests, hash the URL query object. In every case, recursively remove empty values, sort object keys lexicographically, preserve array order, and serialize stable JSON. Values 0, false, and "0" participate; null, undefined, empty or whitespace-only strings, and non-finite numbers do not. For duplicate query keys, only the first value is used.
The signature path is the URL pathname only. For example, the signature path for https://api.example.com/open/mc/merchantPayin/query?orderNo=1 is /open/mc/merchantPayin/query. The Base URL, host, and raw query string are not placed directly in the canonical text.
3.1 Eight-line canonical text
payloadSha256 = hex_lower(SHA256(payloadBytes))
MCV2-HMAC-SHA256
v2
<UPPERCASE_HTTP_METHOD>
<URL_PATHNAME>
<MERCHANT_NO>
<TIMESTAMP_UNIX_SECONDS>
<NONCE>
<PAYLOAD_SHA256>
Join the eight lines with one \n and no trailing newline, then calculate:
sign = hex_lower(HMAC_SHA256(apiKey, canonicalText))The same canonical form and five authentication headers apply to merchant requests and platform notifications. A nonce must be valid and must never be reused.
4. Query Merchant Balance
GET /open/mc/merchantBalance/query
GET /open/mc/merchantBalance/query?currency=INRcurrency is optional. If omitted, all existing currency accounts are returned. If a specified currency has no account record, the platform returns "0.00" for balance, availableBalance, and frozenBalance.
{
"merchantNo": "<MERCHANT_NO>",
"balances": [{
"currency": "INR",
"balance": "1000.00",
"availableBalance": "900.00",
"frozenBalance": "100.00"
}]
}
5. Create Pay-in Order
POST /open/mc/merchantPayin/create
Content-Type: application/json
| Field | Type | Required | Description |
|---|---|---|---|
merchantOrderNo | string | Yes | Unique merchant order number, 1–128 characters |
amount | string | Yes | Greater than zero, up to two decimal places |
currency | string | Yes | Currently INR |
payinInterfaceStyle | string | No | standard or extended; default extended |
notifyUrl | string | No | Terminal-status notification URL |
returnUrl | string | No | HTTPS return URL, up to 2048 characters |
attach | string | No | Pass-through value, up to 512 characters |
returnUrl must be a complete https:// URL without embedded credentials; query parameters and SPA hash routes such as #/payment/result are supported. The open cashier redirects once when it observes a transition to successful payment. Reopening an already successful order requires the user to select “RETURN TO MERCHANT”. A successful UTR submission only means the UTR was accepted and does not trigger an immediate redirect. An idempotency conflict never updates the original return URL.
A successful response returns orderNo, merchantOrderNo, amount, currency, status (CREATED), payUrl, payinInterfaceStyle, merchantFee, utr, payee_upi, and cash_params. With standard, use payUrl and cash_params is null. With extended, non-null cash_params may contain payee_upi, remark, and cash_params.links values links.qr, links.paytm, links.phonepe_ios, and links.phonepe_android.
- Use returned payment URLs without rebuilding or modifying them.
- An expired cashier page keeps order details visible but disables payment; a payer who already transferred may still submit the UTR there.
- Top-level
amountis the order amount. A payment URL may carry a slightly different actual payment amount. - Display only non-empty payment methods and links in a custom cashier.
merchantOrderNois unique per merchant; duplicates return40102.
6. Query Pay-in Order
GET /open/mc/merchantPayin/query?orderNo=<ORDER_NO>
GET /open/mc/merchantPayin/query?merchantOrderNo=<MERCHANT_ORDER_NO>Send exactly one lookup field. The response extends the create response with UTC ISO 8601 createTime and updateTime. Status values are PENDING, PAYING, SUCCESS, REJECTED, FAILED, EXPIRED, and CLOSED.
7. Submit Pay-in UTR
POST /open/mc/merchantPayin/submitUtr
Content-Type: application/json
{
"orderNo": "<ORDER_NO>",
"utr": "UTR123456789"
}orderNo must be the platform pay-in order number; merchant order numbers are not accepted. utr must contain 12–32 letters or digits and is normalized to uppercase. Spaces, hyphens, and other symbols return a parameter error.
- Success means the platform accepted the UTR, not that payment succeeded.
- Submitting the same UTR again for the same order is idempotent.
- A UTR attached to another order follows UTR occupancy rules and is not idempotent.
- A different UTR for the same order returns a parameter error.
- Use order queries or verified notifications for the final result.
Successful response data:
{
"orderNo": "<ORDER_NO>",
"merchantOrderNo": "MP-EXAMPLE-001",
"utr": "UTR123456789",
"status": "PAYING",
"verifyStatus": "SUBMITTED"
}
8. Pay-in Makeup
POST /open/mc/merchantPayin/makeup
Content-Type: application/json
{
"orderNo": "<ORDER_NO>",
"utr": "UTR123456789"
}The field formats match section 7. An order that already has a UTR cannot be submitted again.
SUCCESS: makeup completed.PROCESSING: processing; query the order. The platform does not submit it again automatically.FAILED: definitive failure; the submitted UTR is removed.EXCEPTION: result is uncertain; the UTR is retained. Query the order or contact operations. The platform does not submit it again automatically.- A successful makeup updates a non-manual-terminal order to
SUCCESSand sends the normal terminal notification. For an order already set to a manual terminal state, the order state remains unchanged and its current terminal information is notified.
9. Check UPI Existence
GET /open/mc/merchantPayin/upiQuery?upi=receiver%40upiupi is required, for example receiver@upi. A successful response returns status=YES if the UPI exists or status=NO otherwise. By default, each merchant may query UPI once every 10 seconds; excessive frequency returns 40309.
10. Check UTR Existence
GET /open/mc/merchantPayin/utrQuery?utr=UTR123456789utr must be one 12–32 character alphanumeric value and is normalized to uppercase. Invalid format returns 40100 with data=null.
status | result | Meaning | Makeup allowed |
|---|---|---|---|
YES | AVAILABLE_FOR_MAKEUP | Paid and not linked to an order | Yes |
YES | CURRENT_MERCHANT | Linked to the current merchant; orderNo is returned | No |
YES | OTHER_MERCHANT | Linked to another merchant; no order number is returned | No |
YES | UTR_ORDER_ABNORMAL | UTR order is abnormal | No |
NO | NOT_FOUND | Not found or not paid | No |
result=AVAILABLE_FOR_MAKEUP is the only result that allows makeup. Other results are result=CURRENT_MERCHANT, result=OTHER_MERCHANT, and result=UTR_ORDER_ABNORMAL. 40302 means the service is busy. By default, each merchant may query UTR once every 10 seconds; excessive frequency returns 40309.
11. Create Payout Order
POST /open/mc/merchantPayout/create
Content-Type: application/json
| Field | Type | Required | Description |
|---|---|---|---|
merchantOrderNo | string | Yes | Unique merchant order number |
amount | string | Yes | Amount string with two decimal places |
currency | string | Yes | Currently INR |
accountName | string | Yes | Beneficiary name |
accountNo | string | Yes | Beneficiary bank account number |
payMethod | string | Yes | Only BANK is currently available |
platformName | string | No | Bank name; recommended. Missing, null, blank, "null", and "undefined" are treated as absent |
ifscOrBankCode | string | Required for BANK | IFSC/bank code |
notifyUrl | string | No | Terminal-status notification URL |
attach | string | No | Pass-through value, up to 512 characters |
UPI payout is not currently available. A request with
payMethod=UPIis rejected before order creation or fund freezing; no platform order is created and no external payout request is initiated.
A successful response returns orderNo, merchantOrderNo, amount, merchantFee, currency, status: CREATED, freezeAmount, and utr. CREATED only means the platform accepted and completed the current creation step; it does not mean the beneficiary received funds. Duplicates return 40102, and every retry requires a new x-nonce.
12. Query Payout Order
GET /open/mc/merchantPayout/query?orderNo=<ORDER_NO>
GET /open/mc/merchantPayout/query?merchantOrderNo=<MERCHANT_ORDER_NO>Send exactly one lookup field. Status values are PENDING, PAYING, SUCCESS, REJECTED, and FAILED. For a regular payout, utr is one value. When a split parent succeeds, it contains valid UTR values from successful child orders joined with -. If no valid UTR exists, it is null; store it as a variable-length string. After a successful payout is manually reversed by the platform, the merchant query returns FAILED.
Example UTR value for a successful split parent: UTR123456789-UTR987654321.
13. Asynchronous Notifications
When order creation includes notifyUrl and the order becomes eligible for notification, the platform sends HTTP POST JSON with the same five headers and signature rules from sections 2 and 3. Every attempt uses a new timestamp, nonce, and signature.
13.1 Pay-in notification
Fields: orderNo, merchantOrderNo, status (SUCCESS, REJECTED, or EXPIRED), amount, currency, utr, and attach when provided during creation.
13.2 Payout notification
Fields: orderNo, merchantOrderNo, status (SUCCESS, REJECTED, or FAILED), amount, merchantFee, currency, utr, and attach when provided during creation. A manual reversal is notified as FAILED. Split-parent UTR formatting follows section 12. Notifications contain only the merchant business fields declared here.
13.3 Merchant ACK
A notification is acknowledged by either an HTTP 2xx response whose trimmed text equals success case-insensitively, or an HTTP 2xx JSON response whose code is the number 1000 or string "1000".
14. Common Business Codes
| Code | Description |
|---|---|
1000 | Success |
40000 | Unclassified request failure |
40001 | Missing or invalid authentication header |
40002 | Invalid or expired timestamp |
40003 | Invalid signature |
40004 | Merchant not found, disabled, or API disabled |
40007 | Invalid or reused nonce |
40100 | Invalid request parameters |
40101 | Invalid amount |
40102 | Duplicate merchant order number or idempotency conflict |
40201 | Order not found for the current merchant |
40301 | Balance or fund operation failed |
40302 | Service temporarily unavailable |
40304 | Operation not supported for the current order or payment method; also returned while payout by UPI is unavailable |
40305 | UTR was not accepted |
40306 | Makeup failed; an existing SUCCESS state is not overwritten |
40307 | Makeup could not be completed; query the current order state and contact operations |
40308 | UTR submission could not be completed; query the current order state before retrying |
40309 | UPI or UTR queries are too frequent |
Branch on code; do not parse message for program logic.
15. Node.js Signing Example
import crypto from 'node:crypto';
function clean(value) {
if (value === undefined || value === null ||
(typeof value === 'string' && value.trim() === '') ||
(typeof value === 'number' && !Number.isFinite(value))) return undefined;
if (Array.isArray(value)) return value.map(clean).filter(item => item !== undefined);
if (typeof value === 'object') return Object.keys(value).sort().reduce((result, key) => {
const item = clean(value[key]);
if (item !== undefined) result[key] = item;
return result;
}, {});
return value;
}
export function sign({ method, path, merchantNo, timestamp, nonce, payload, apiKey }) {
const payloadBytes = JSON.stringify(clean(payload) ?? {});
const payloadSha256 = crypto.createHash('sha256').update(payloadBytes, 'utf8').digest('hex');
const canonicalText = ['MCV2-HMAC-SHA256', 'v2', method.toUpperCase(), path,
merchantNo, String(timestamp), nonce, payloadSha256].join('\n');
return crypto.createHmac('sha256', apiKey).update(canonicalText, 'utf8').digest('hex');
}For GET, pass the query object as payload. For POST and notifications, pass the parsed JSON object. Clean and recursively sort before signing, and send x-sign-version: v2.
16. Pre-launch Checklist
- Methods and paths match section 1.
- The production Base URL uses HTTPS.
- Caller time stays within 300 seconds of platform time.
- Every fixed public egress IP is registered.
- Every request and notification attempt uses a new timestamp, nonce, and signature and sends
x-sign-version: v2. - Method, pathname, merchant number, timestamp, nonce, and payload SHA-256 are all signed.
- GET, POST, and notifications hash stable JSON after removing empty values and recursively sorting keys.
- Amounts use two-decimal strings.
- Duplicate merchant order numbers return
40102without original order data. - The notification endpoint returns an ACK from section 13.3.
- Merchant implementations depend only on fields and states declared in this guide.