ChineseEnglish

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

  1. Open APIs cannot authenticate until an API Key has been generated.
  2. Generate and view the API Key after completing verification in the merchant console.
  3. The console normally displays a masked value; the full value is shown temporarily after verification.
  4. Resetting the API Key invalidates the previous key immediately.
  5. Console verification data is never sent with API requests and is not part of order signatures.

1. API List

OperationMethodPath
Query merchant balanceGET/open/mc/merchantBalance/query
Create pay-in orderPOST/open/mc/merchantPayin/create
Query pay-in orderGET/open/mc/merchantPayin/query
Submit pay-in UTRPOST/open/mc/merchantPayin/submitUtr
Pay-in makeupPOST/open/mc/merchantPayin/makeup
Check whether a UPI existsGET/open/mc/merchantPayin/upiQuery
Check whether a UTR existsGET/open/mc/merchantPayin/utrQuery
Create payout orderPOST/open/mc/merchantPayout/create
Query payout orderGET/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:

HeaderRequiredDescription
x-merchant-noYesMerchant number assigned by the platform
x-timestampYesUnix timestamp in seconds; the default maximum clock difference is 300 seconds
x-nonceYesUnique random string for this request, 16–64 characters; letters, digits, _, and - only
x-sign-versionYesMust be v2
x-signYesHMAC-SHA256 signature as 64 lowercase hexadecimal characters

POST requests also require:

Content-Type: application/json

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

RuleDescription
AmountSend a string greater than zero with two decimal places, for example "500.00"
Optional fieldsOmit fields without a value when possible; request and notification bodies may contain null
GET parametersUse URL query parameters and send each key only once
Order lookupSend exactly one of orderNo or merchantOrderNo
Unknown fieldsFields 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=INR

currency 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
FieldTypeRequiredDescription
merchantOrderNostringYesUnique merchant order number, 1–128 characters
amountstringYesGreater than zero, up to two decimal places
currencystringYesCurrently INR
payinInterfaceStylestringNostandard or extended; default extended
notifyUrlstringNoTerminal-status notification URL
returnUrlstringNoHTTPS return URL, up to 2048 characters
attachstringNoPass-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.

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.

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.

9. Check UPI Existence

GET /open/mc/merchantPayin/upiQuery?upi=receiver%40upi

upi 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=UTR123456789

utr must be one 12–32 character alphanumeric value and is normalized to uppercase. Invalid format returns 40100 with data=null.

statusresultMeaningMakeup allowed
YESAVAILABLE_FOR_MAKEUPPaid and not linked to an orderYes
YESCURRENT_MERCHANTLinked to the current merchant; orderNo is returnedNo
YESOTHER_MERCHANTLinked to another merchant; no order number is returnedNo
YESUTR_ORDER_ABNORMALUTR order is abnormalNo
NONOT_FOUNDNot found or not paidNo

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
FieldTypeRequiredDescription
merchantOrderNostringYesUnique merchant order number
amountstringYesAmount string with two decimal places
currencystringYesCurrently INR
accountNamestringYesBeneficiary name
accountNostringYesBeneficiary bank account number
payMethodstringYesOnly BANK is currently available
platformNamestringNoBank name; recommended. Missing, null, blank, "null", and "undefined" are treated as absent
ifscOrBankCodestringRequired for BANKIFSC/bank code
notifyUrlstringNoTerminal-status notification URL
attachstringNoPass-through value, up to 512 characters

UPI payout is not currently available. A request with payMethod=UPI is 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

CodeDescription
1000Success
40000Unclassified request failure
40001Missing or invalid authentication header
40002Invalid or expired timestamp
40003Invalid signature
40004Merchant not found, disabled, or API disabled
40007Invalid or reused nonce
40100Invalid request parameters
40101Invalid amount
40102Duplicate merchant order number or idempotency conflict
40201Order not found for the current merchant
40301Balance or fund operation failed
40302Service temporarily unavailable
40304Operation not supported for the current order or payment method; also returned while payout by UPI is unavailable
40305UTR was not accepted
40306Makeup failed; an existing SUCCESS state is not overwritten
40307Makeup could not be completed; query the current order state and contact operations
40308UTR submission could not be completed; query the current order state before retrying
40309UPI 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