Introduction
Getting Started With Your API
This guide helps you get started with the Payment System API integration.
API Base URL
We provide separate base URLs for two environments:
-
UAT (Testing): https://uat.makemypayment.in
Use this environment to test your API calls, integrations, and development workflow. -
Production: https://makemypayment.in
Use this environment to interact with live data in production.
Recommended Steps:
- Start with the UAT base URL to explore the API and test requests.
- Once your integration works as expected, switch to the Production base URL.
Key Features
- ✅ Initiate Payout – Initiate a payout for your account.
- 🔎 Check Payout Status – Track the status of any payout in real time.
- 💰 Retrieve Account Balances – Get your current available balance instantly.
🔐 Authentication
All endpoints require authentication using two credentials:
X-API-KEY– Your unique API key.X-API-SECRET– Used with AES encryption for secure request signing.
Every payout and balance request is validated in this order:
- API key and secret match a merchant account.
- Merchant status must be
active. kyc_statusandvan_statusmust beverified.- Merchant callback/IP activation must be
verified, with webhook URL/secret set. - Caller IP must match the merchant's whitelisted IP.
If any validation fails, the API returns an error response in the same response envelope format.
🔒 Encryption
All sensitive request data is encrypted using AES-256-CBC.
- Algorithm: AES-256-CBC
- Default IV:
0g7H#8X2mTqjvLwR
Ensure your client application implements the same encryption and decryption logic before sending the payload. The API expects the payload to be base64 encoded after encryption.
Example workflow:
- Prepare your request data as JSON.
- Encrypt using AES-256-CBC with your
api_secretand the default IV. - Base64 encode the encrypted string.
- Send it in the request body or as defined by the endpoint.
💡 Tip for Developers
Use the provided code examples (on the right in desktop view or below on mobile) to quickly test requests in your preferred programming language.
Following the examples ensures correct headers, encryption, and payload structure.
Webhook Integration Guide
When a payout status changes (e.g., processing → success or failed), the system automatically sends a POST request to your registered Webhook URL.
You must implement this endpoint on your server to receive real-time payout status updates.
How It Works
- You register a Webhook URL and a Webhook Secret during IP/callback activation.
- When a payout status changes, the system encrypts the payload and POSTs it to your URL.
- Your endpoint must respond with HTTP 200 to acknowledge receipt.
- If your server does not return
200, the system will retry up to 5 times at the following intervals:- 1 minute, 5 minutes, 15 minutes (×3 remaining retries)
Incoming Request Format
Your webhook URL will receive a POST request with:
| Header | Value |
|---|---|
Content-Type |
application/json |
x-signature |
Your registered Webhook Secret |
The raw request body is an AES-256-CBC encrypted, Base64-encoded string — not a JSON object.
Decrypting the Payload
To read the webhook data, decrypt the raw body using:
- Algorithm: AES-256-CBC
- Key: Your
api_secret(the same secret used to sign API requests) - IV:
0g7H#8X2mTqjvLwR(fixed) - Step 1: Base64-decode the raw body
- Step 2: Decrypt using AES-256-CBC
After decryption, you get a JSON string with the following fields:
{
"transaction_id": "TXN987654321",
"beneficiary_account_holder": "John Doe",
"beneficiary_account_number": "1234567890",
"beneficiary_bank_name": "HDFC Bank",
"beneficiary_ifsc_code": "HDFC0001234",
"amount": 500,
"status": "success",
"utr": "UTR1234567890",
"remarks": "Payment for services",
"narration": "Salary"
}
Payload Field Reference
| Field | Type | Description |
|---|---|---|
transaction_id |
string | Your unique payout transaction ID |
beneficiary_account_holder |
string | Beneficiary's full name |
beneficiary_account_number |
string | Beneficiary's bank account number |
beneficiary_bank_name |
string | Beneficiary's bank name |
beneficiary_ifsc_code |
string | Beneficiary's bank IFSC code |
amount |
number | Payout amount in INR |
status |
string | Current payout status: pending, processing, success, failed |
utr |
string|null | UTR number (available on success) |
remarks |
string|null | Remarks for the payout |
narration |
string|null | Narration for the transaction |
Verifying the Request (Recommended)
Before processing, verify that the request is genuinely from our system by checking the x-signature header against your registered Webhook Secret.
Implementation Examples
PHP
<?php
// Webhook endpoint: POST https://your-domain.com/webhook/payout
$api_secret = 'YOUR_API_SECRET'; // same as your api_secret
$webhook_secret = 'YOUR_WEBHOOK_SECRET';
$iv = '0g7H#8X2mTqjvLwR';
// 1. Verify signature
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
if ($signature !== $webhook_secret) {
http_response_code(401);
exit('Unauthorized');
}
// 2. Read and decrypt body
$raw_body = file_get_contents('php://input');
$decrypted = openssl_decrypt(
base64_decode($raw_body),
'AES-256-CBC',
$api_secret,
OPENSSL_RAW_DATA,
$iv
);
$payload = json_decode($decrypted, true);
// 3. Process the payload
$transaction_id = $payload['transaction_id'];
$status = $payload['status'];
$utr = $payload['utr'] ?? null;
// ... update your database, notify user, etc.
// 4. MUST return HTTP 200
http_response_code(200);
echo json_encode(['received' => true]);
Node.js (Express)
const express = require('express');
const crypto = require('crypto');
const app = express();
const API_SECRET = 'YOUR_API_SECRET';
const WEBHOOK_SECRET = 'YOUR_WEBHOOK_SECRET';
const IV = '0g7H#8X2mTqjvLwR';
app.post('/webhook/payout', express.text({ type: '*/*' }), (req, res) => {
// 1. Verify signature
if (req.headers['x-signature'] !== WEBHOOK_SECRET) {
return res.status(401).send('Unauthorized');
}
// 2. Decrypt body
const decipher = crypto.createDecipheriv(
'aes-256-cbc',
Buffer.from(API_SECRET),
Buffer.from(IV)
);
let decrypted = decipher.update(Buffer.from(req.body, 'base64'));
decrypted = Buffer.concat([decrypted, decipher.final()]);
const payload = JSON.parse(decrypted.toString());
// 3. Process
console.log('Payout update:', payload.transaction_id, payload.status);
// 4. MUST return HTTP 200
res.status(200).json({ received: true });
});
⚠️ Important: Your webhook endpoint must return HTTP 200 within the timeout window.
Any other status code (including 201, 204, 4xx, 5xx) will be treated as a failure and trigger a retry.
Authenticating requests
To authenticate requests, include a X-API-KEY header with the value "{YOUR_API_KEY}".
All authenticated endpoints are marked with a requires authentication badge in the documentation below.
Also send X-API-SECRET in headers for decryption/encryption and merchant validation.
Account
Get account balance.
Retrieve the current balance of the authenticated merchant's virtual account.
Note:
- Response will be AES encrypted using the merchant's API secret.
- API key and secret must be sent in request headers for authentication.
- Merchant pre-validation is enforced before request processing.
Example request:
curl --request GET \
--get "https://uat.makemypayment.in/api/v1/balance" \
--header "X-API-KEY: string required Your API key for authentication." \
--header "X-API-SECRET: string required Your API secret for encryption and authentication." \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://uat.makemypayment.in/api/v1/balance"
);
const headers = {
"X-API-KEY": "string required Your API key for authentication.",
"X-API-SECRET": "string required Your API secret for encryption and authentication.",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://uat.makemypayment.in/api/v1/balance';
$response = $client->get(
$url,
[
'headers' => [
'X-API-KEY' => 'string required Your API key for authentication.',
'X-API-SECRET' => 'string required Your API secret for encryption and authentication.',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://uat.makemypayment.in/api/v1/balance'
headers = {
'X-API-KEY': 'string required Your API key for authentication.',
'X-API-SECRET': 'string required Your API secret for encryption and authentication.',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers)
response.json()Example response (200):
{
"status": true,
"message": "Account balance retrieved successfully",
"data": {
"balance": 15000.5
},
"errors": null
}
Example response (400):
{
"status": false,
"message": "API key and secret are required",
"data": null,
"errors": []
}
Example response (401):
{
"status": false,
"message": "Invalid API key or secret",
"data": null,
"errors": []
}
Example response (403):
{
"status": false,
"message": "Merchant virtual account is not verified.",
"data": null,
"errors": []
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Payouts
Initiate a payout.
Start a new payout transaction for the authenticated merchant.
Note: The request body must be:
- AES encrypted (AES/CBC/PKCS5Padding)
- Base64 encoded after encryption
- Raw JSON payload before encryption should include: account_holder, account_number, bank_name, ifsc_code, amount, email, mobile
- Merchant pre-validation is enforced before request processing: API key/secret, merchant status=active, kyc_status=verified, van_status=verified, verified IP/webhook configuration.
Outbound Webhook (Status Notification):
When the payout status changes (e.g., processing, success, failed), the system will
automatically POST an AES-256-CBC encrypted, Base64-encoded payload to your registered webhook URL.
The raw JSON payload before encryption contains:
{
"transaction_id": "TXN987654321",
"beneficiary_account_holder": "John Doe",
"beneficiary_account_number": "1234567890",
"beneficiary_bank_name": "HDFC Bank",
"beneficiary_ifsc_code": "HDFC0001234",
"amount": 500,
"status": "success",
"utr": "UTR1234567890",
"remarks": "Payment for services",
"narration": "Salary"
}
The encrypted string is sent as the raw POST body with headers:
Content-Type: application/jsonx-signature: <your_webhook_secret>
Your webhook endpoint MUST respond with HTTP 200 to acknowledge receipt. Any other response code is treated as failure and the system will retry up to 5 times (at 1 min, 5 min, and 15 min intervals).
To decrypt the webhook payload, use AES-256-CBC with your api_secret as the key
and the fixed IV 0g7H#8X2mTqjvLwR, then Base64-decode the body first.
Example request:
curl --request POST \
"https://uat.makemypayment.in/api/v1/payouts/initiate" \
--header "X-API-KEY: string required Your API key for authentication." \
--header "X-API-SECRET: string required Your API secret for decryption and authentication." \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"account_holder\": \"John Doe\",
\"account_number\": \"1234567890\",
\"ifsc_code\": \"HDFC0001234\",
\"bank_name\": \"HDFC Bank\",
\"branch_name\": \"architecto\",
\"branch_code\": \"architecto\",
\"mobile\": \"9876543210\",
\"city\": \"architecto\",
\"beneficiary_address\": \"architecto\",
\"amount\": \"500\",
\"mode\": \"neft\",
\"purpose\": \"architecto\",
\"email\": \"user@example.com\",
\"state\": \"bngzmi\",
\"pincode\": \"569775\",
\"remarks\": \"architecto\",
\"narration\": \"architecto\"
}"
const url = new URL(
"https://uat.makemypayment.in/api/v1/payouts/initiate"
);
const headers = {
"X-API-KEY": "string required Your API key for authentication.",
"X-API-SECRET": "string required Your API secret for decryption and authentication.",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"account_holder": "John Doe",
"account_number": "1234567890",
"ifsc_code": "HDFC0001234",
"bank_name": "HDFC Bank",
"branch_name": "architecto",
"branch_code": "architecto",
"mobile": "9876543210",
"city": "architecto",
"beneficiary_address": "architecto",
"amount": "500",
"mode": "neft",
"purpose": "architecto",
"email": "user@example.com",
"state": "bngzmi",
"pincode": "569775",
"remarks": "architecto",
"narration": "architecto"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://uat.makemypayment.in/api/v1/payouts/initiate';
$response = $client->post(
$url,
[
'headers' => [
'X-API-KEY' => 'string required Your API key for authentication.',
'X-API-SECRET' => 'string required Your API secret for decryption and authentication.',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'json' => [
'account_holder' => 'John Doe',
'account_number' => '1234567890',
'ifsc_code' => 'HDFC0001234',
'bank_name' => 'HDFC Bank',
'branch_name' => 'architecto',
'branch_code' => 'architecto',
'mobile' => '9876543210',
'city' => 'architecto',
'beneficiary_address' => 'architecto',
'amount' => '500',
'mode' => 'neft',
'purpose' => 'architecto',
'email' => 'user@example.com',
'state' => 'bngzmi',
'pincode' => '569775',
'remarks' => 'architecto',
'narration' => 'architecto',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://uat.makemypayment.in/api/v1/payouts/initiate'
payload = {
"account_holder": "John Doe",
"account_number": "1234567890",
"ifsc_code": "HDFC0001234",
"bank_name": "HDFC Bank",
"branch_name": "architecto",
"branch_code": "architecto",
"mobile": "9876543210",
"city": "architecto",
"beneficiary_address": "architecto",
"amount": "500",
"mode": "neft",
"purpose": "architecto",
"email": "user@example.com",
"state": "bngzmi",
"pincode": "569775",
"remarks": "architecto",
"narration": "architecto"
}
headers = {
'X-API-KEY': 'string required Your API key for authentication.',
'X-API-SECRET': 'string required Your API secret for decryption and authentication.',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('POST', url, headers=headers, json=payload)
response.json()Example response (200):
{
"status": true,
"message": "Payout initiated successfully",
"data": "TXN987654321",
"errors": null
}
Example response (400):
{
"status": false,
"message": "API key and secret are required",
"data": null,
"errors": []
}
Example response (400):
{
"status": false,
"message": "Request body is not valid base64 encoded",
"data": null,
"errors": []
}
Example response (400):
{
"status": false,
"message": "Failed to decrypt data",
"data": null,
"errors": []
}
Example response (400):
{
"status": false,
"message": "Invalid JSON format after decryption",
"data": null,
"errors": []
}
Example response (401):
{
"status": false,
"message": "Invalid API key or secret",
"data": null,
"errors": []
}
Example response (403):
{
"status": false,
"message": "Merchant KYC is not verified.",
"data": null,
"errors": []
}
Example response (422):
{
"status": false,
"message": "Validation failed",
"data": null,
"errors": {
"account_number": [
"The account number field is required."
],
"amount": [
"The amount must be at least merchant min transfer limit."
]
}
}
Example response (500):
{
"status": false,
"message": "Failed to initiate payout. Please try again.",
"data": null,
"errors": []
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Initiate bulk payouts.
Accepts an array of payout objects, persists each as 'initiated', and dispatches async jobs to process them via SprintNXT.
Note: AES encrypted request / response same as initiate payout. Merchant pre-validation is enforced before request processing.
Example request:
curl --request POST \
"https://uat.makemypayment.in/api/v1/payouts/bulk" \
--header "X-API-KEY: string required" \
--header "X-API-SECRET: string required" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"payouts\": [
\"architecto\"
]
}"
const url = new URL(
"https://uat.makemypayment.in/api/v1/payouts/bulk"
);
const headers = {
"X-API-KEY": "string required",
"X-API-SECRET": "string required",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"payouts": [
"architecto"
]
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://uat.makemypayment.in/api/v1/payouts/bulk';
$response = $client->post(
$url,
[
'headers' => [
'X-API-KEY' => 'string required',
'X-API-SECRET' => 'string required',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'json' => [
'payouts' => [
'architecto',
],
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://uat.makemypayment.in/api/v1/payouts/bulk'
payload = {
"payouts": [
"architecto"
]
}
headers = {
'X-API-KEY': 'string required',
'X-API-SECRET': 'string required',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('POST', url, headers=headers, json=payload)
response.json()Example response (200):
{
"status": true,
"message": "Bulk payout initiated",
"data": {
"transaction_ids": [
"ABCDEF1234567890"
]
},
"errors": null
}
Example response (403):
{
"status": false,
"message": "Merchant API activation is not verified.",
"data": null,
"errors": []
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Check payout status.
Get the status of a specific payout transaction for the authenticated merchant.
Note:
- Response will be AES encrypted using the merchant's API secret.
- API key and secret must be sent in request headers for authentication.
- Merchant pre-validation is enforced before request processing.
Example request:
curl --request GET \
--get "https://uat.makemypayment.in/api/v1/payouts/status" \
--header "X-API-KEY: string required Your API key for authentication." \
--header "X-API-SECRET: string required Your API secret for encryption and authentication." \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"transaction_id\": \"b\"
}"
const url = new URL(
"https://uat.makemypayment.in/api/v1/payouts/status"
);
const headers = {
"X-API-KEY": "string required Your API key for authentication.",
"X-API-SECRET": "string required Your API secret for encryption and authentication.",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"transaction_id": "b"
};
fetch(url, {
method: "GET",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://uat.makemypayment.in/api/v1/payouts/status';
$response = $client->get(
$url,
[
'headers' => [
'X-API-KEY' => 'string required Your API key for authentication.',
'X-API-SECRET' => 'string required Your API secret for encryption and authentication.',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'json' => [
'transaction_id' => 'b',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://uat.makemypayment.in/api/v1/payouts/status'
payload = {
"transaction_id": "b"
}
headers = {
'X-API-KEY': 'string required Your API key for authentication.',
'X-API-SECRET': 'string required Your API secret for encryption and authentication.',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers, json=payload)
response.json()Example response (200):
{
"status": true,
"message": "Payout status retrieved successfully",
"data": {
"transaction_id": "TXN123456",
"beneficiary": {
"account_holder": "John Doe",
"account_number": "1234567890",
"bank_name": "HDFC Bank",
"ifsc_code": "HDFC0001234"
},
"amount": "1000.00",
"status": "success",
"utr": "UTR123456789",
"remarks": "Payment processed successfully"
},
"errors": null
}
Example response (400):
{
"status": false,
"message": "API key and secret are required",
"data": null,
"errors": []
}
Example response (401):
{
"status": false,
"message": "Invalid API key or secret",
"data": null,
"errors": []
}
Example response (403):
{
"status": false,
"message": "Request IP is not whitelisted for this merchant.",
"data": null,
"errors": []
}
Example response (404):
{
"status": false,
"message": "Payout not found",
"data": null,
"errors": []
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.