XoomPe Client Payments API
Client endpoints for authentication, wallet balance, payout creation, and transaction tracking.
Base URL
Authorization: Bearer <accessToken>.{{base_url}}, {{access_token}}, {{refresh_token}}, {{txnId}}.Authentication
Generate access tokens using ClientId and ClientSecret, then call payout endpoints with Bearer JWT.
Authorization Header
Authorization: Bearer {{access_token}}
Generate Access Token
Generate tokens using ClientId + ClientSecret (recommended) or Email + Password.
POSTRequest
curl --location '{{base_url}}/token' \
--header 'Content-Type: application/json' \
--data '{
"clientId": "{{client_id}}",
"clientSecret": "{{client_secret}}"
}'
const response = await fetch('{{base_url}}/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
clientId: '{{client_id}}',
clientSecret: '{{client_secret}}'
})
});
const data = await response.json();
import requests
response = requests.post('{{base_url}}/token', json={
'clientId': '{{client_id}}',
'clientSecret': '{{client_secret}}'
})
data = response.json()
$ch = curl_init('{{base_url}}/token');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'clientId' => '{{client_id}}',
'clientSecret' => '{{client_secret}}'
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
Response
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "def50200...",
"expiresIn": 600,
"tokenType": "Bearer"
}
Refresh Token
Use refresh token to get a new access token.
POSTRequest
curl --location '{{base_url}}/refresh' \
--header 'Content-Type: application/json' \
--data '{
"refreshToken": "{{refresh_token}}"
}'
const response = await fetch('{{base_url}}/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refreshToken: '{{refresh_token}}' })
});
const data = await response.json();
import requests
response = requests.post('{{base_url}}/refresh', json={'refreshToken': '{{refresh_token}}'})
data = response.json()
$ch = curl_init('{{base_url}}/refresh');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(['refreshToken' => '{{refresh_token}}']));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
Response
{
"accessToken": "...new...",
"refreshToken": "...new...",
"expiresIn": 600,
"tokenType": "Bearer"
}
Get Wallet Balance
Retrieve current available wallet balance.
GETRequest
curl --location '{{base_url}}/Payout/getWalletBalance' \
--header 'Authorization: Bearer {{access_token}}'
const response = await fetch('{{base_url}}/Payout/getWalletBalance', {
headers: { 'Authorization': 'Bearer {{access_token}}' }
});
const data = await response.json();
import requests
response = requests.get('{{base_url}}/Payout/getWalletBalance',
headers={'Authorization': 'Bearer {{access_token}}'}
)
data = response.json()
$ch = curl_init('{{base_url}}/Payout/getWalletBalance');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bearer {{access_token}}']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
Response
{
"statusCode": 200,
"status": true,
"message": "Balance fetched successfully",
"availableBalance": 12345.67
}
Initiate Payout
Create a payout transaction from client wallet to beneficiary bank account.
POSTRequest Body
| Field | Type | Required | Description |
|---|---|---|---|
| beneficiaryName | string | Yes | Only letters and spaces; 3–200 chars |
| beneficiaryAccountNumber | string | Yes | 8–20 chars |
| ifsc | string | Yes | ^[A-Z]{4}0[A-Z0-9]{6}$ |
| amount | number | Yes | 5000–10000000 |
| clientTransactionRefNo | string | No | Optional idempotency reference |
Request
curl --location '{{base_url}}/Payout/CreateTransaction' \
--header 'Authorization: Bearer {{access_token}}' \
--header 'Content-Type: application/json' \
--data '{
"beneficiaryName": "John Doe",
"beneficiaryAccountNumber": "123456789012",
"ifsc": "ABCD0123456",
"amount": 5000,
"clientTransactionRefNo": "INV-1001"
}'
const response = await fetch('{{base_url}}/Payout/CreateTransaction', {
method: 'POST',
headers: {
'Authorization': 'Bearer {{access_token}}',
'Content-Type': 'application/json'
},
body: JSON.stringify({
beneficiaryName: 'John Doe',
beneficiaryAccountNumber: '123456789012',
ifsc: 'ABCD0123456',
amount: 5000,
clientTransactionRefNo: 'INV-1001'
})
});
const data = await response.json();
import requests
response = requests.post(
'{{base_url}}/Payout/CreateTransaction',
headers={'Authorization': 'Bearer {{access_token}}'},
json={
'beneficiaryName': 'John Doe',
'beneficiaryAccountNumber': '123456789012',
'ifsc': 'ABCD0123456',
'amount': 5000,
'clientTransactionRefNo': 'INV-1001'
}
)
data = response.json()
$ch = curl_init('{{base_url}}/Payout/CreateTransaction');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'beneficiaryName' => 'John Doe',
'beneficiaryAccountNumber' => '123456789012',
'ifsc' => 'ABCD0123456',
'amount' => 5000,
'clientTransactionRefNo' => 'INV-1001'
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer {{access_token}}',
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
Response (success)
{
"statusCode": 201,
"responseCode": 1,
"status": true,
"message": "Transaction created successfully",
"transactionNumber": "TX26010300001",
"transaction": {
"transactionNumber": "TX26010300001",
"clientTransactionRefNo": "INV-1001",
"beneficiaryName": "John Doe",
"beneficiaryAccountNumber": "123456789012",
"ifsc": "ABCD0123456",
"amount": 5000,
"status": "Pending",
"utr": null,
"remarks": null,
"createdAt": "2026-01-09T17:21:00",
"updatedAt": null
}
}
Response (idempotent duplicate reference)
{
"statusCode": 200,
"responseCode": 1,
"status": true,
"message": "Transaction already exists (idempotent)",
"transactionNumber": "TX26010300001",
"transaction": {
"transactionNumber": "TX26010300001",
"clientTransactionRefNo": "INV-1001",
"beneficiaryName": "John Doe",
"beneficiaryAccountNumber": "123456789012",
"ifsc": "ABCD0123456",
"amount": 5000,
"status": "Pending"
}
}
Create Bulk Payouts
Submit multiple payout transactions in a single API call (1–500 items).
POSTRequest
curl --location '{{base_url}}/Payout/CreateBulkTransactions' \
--header 'Authorization: Bearer {{access_token}}' \
--header 'Content-Type: application/json' \
--data '{
"transactions": [
{
"beneficiaryName": "A Person",
"beneficiaryAccountNumber": "1234567890",
"ifsc": "ABCD0123456",
"amount": 5000,
"clientTransactionRefNo": "BULK-1"
},
{
"beneficiaryName": "B Person",
"beneficiaryAccountNumber": "9876543210",
"ifsc": "WXYZ0123456",
"amount": 7500,
"clientTransactionRefNo": "BULK-2"
}
]
}'
const response = await fetch('{{base_url}}/Payout/CreateBulkTransactions', {
method: 'POST',
headers: {
'Authorization': 'Bearer {{access_token}}',
'Content-Type': 'application/json'
},
body: JSON.stringify({
transactions: [
{ beneficiaryName:'A Person', beneficiaryAccountNumber:'1234567890', ifsc:'ABCD0123456', amount:5000, clientTransactionRefNo:'BULK-1' },
{ beneficiaryName:'B Person', beneficiaryAccountNumber:'9876543210', ifsc:'WXYZ0123456', amount:7500, clientTransactionRefNo:'BULK-2' }
]
})
});
const data = await response.json();
import requests
response = requests.post(
'{{base_url}}/Payout/CreateBulkTransactions',
headers={'Authorization': 'Bearer {{access_token}}'},
json={
'transactions': [
{'beneficiaryName':'A Person','beneficiaryAccountNumber':'1234567890','ifsc':'ABCD0123456','amount':5000,'clientTransactionRefNo':'BULK-1'},
{'beneficiaryName':'B Person','beneficiaryAccountNumber':'9876543210','ifsc':'WXYZ0123456','amount':7500,'clientTransactionRefNo':'BULK-2'}
]
}
)
data = response.json()
$ch = curl_init('{{base_url}}/Payout/CreateBulkTransactions');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'transactions' => [
['beneficiaryName'=>'A Person','beneficiaryAccountNumber'=>'1234567890','ifsc'=>'ABCD0123456','amount'=>5000,'clientTransactionRefNo'=>'BULK-1'],
['beneficiaryName'=>'B Person','beneficiaryAccountNumber'=>'9876543210','ifsc'=>'WXYZ0123456','amount'=>7500,'clientTransactionRefNo'=>'BULK-2']
]
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer {{access_token}}',
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
Response
{
"statusCode": 200,
"status": true,
"message": "Success: 2, Failed: 0",
"successCount": 2,
"failedCount": 0,
"totalProcessed": 2,
"successTransactions": [
{ "transactionNumber": "TX26010300001", "clientTransactionRefNo": "BULK-1", "beneficiaryName": "A Person", "amount": 5000 }
],
"failedTransactions": []
}
Get Transactions (Client Filters)
Retrieve your transactions with pagination, search, and a wide set of column-level filters. All parameters are optional — combine any number of them to narrow down results.
GETQuery Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number for pagination |
pageSize | integer | 50 | Number of records per page |
status | string | — | Filter by transaction status. Values: Pending, Success, Failed, Cancelled |
search | string | — | Global keyword search across transaction number, beneficiary name, account number, client ref no, and UTR |
transactionNumber | string | — | Filter by transaction number (partial match) |
beneficiaryName | string | — | Filter by beneficiary name (partial match) |
accountNumber | string | — | Filter by beneficiary account number (partial match) |
ifsc | string | — | Filter by IFSC code (partial match) |
utr | string | — | Filter by UTR number (partial match) |
remarks | string | — | Filter by remarks (partial match) |
batchStatus | string | — | Filter by batch assignment. Values: batched, unbatched |
amount | decimal | — | Exact amount match. Takes priority over minAmount/maxAmount |
minAmount | decimal | — | Minimum transaction amount (used only if amount is not set) |
maxAmount | decimal | — | Maximum transaction amount (used only if amount is not set) |
date | string | — | Exact date filter — returns all transactions from that calendar day. Format: YYYY-MM-DD. Takes priority over date range. |
startDate | string | — | Start of date range. Format: YYYY-MM-DD |
endDate | string | — | End of date range (inclusive). Format: YYYY-MM-DD |
amount is provided, minAmount and maxAmount are ignored. When date is provided, it takes precedence over startDate/endDate.
Request
curl --location '{{base_url}}/Payout/GetClientTransactionsWithFilters?page=1&pageSize=50&status=Success&startDate=2026-01-01&endDate=2026-01-31' \
--header 'Authorization: Bearer {{access_token}}'
const url = new URL('{{base_url}}/Payout/GetClientTransactionsWithFilters');
url.searchParams.set('page', '1');
url.searchParams.set('pageSize', '50');
url.searchParams.set('status', 'Success');
url.searchParams.set('startDate', '2026-01-01');
url.searchParams.set('endDate', '2026-01-31');
// Optional: add more filters as needed
// url.searchParams.set('beneficiaryName', 'John');
// url.searchParams.set('minAmount', '5000');
// url.searchParams.set('maxAmount', '100000');
// url.searchParams.set('utr', 'UTR123');
// url.searchParams.set('batchStatus', 'unbatched');
const response = await fetch(url, {
headers: { 'Authorization': 'Bearer {{access_token}}' }
});
const data = await response.json();
import requests
response = requests.get(
'{{base_url}}/Payout/GetClientTransactionsWithFilters',
headers={'Authorization': 'Bearer {{access_token}}'},
params={
'page': 1,
'pageSize': 50,
'status': 'Success',
'startDate': '2026-01-01',
'endDate': '2026-01-31',
# Optional filters:
# 'beneficiaryName': 'John',
# 'minAmount': 5000,
# 'maxAmount': 100000,
# 'utr': 'UTR123',
# 'batchStatus': 'unbatched',
}
)
data = response.json()
$params = http_build_query([
'page' => 1,
'pageSize' => 50,
'status' => 'Success',
'startDate' => '2026-01-01',
'endDate' => '2026-01-31',
// Optional filters:
// 'beneficiaryName' => 'John',
// 'minAmount' => 5000,
// 'maxAmount' => 100000,
// 'utr' => 'UTR123',
// 'batchStatus' => 'unbatched',
]);
$ch = curl_init("{{base_url}}/Payout/GetClientTransactionsWithFilters?{$params}");
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bearer {{access_token}}']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
Response
{
"status": true,
"message": "Transactions fetched successfully",
"data": [
{
"transactionNumber": "TX26010300001",
"clientTransactionRefNo": "INV-1001",
"beneficiaryName": "John Doe",
"beneficiaryAccountNumber": "123456789012",
"ifsc": "ABCD0123456",
"amount": 5000,
"status": "Success",
"utr": "UTR123456789",
"remarks": null,
"createdAt": "2026-01-09T17:21:00",
"updatedAt": "2026-01-09T17:25:00"
}
],
"pagination": {
"currentPage": 1,
"pageSize": 50,
"totalCount": 1,
"totalPages": 1,
"hasNextPage": false,
"hasPreviousPage": false
},
"totalUnbatchedCount": 1
}
Get Transaction by ID
Retrieve complete details using transaction number (txnId).
GETRequest
curl --location '{{base_url}}/Payout/GetTransactionByTxnId/TX26010300001' \
--header 'Authorization: Bearer {{access_token}}'
const response = await fetch('{{base_url}}/Payout/GetTransactionByTxnId/TX26010300001', {
headers: { 'Authorization': 'Bearer {{access_token}}' }
});
const data = await response.json();
import requests
response = requests.get(
'{{base_url}}/Payout/GetTransactionByTxnId/TX26010300001',
headers={'Authorization': 'Bearer {{access_token}}'}
)
data = response.json()
$ch = curl_init('{{base_url}}/Payout/GetTransactionByTxnId/TX26010300001');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bearer {{access_token}}']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
Response
{
"status": true,
"message": "Transaction fetched successfully",
"transaction": {
"transactionNumber": "TX26010300001",
"clientTransactionRefNo": "INV-1001",
"beneficiaryName": "John Doe",
"beneficiaryAccountNumber": "123456789012",
"ifsc": "ABCD0123456",
"amount": 5000,
"status": "Success",
"utr": "UTR123456789",
"remarks": null,
"createdAt": "2026-01-09T17:21:00",
"updatedAt": "2026-01-09T17:25:00"
}
}
Get Transaction by Client Reference
Retrieve transaction details using your own reference number (clientTransactionRefNo) that was supplied when creating the transaction.
Path Parameter
| Parameter | Type | Required | Description |
|---|---|---|---|
clientRefNo | string | Yes | The client-supplied reference number used when creating the transaction (e.g. INV-1001) |
Request
curl --location '{{base_url}}/Payout/GetTransactionByClientRef/INV-1001' \
--header 'Authorization: Bearer {{access_token}}'
const clientRefNo = 'INV-1001';
const response = await fetch(`{{base_url}}/Payout/GetTransactionByClientRef/${clientRefNo}`, {
headers: { 'Authorization': 'Bearer {{access_token}}' }
});
const data = await response.json();
import requests
client_ref_no = 'INV-1001'
response = requests.get(
f'{{base_url}}/Payout/GetTransactionByClientRef/{client_ref_no}',
headers={'Authorization': 'Bearer {{access_token}}'}
)
data = response.json()
$clientRefNo = 'INV-1001';
$ch = curl_init("{{base_url}}/Payout/GetTransactionByClientRef/{$clientRefNo}");
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bearer {{access_token}}']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
Response (success)
{
"status": true,
"message": "Transaction fetched successfully",
"transaction": {
"transactionNumber": "TX26010300001",
"clientTransactionRefNo": "INV-1001",
"beneficiaryName": "John Doe",
"beneficiaryAccountNumber": "123456789012",
"ifsc": "ABCD0123456",
"amount": 5000,
"status": "Success",
"utr": "UTR123456789",
"remarks": null,
"createdAt": "2026-01-09T17:21:00",
"updatedAt": "2026-01-09T17:25:00"
}
}
Response (not found)
{
"status": false,
"message": "Transaction not found"
}
clientTransactionRefNo during creation — useful for idempotency checks.Webhooks
Receive real-time HTTP POST notifications to your configured webhook URL whenever a transaction status is updated by the admin.
Webhook Payload
The request body is a flat JSON object sent directly to your endpoint:
{
"transactionNumber": "TXN26021700002",
"clientTransactionRefNo": null,
"status": "Success",
"previousStatus": "Pending",
"utr": "CMS260217147206",
"amount": 6000,
"beneficiaryName": "John Doe",
"beneficiaryAccountNumber": "2345678866",
"ifsc": "SBIN0001234",
"remarks": "Processed",
"updatedAt": "2026-02-17T14:49:55.6725005+05:30",
"timestamp": 1771319995
}
Payload Fields
| Field | Type | Description |
|---|---|---|
transactionNumber | string | Unique system-generated transaction ID |
clientTransactionRefNo | string | null | Your reference number supplied at creation. null if not provided. |
status | string | New (current) status — Success, Failed, or Cancelled |
previousStatus | string | Status before this update (e.g. Pending) |
utr | string | null | UTR reference number. Populated when status is Success. |
amount | number | Transaction amount in INR |
beneficiaryName | string | Name of the beneficiary |
beneficiaryAccountNumber | string | Beneficiary bank account number |
ifsc | string | Beneficiary bank IFSC code |
remarks | string | null | Admin remarks, if any |
updatedAt | string (ISO 8601) | Timestamp of the status change (IST, with offset) |
timestamp | integer | Unix epoch seconds (UTC) — use this to detect and reject replayed webhooks |
Security Headers
Every webhook request includes two custom headers. HTTP delivers them in lowercase — use the exact names below:
| Header (exact, lowercase) | Example value | Description |
|---|---|---|
x-webhook-signature | 55d01f91372a768f6d0c89f5c75871... | HMAC-SHA256 lowercase hex digest of the raw JSON body, keyed with your webhook secret |
x-webhook-timestamp | 1771319995 | Unix epoch seconds (UTC) at the moment the webhook was dispatched |
HMAC-SHA256(key=webhookSecret, message=rawJsonBody) → lowercase hex string.The JSON body uses camelCase keys, no whitespace, and UTF-8 encoding — exactly as received in the request body. To verify, recompute the same HMAC over the raw bytes of the request body before any JSON parsing.
Verifying the Signature
const crypto = require('crypto');
// Call with the raw Buffer body (do NOT parse JSON first)
function verifyWebhook(rawBody, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody) // raw UTF-8 Buffer or string
.digest('hex'); // lowercase hex — matches server output
return crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(signature, 'hex')
);
}
// Express example — use express.raw() so req.body is a Buffer
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-webhook-signature'];
const timestamp = req.headers['x-webhook-timestamp'];
// Reject stale webhooks (older than 5 minutes)
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
return res.status(400).send('Stale webhook');
}
if (!sig || !verifyWebhook(req.body, sig, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const payload = JSON.parse(req.body);
// payload.status → "Success" | "Failed" | "Cancelled"
// payload.utr → UTR string (null if not Success)
// payload.timestamp → Unix epoch — use for your own dedup logic
console.log('Status:', payload.status, '| UTR:', payload.utr);
res.status(200).send('OK');
});
import hmac
import hashlib
import time
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = 'your-webhook-secret'
@app.route('/webhook', methods=['POST'])
def webhook():
# Flask normalises headers — both forms work, but lowercase matches actual delivery
sig = request.headers.get('x-webhook-signature', '')
timestamp = request.headers.get('x-webhook-timestamp', '0')
# Reject stale webhooks (older than 5 minutes)
if abs(time.time() - int(timestamp)) > 300:
abort(400, 'Stale webhook')
raw_body = request.get_data() # raw bytes — do NOT decode/parse first
expected = hmac.new(
WEBHOOK_SECRET.encode('utf-8'),
raw_body,
hashlib.sha256
).hexdigest() # lowercase hex — matches server output
if not hmac.compare_digest(expected, sig):
abort(401, 'Invalid signature')
payload = request.get_json()
# payload['status'] → "Success" | "Failed" | "Cancelled"
# payload['utr'] → UTR string or None
# payload['timestamp'] → Unix epoch int
print('Status:', payload['status'], '| UTR:', payload.get('utr'))
return 'OK', 200
<?php
$secret = 'your-webhook-secret';
$sig = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$timestamp = $_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'] ?? 0;
// Reject stale webhooks (older than 5 minutes)
if (abs(time() - (int)$timestamp) > 300) {
http_response_code(400);
exit('Stale webhook');
}
$rawBody = file_get_contents('php://input');
$expected = hash_hmac('sha256', $rawBody, $secret);
if (!hash_equals($expected, $sig)) {
http_response_code(401);
exit('Invalid signature');
}
$payload = json_decode($rawBody, true);
error_log('Status: ' . $payload['status'] . ' UTR: ' . ($payload['utr'] ?? 'N/A'));
http_response_code(200);
echo 'OK';
2xx response. Webhooks are fire-and-forget — no automatic retries are performed. Configure your webhook URL and secret in the Client Dashboard.default-secret. Always set a strong, unique secret in your dashboard before going to production.Replay attack prevention: Always validate the
X-Webhook-Timestamp header and reject requests older than 5 minutes.Transaction Statuses
| Status | Description |
|---|---|
| Pending | Transaction created and queued for processing |
| Processing | Transaction is being processed by the payment gateway |
| Success | Transaction completed successfully |
| Failed | Transaction failed (insufficient balance, invalid account, etc.) |
| Cancelled | Transaction was reversed/refunded |
Error Handling
API uses standard HTTP status codes and returns error details in JSON format.
Error Response Format
{
"statusCode": 400,
"responseCode": 0,
"status": false,
"message": "Invalid beneficiary name format. Only letters and spaces are allowed."
}
Common HTTP Status Codes
| Code | Description |
|---|---|
| 200 | Success |
| 201 | Created |
| 400 | Bad Request - Validation error |
| 401 | Unauthorized - Invalid or expired token |
| 403 | Forbidden - Insufficient permissions |
| 404 | Not Found |
| 429 | Too Many Requests - Rate limit exceeded |
| 500 | Internal Server Error |
Validation Rules
Beneficiary Name
- Only letters and spaces allowed
- Minimum 3 characters, maximum 28 characters
- No special characters or numbers
Account Number
- 8 to 20 characters
- Alphanumeric
IFSC Code
- Must match pattern: ^[A-Z]{4}0[A-Z0-9]{6}$
- Example: ABCD0123456
Amount
- Minimum: ₹5,000
- Maximum: ₹1,00,00,000
- Must be a positive number
Security Best Practices
- Store ClientId/ClientSecret and webhook secret securely (environment variables or vault)
- Always use HTTPS for all API requests
- Verify webhook signature and timestamp (reject old timestamps to prevent replay attacks)
- Use idempotency key
clientTransactionRefNoto avoid duplicate transactions - Rotate access tokens regularly using refresh token
- Never expose credentials in client-side code or version control
- Implement proper error handling without exposing sensitive information
- Monitor API usage for unusual patterns