Every endpoint is an HTTPS POST with Content-Type: application/json.
The body is a UTF-8 JSON object holding all parameters (no query string), up to 32 KB.
The body must include merchant_id and sign, and no undocumented fields.
Error messages are in Chinese by default; send Accept-Language: en for English.
Retries and duplicates
The merchant order number prevents duplicates: creating again with the same number and content returns the original order; nothing is collected or paid twice.
The same order number with different content is rejected.
After a timeout or internal error, query the original order first, then retry with the same parameters. Never resubmit under a new order number, or you may create two orders.
A PENDING payout is still processing. Wait for the final result; do not pay again.
Docs / Guides / Signing
Signing
An HMAC-SHA256 signature goes in the sign field of the JSON body; no extra authentication headers.
Steps
Take every body field except sign (including merchant_id).
Sort keys (nested objects too) and serialise as compact JSON without spaces. Keep non-ASCII characters as they are, without \u escapes; decimals such as amounts must be strings.
String to sign = POST + newline + endpoint path + newline + the JSON from step 2, with no trailing newline.
HMAC-SHA256 that string with your API secret and put the 64-character lowercase hex result in sign.
A wrong signature, merchant number or revoked key all return the same signature error, without saying which.
Code
Defines the call() used in each endpoint example
# Requires jq and openssl
API_HOST="https://<api-host>" # API host, see API settings in the merchant portal
API_SECRET="<your API secret>" # API secret; keep it on your server only
API_PATH="/api/v1/payins/query"
BODY='{"merchant_id":"10001","merchant_order_id":"ORDER-10001"}'
# Drop sign, sort keys, serialise as compact JSON
CANONICAL=$(printf '%s' "$BODY" | jq -cS 'del(.sign)')
SIGN=$(printf 'POST\n%s\n%s' "$API_PATH" "$CANONICAL" \
| openssl dgst -sha256 -hmac "$API_SECRET" | awk '{print $NF}')
curl -X POST "$API_HOST$API_PATH" \
-H "Content-Type: application/json" \
-d "$(printf '%s' "$CANONICAL" | jq -c --arg sign "$SIGN" '. + {sign: $sign}')"
import hashlib, hmac, json
from urllib.request import Request, urlopen
API_HOST = "https://<api-host>" # API host, see API settings in the merchant portal
MERCHANT_ID = "10001" # Merchant number
API_SECRET = "<your API secret>" # API secret; keep it on your server only
def canonical(payload):
# Drop sign, sort keys, serialise as compact JSON
unsigned = {k: v for k, v in payload.items() if k != "sign"}
return json.dumps(unsigned, sort_keys=True, ensure_ascii=False,
separators=(",", ":"), allow_nan=False)
def call(path, fields):
payload = {"merchant_id": MERCHANT_ID, **fields}
message = "POST\n" + path + "\n" + canonical(payload)
payload["sign"] = hmac.new(API_SECRET.encode(), message.encode("utf-8"),
hashlib.sha256).hexdigest()
request = Request(API_HOST + path, method="POST",
data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
headers={"Content-Type": "application/json"})
with urlopen(request, timeout=15) as response:
return json.load(response)
print(call("/api/v1/payins/query", {"merchant_order_id": "ORDER-10001"}))
<?php
const API_HOST = 'https://<api-host>'; // API host, see API settings in the merchant portal
const MERCHANT_ID = '10001'; // Merchant number
const API_SECRET = '<your API secret>'; // API secret; keep it on your server only
const JSON_FLAGS = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES;
// Drop sign, sort keys, serialise as compact JSON
// Use new stdClass() for an empty object, not []
function canonical($value): string {
if (is_array($value) && array_is_list($value)) {
return '[' . implode(',', array_map('canonical', $value)) . ']';
}
if (is_array($value) || is_object($value)) {
$fields = (array) $value;
ksort($fields, SORT_STRING);
$parts = [];
foreach ($fields as $key => $item) {
$parts[] = json_encode((string) $key, JSON_FLAGS) . ':' . canonical($item);
}
return '{' . implode(',', $parts) . '}';
}
return json_encode($value, JSON_FLAGS);
}
function call(string $path, array $fields): array {
$payload = ['merchant_id' => MERCHANT_ID] + $fields;
unset($payload['sign']);
$message = "POST\n" . $path . "\n" . canonical($payload);
$payload['sign'] = hash_hmac('sha256', $message, API_SECRET);
$ch = curl_init(API_HOST . $path);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_FLAGS),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
]);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
print_r(call('/api/v1/payins/query', ['merchant_order_id' => 'ORDER-10001']));
// Java 17+, no third-party dependencies
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.*;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class WinniePay {
static final String API_HOST = "https://<api-host>"; // API host, see API settings in the merchant portal
static final String MERCHANT_ID = "10001"; // Merchant number
static final String API_SECRET = "<your API secret>"; // API secret; keep it on your server only
// Drop sign, sort keys, serialise as compact JSON
static String canonical(Object value) {
if (value instanceof Map<?, ?> map) {
StringJoiner out = new StringJoiner(",", "{", "}");
new TreeMap<>(map).forEach((k, v) -> out.add(quote((String) k) + ":" + canonical(v)));
return out.toString();
}
if (value instanceof List<?> list) {
StringJoiner out = new StringJoiner(",", "[", "]");
list.forEach(v -> out.add(canonical(v)));
return out.toString();
}
if (value instanceof String s) return quote(s);
return String.valueOf(value); // null, true/false, whole numbers
}
static String quote(String s) {
StringBuilder b = new StringBuilder("\"");
for (char c : s.toCharArray()) {
switch (c) {
case '"' -> b.append("\\\"");
case '\\' -> b.append("\\\\");
case '\n' -> b.append("\\n");
case '\r' -> b.append("\\r");
case '\t' -> b.append("\\t");
case '\b' -> b.append("\\b");
case '\f' -> b.append("\\f");
default -> b.append(c < 0x20 ? String.format("\\u%04x", (int) c) : String.valueOf(c));
}
}
return b.append('"').toString();
}
static String hmac(String secret, String message) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
return HexFormat.of().formatHex(mac.doFinal(message.getBytes(StandardCharsets.UTF_8)));
}
static String call(String path, Map<String, Object> fields) throws Exception {
Map<String, Object> payload = new TreeMap<>(fields);
payload.put("merchant_id", MERCHANT_ID);
payload.remove("sign");
String body = canonical(payload);
String sign = hmac(API_SECRET, "POST\n" + path + "\n" + body);
String signed = body.substring(0, body.length() - 1) + ",\"sign\":" + quote(sign) + "}";
HttpRequest request = HttpRequest.newBuilder(URI.create(API_HOST + path))
.header("Content-Type", "application/json")
.timeout(Duration.ofSeconds(15))
.POST(HttpRequest.BodyPublishers.ofString(signed, StandardCharsets.UTF_8))
.build();
return HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString()).body();
}
public static void main(String[] args) throws Exception {
System.out.println(call("/api/v1/payins/query", Map.of("merchant_order_id", "ORDER-10001")));
}
}
// Node.js 18+
const crypto = require("crypto");
const API_HOST = "https://<api-host>"; // API host, see API settings in the merchant portal
const MERCHANT_ID = "10001"; // Merchant number
const API_SECRET = "<your API secret>"; // API secret; keep it on your server only
// Drop sign, sort keys, serialise as compact JSON
function canonicalJson(value) {
if (Array.isArray(value)) return "[" + value.map(canonicalJson).join(",") + "]";
if (value !== null && typeof value === "object") {
return "{" + Object.keys(value).sort()
.map((k) => JSON.stringify(k) + ":" + canonicalJson(value[k])).join(",") + "}";
}
return JSON.stringify(value);
}
async function call(path, fields) {
const payload = { merchant_id: MERCHANT_ID, ...fields };
delete payload.sign;
const message = `POST\n${path}\n${canonicalJson(payload)}`;
payload.sign = crypto.createHmac("sha256", API_SECRET).update(message, "utf8").digest("hex");
const response = await fetch(API_HOST + path, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
return response.json();
}
call("/api/v1/payins/query", { merchant_order_id: "ORDER-10001" }).then(console.log);
{
"code": 500,
"message": "This merchant order number was already used with different content",
"data": null
}
Error messages
message
What to do
Authentication failed. Check the merchant number, API secret and signing method
Check the string to sign against Signing; confirm the merchant number and that the key is not revoked.
The request IP is not on your API allowlist
Add your server's outgoing IP to the API allowlist in the merchant portal.
The API key does not have permission for this endpoint
Grant the needed scope to the key, or use a key that has it.
This product is not enabled for your account
Contact us to enable the product.
Your merchant account is not active
Contact us about your account status.
A request field is missing, unexpected or invalid
Check field names, required fields and formats against the endpoint's table.
bank_account is incomplete or invalid: beneficiary name, account number, IFSC, bank name, Indian mobile number and email are required
Complete and check all six bank account fields.
UPI account details are incomplete or invalid
Check that the payee UPI details are complete and valid.
The request body is not valid JSON
Send a UTF-8 JSON object without duplicate keys.
Content-Type must be application/json
Set the header Content-Type: application/json.
The request body exceeds 32 KB
Make the body smaller, for example by trimming extra.
Order not found, or the endpoint path is wrong
Check the merchant order number and the endpoint path.
Only POST requests are accepted
Use POST.
Response format not acceptable; accept application/json
Remove the Accept header or set it to application/json.
This merchant order number was already used with different content
One order number maps to one request; use a new number for a new order.
The request conflicts with the order's current state
Query the order's current status before deciding what to do next.
This action is not allowed in the order's current state
Query the order's current status; a completed order no longer accepts this action.
A UPI is being allocated. Retry with the same merchant order number and parameters
Retry later with the same order number and parameters, or query the order.
Allocating a UPI timed out. Retry with the same merchant order number and parameters
Retry with the same order number and parameters, or query the order.
No channel is available. Please contact us
Try again later; contact us if it persists.
Insufficient available balance for this payout
Check your balance; wait for more pay-ins to be credited, or lower the payout amount.
The recipient is blacklisted and can't receive payouts
The recipient is blacklisted; payouts to this account are refused.
For test orders, the rupee amount must end in 1 (success) or 0 (failure)
During integration testing, end the rupee amount in 1 to test success or 0 to test failure, for example 101.00 or 100.00.
Too many requests. Try again later
Slow down and retry.
Internal error. Query the original order before retrying
Query the original order before retrying; if it persists, contact us with the X-Request-ID.
Pay-in statuses
Status
Meaning
ALLOCATING
Allocating a receiving UPI; query again shortly.
PENDING
Waiting for payment. If the payment is confirmed after the payment deadline, the order still becomes successful and you receive the callback.
SUCCESS
Succeeded: the customer paid the payable amount; credited at the order amount.
SUCCESS_UNDERPAID
Succeeded (underpaid): less than the payable amount arrived; credited as received.
SUCCESS_OVERPAID
Succeeded (overpaid): more than the payable amount arrived; credited as received.
CREATE_TIMEOUT
Timed out allocating payment details; the order cannot be paid.
NO_AVAILABLE_CHANNEL
No channel available; the order cannot be paid.
SYSTEM_ERROR
System error; the order cannot be paid. Please contact support.
CREATE_FAILED
Order creation failed; the order cannot be paid.
The three statuses starting with SUCCESS are final and the money has arrived; credit the customer with paid_amount.
Payout statuses
Status
Meaning
PENDING
Processing. Do not pay again while the result is pending.
SUCCESS
Paid.
FAILED
Failed; the held amount has been returned to your available balance.
REFUND
A successful payout was reversed; the amount and fee have been returned to your available balance.
SUCCESS, FAILED and REFUND are final; a SUCCESS can later become REFUND.
UTR statuses
Status
Meaning
RECEIVED
Payment received.
AVAILABLE_FOR_SUPPLEMENT
Payment received and not linked to an order; you can submit a missed-payment claim.
NOT_FOUND
UTR not found.
PAYMENT_FAILED
The bank transfer failed.
CLAIMED_BY_OTHER_MERCHANT
The UTR has been claimed by another merchant.
INVALID_FORMAT
Invalid UTR format.
ERROR
Unable to check right now; try again later.
Docs / Guides / Callbacks
Callbacks
When an order reaches a final result, we send a JSON POST to the notify_url you gave when creating it.
POSTnotify_url
Content-Type: application/jsonUTF-8INR
When we send it
Pay-ins: sent when the payment succeeds (SUCCESS, SUCCESS_UNDERPAID, SUCCESS_OVERPAID), with paid_amount. Unpaid orders get no callback; use the query endpoint.
Payouts: sent on SUCCESS, FAILED or REFUND.
{ }Callback fields
Body · application/json
Field
Type
Description
merchant_id
string
Your merchant number.
merchant_order_id
string
Your order number.
platform_order_id
string
Platform order number: PI for pay-ins, PO for payouts. Quote it when contacting support.
status
string
Order status. Pay-ins: SUCCESS, SUCCESS_UNDERPAID, SUCCESS_OVERPAID. Payouts: SUCCESS, FAILED, REFUND.
amount
string
Order amount, as submitted at creation.
INR as a string, up to 2 decimals, e.g. "1000.00"
paid_amount
string
Pay-ins only: amount to credit the customer, as in the query endpoint.
INR as a string, up to 2 decimals, e.g. "1000.00"
merchant_fee
string
Fee for this order; "0.00" for a failed payout.
INR as a string, up to 2 decimals, e.g. "1000.00"
message
string
success when paid; the failure reason for a failed payout; refund for a reversal.
completed_at
string | null
When the payment succeeded; null for a failed payout.
ISO 8601 with time zone
utr
string | null
Payouts only: bank transaction reference.
extra
object
Payouts only: the extra you sent at creation, or {}.
test
boolean
Whether this is a test order. Orders created with a test key are true; they only use the test channel and never move real money.
sign
string
Callback signature, computed with your callback secret; see below.
Verify sign with your callback secret: steps 1 and 2 of request signing, but sign only the sorted JSON, without POST or a path.
Record the result once verified. The same status may arrive more than once; deduplicate by platform_order_id and status.
Reply HTTP 200, Content-Type: text/plain, body success in lowercase, with no spaces, newlines or JSON. Any other reply counts as not received.
# Requires jq and openssl
# callback.json holds the callback body you received
CALLBACK_SECRET="<your callback secret>" # Callback secret (not the API secret)
SIGN=$(jq -r '.sign' callback.json)
EXPECTED=$(jq -cjS 'del(.sign)' callback.json \
| openssl dgst -sha256 -hmac "$CALLBACK_SECRET" | awk '{print $NF}')
[ "$SIGN" = "$EXPECTED" ] && echo valid || echo invalid
import hashlib, hmac, json
CALLBACK_SECRET = "<your callback secret>" # Callback secret (not the API secret)
def verify(payload):
unsigned = {k: v for k, v in payload.items() if k != "sign"}
message = json.dumps(unsigned, sort_keys=True, ensure_ascii=False,
separators=(",", ":"), allow_nan=False)
expected = hmac.new(CALLBACK_SECRET.encode(), message.encode("utf-8"),
hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, payload.get("sign", ""))
# Once verified and recorded, reply HTTP 200, text/plain, body success
<?php
const CALLBACK_SECRET = '<your callback secret>'; // Callback secret (not the API secret)
// canonical() as in the signing example
function verify(string $rawBody): bool {
$payload = json_decode($rawBody); // Decode as objects so an empty {} stays an object
$sign = $payload->sign ?? '';
unset($payload->sign);
$expected = hash_hmac('sha256', canonical($payload), CALLBACK_SECRET);
return hash_equals($expected, $sign);
}
// Once verified and recorded, reply HTTP 200, text/plain, body success
// Parsing the callback body needs Jackson (com.fasterxml.jackson.databind)
// canonical(), quote() and hmac() as in the signing example
static final String CALLBACK_SECRET = "<your callback secret>"; // Callback secret (not the API secret)
static boolean verify(String rawBody) throws Exception {
Map<String, Object> payload = new com.fasterxml.jackson.databind.ObjectMapper()
.readValue(rawBody, new com.fasterxml.jackson.core.type.TypeReference<Map<String, Object>>() {});
Object sign = payload.remove("sign");
String expected = hmac(CALLBACK_SECRET, canonical(payload));
return sign instanceof String s && java.security.MessageDigest.isEqual(
expected.getBytes(StandardCharsets.UTF_8), s.getBytes(StandardCharsets.UTF_8));
}
// Once verified and recorded, reply HTTP 200, text/plain, body success
const crypto = require("crypto");
// canonicalJson() as in the signing example
const CALLBACK_SECRET = "<your callback secret>"; // Callback secret (not the API secret)
// rawBody is the callback body you received, as a string
function verify(rawBody) {
const { sign = "", ...unsigned } = JSON.parse(rawBody);
const expected = crypto.createHmac("sha256", CALLBACK_SECRET)
.update(canonicalJson(unsigned), "utf8").digest("hex");
return sign.length === expected.length
&& crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sign));
}
// Once verified and recorded, reply HTTP 200, text/plain, body success
text/plain
success
Retries
If we do not receive a valid reply we retry, up to 7 attempts in total: about 1 minute, 5 minutes, 15 minutes, 1 hour, 6 hours and 24 hours after each failure. Each request waits 5 seconds. After the last attempt you can resend from Callbacks in the merchant portal.
Docs / Pay-ins / Create a pay-in
Create a pay-in
Creates an INR pay-in and returns the checkout page and payment details. Send the customer to checkout_url, or use upi / deeplink in your own page.
A successful response means the order exists. Use the query endpoint or the callback for the payment result.
If the response says a UPI is being allocated, or the request times out, retry with the same merchant order number and the same parameters, or query the order first. Do not resubmit under a new order number.
{ }Request fields
Body · application/json
Field
Type
Required
Description
merchant_id
string
Yes
Your merchant number, shown under API settings in the merchant portal.
Decimal digits as a string, e.g. "10001"
sign
string
Yes
Request signature; see Signing.
64 lowercase hex characters
merchant_order_id
string
Yes
Your order number, unique within your account.
1–128 characters: letters, digits and _ : . - /
amount
string
Yes
Order amount. Limits follow your account configuration.
INR as a string, up to 2 decimals, e.g. "1000.00"
notify_url
string
Yes
Where we send the result notification for this order; see Callbacks.
HTTPS, up to 2048 characters, domain enabled by the platform
return_url
string
Yes
Where the checkout page sends the customer after a successful payment.
{
"code": 500,
"message": "This merchant order number was already used with different content",
"data": null
}
Response fields
data
Field
Type
Description
merchant_order_id
string
Your order number.
platform_order_id
string
Platform order number: PI for pay-ins, PO for payouts. Quote it when contacting support.
amount
string
Order amount, as submitted at creation.
INR as a string, up to 2 decimals, e.g. "1000.00"
paid_amount
string | null
Amount to credit the customer: the order amount if the payable amount was paid exactly, otherwise the amount received; null until paid.
INR as a string, up to 2 decimals, e.g. "1000.00"
status
string
Order status; see Statuses.
merchant_fee
string
Fee charged for this order; "0.00" until the order succeeds.
INR as a string, up to 2 decimals, e.g. "1000.00"
utr
string | null
Bank transaction reference; null until one is confirmed.
created_at
string
When the order was created.
ISO 8601 with time zone
paid_at
string | null
When the payment was confirmed; null if not paid.
ISO 8601 with time zone
callback_at
string | null
When a callback was last delivered successfully; null if none yet.
ISO 8601 with time zone
test
boolean
Whether this is a test order. Orders created with a test key are true; they only use the test channel and never move real money.
Docs / Pay-ins / Submit the customer's UTR
Submit the customer's UTR
If your own payment page collects the customer's UTR, submit it to help us confirm the payment faster. Use the query endpoint or the callback for the payment result.
{
"code": 500,
"message": "This merchant order number was already used with different content",
"data": null
}
Response fields
data
Field
Type
Description
submitted
boolean
true when the UTR has been saved.
Docs / Pay-ins / Look up a UTR
Look up a UTR
Shows whether a UTR has been received and whether it can be used for a missed-payment claim. Order details are returned only if the UTR belongs to one of your orders.
{
"code": 500,
"message": "This merchant order number was already used with different content",
"data": null
}
Response fields
data
Field
Type
Description
utr
string
The UTR you looked up.
utr_status
string
UTR status; see Statuses.
utr_amount
string | null
Amount confirmed for this UTR; null if unknown.
INR as a string, up to 2 decimals, e.g. "1000.00"
received
integer
1 if received; 0 if not confirmed yet.
supplemented
integer
1 if the payment was credited through a missed-payment claim.
merchant_order_id
string | null
Related merchant order number; null if not one of your orders.
platform_order_id
string | null
Related platform order number; null if not one of your orders.
status
string | null
Status of the related order; null if not one of your orders.
amount
string | null
Amount of the related order; null if not one of your orders.
INR as a string, up to 2 decimals, e.g. "1000.00"
Docs / Pay-ins / Claim a missed payment
Claim a missed payment
If the customer paid but the order did not succeed, submit the original merchant order number and the UTR. Once we verify the payment, the order is updated and a callback is sent.
{
"code": 500,
"message": "This merchant order number was already used with different content",
"data": null
}
Response fields
data
Field
Type
Description
merchant_order_id
string
Your order number.
platform_order_id
string
Platform order number: PI for pay-ins, PO for payouts. Quote it when contacting support.
status
string
Current status of the order: PENDING, SUCCESS, SUCCESS_UNDERPAID or SUCCESS_OVERPAID.
utr
string
The UTR you submitted.
actual_amount
string | null
Verified amount received; null until verified.
INR as a string, up to 2 decimals, e.g. "1000.00"
Docs / Pay-ins / List receiving UPI addresses
List receiving UPI addresses
Returns the UPI addresses that may currently receive your pay-ins, so you can recognise where customers paid. Each order's actual UPI is the one returned when the order is created.
Your merchant number, shown under API settings in the merchant portal.
Decimal digits as a string, e.g. "10001"
sign
string
Yes
Request signature; see Signing.
64 lowercase hex characters
Request example
{
"merchant_id": "10001",
"sign": "<64 hex>"
}
# See Signing for how to compute sign
curl --request POST 'https://<api-host>/api/v1/upis' \
--header 'Content-Type: application/json' \
--data-raw '{
"merchant_id": "10001",
"sign": "<64 hex>"
}'
# call() see Signing
result = call("/api/v1/upis", {})
// call() see Signing
$result = call('/api/v1/upis', []);
// call() see Signing
String result = call("/api/v1/upis", Map.of());
// call() see Signing
const result = await call("/api/v1/upis", {});
{
"code": 500,
"message": "This merchant order number was already used with different content",
"data": null
}
Response fields
data
Field
Type
Description
upi
string
The UPI address you looked up.
status
string | null
ACTIVE in use; INACTIVE retired; FROZEN frozen; CONFLICT ownership under review; null if not on record.
exists
integer
1 if on record; 0 if not.
Docs / Payouts / Create a payout
Create a payout
Pays out INR to an Indian bank account. The amount plus fee is held from your available balance at creation, deducted on success and released on failure.
A successful response means the payout was accepted. Use the query endpoint or the callback for the result.
If the request times out or fails, query the original merchant order number first, then retry with the same parameters if needed. Resubmitting the same order number with the same content never pays twice.
{ }Request fields
Body · application/json
Field
Type
Required
Description
merchant_id
string
Yes
Your merchant number, shown under API settings in the merchant portal.
Decimal digits as a string, e.g. "10001"
sign
string
Yes
Request signature; see Signing.
64 lowercase hex characters
merchant_order_id
string
Yes
Your order number, unique within your account.
1–128 characters: letters, digits and _ : . - /
amount
string
Yes
Payout amount. Limits follow your account configuration.
INR as a string, up to 2 decimals, e.g. "1000.00"
bank_account
object
Yes
Beneficiary bank account.
bank_account.beneficiary_name
string
Yes
Beneficiary name.
1–100 characters: letters, digits, spaces and . ' & , / ( ) -, with at least one letter
bank_account.account_number
string
Yes
Bank account number.
6–34 digits, not all zeros
bank_account.ifsc
string
Yes
IFSC code.
11 characters, e.g. HDFC0001234
bank_account.bank_name
string
Yes
Bank name.
1–100 characters: letters, digits, spaces and . ' & , / ( ) -, with at least one letter
bank_account.phone
string
Yes
Beneficiary mobile number.
10-digit Indian mobile starting 6–9, optional +91
bank_account.email
string
Yes
Beneficiary email.
Up to 200 characters
notify_url
string
Yes
Where we send the result notification for this order; see Callbacks.
HTTPS, up to 2048 characters, domain enabled by the platform
upi
string
No
Beneficiary UPI address as additional information; the payout still goes to the bank account.
extra
object
No
Your own data, returned unchanged in the callback.
{
"code": 500,
"message": "This merchant order number was already used with different content",
"data": null
}
Response fields
data
Field
Type
Description
count
integer
Total number of matching orders.
next
integer | null
Next page number; null on the last page.
previous
integer | null
Previous page number; null on the first page.
results
array
Orders, each with the same fields as Query a payout.
Docs / Payouts / Get a payout receipt
Get a payout receipt
Creates a web receipt for a successful payout that you can share with the beneficiary. It shows the beneficiary name, full account number and UTR. The link is valid for 24 hours; do not publish it.
Your merchant number, shown under API settings in the merchant portal.
Decimal digits as a string, e.g. "10001"
sign
string
Yes
Request signature; see Signing.
64 lowercase hex characters
Request example
{
"merchant_id": "10001",
"sign": "<64 hex>"
}
# See Signing for how to compute sign
curl --request POST 'https://<api-host>/api/v1/balances' \
--header 'Content-Type: application/json' \
--data-raw '{
"merchant_id": "10001",
"sign": "<64 hex>"
}'
# call() see Signing
result = call("/api/v1/balances", {})
// call() see Signing
$result = call('/api/v1/balances', []);
// call() see Signing
String result = call("/api/v1/balances", Map.of());
// call() see Signing
const result = await call("/api/v1/balances", {});
{
"code": 500,
"message": "This merchant order number was already used with different content",
"data": null
}
Response fields
data
Field
Type
Description
available_balance
string
Available balance, usable for payouts and settlements. It is your balance minus payouts and settlements in progress and frozen pay-ins. It is negative (for example -380.00) while frozen pay-ins exceed your balance; payouts and settlements are not possible then.
INR as a string, up to 2 decimals, e.g. "1000.00"
frozen_balance
string
Frozen amount: amounts held by payouts and settlement requests in progress and by frozen pay-ins. Not usable yet.
INR as a string, up to 2 decimals, e.g. "1000.00"
order_frozen_balance
string
Part of the frozen amount held by frozen pay-ins, for example after a payer complaint at the upstream. It returns to the available balance when unfrozen.