Payout Seamless IMPS Integration
For onboarding in both the UAT and production environments, the merchant needs to provide the following information:
Technical Information Required:
IP Address: (For dynamic IPs, please provide a range of IP addresses.)
Callback URL/WebHook URL: This is used to notify your server of any changes in transaction status from our back office.
Once the merchant has provided all the required technical details, we will complete the necessary configuration in our back office and provide the Merchant ID (MID/PID) along with login credentials.
Let's see how it works:
The merchant will send a payout request through our API. Along with that, the merchant has to send the customer's UPI id, account_holder, the amount, payment_mode as upi.
Once we receive the request in the correct format, we will share a payment reference string (ref_code) that is necessary for any future references or actions related to the transaction.
After the payout is processed, whether successful or failed, we will send callback data to the provided callback URL (WebHook URL) with the details of the transaction.
To confirm the status of the payout, the merchant can use the polling API to check the current status and update their system accordingly with the payment outcome. (Status Polling)
AUTHENTICATION :
Required Authentication Headers
All requests must include the following authentication:
API Key: Your provided API key must be passed in X-Api-Key header
Signature: SHA256 signature for request validation
IP Whitelisting: Your server IP must be pre-registered in our system.
All V2 API requests must be authenticated by including a signature parameter in the JSON body.
The logic for creating this signature is identical for all V2 endpoints. Please follow the detailed instructions on our central guide:
➡️ V2 API Signature Generation
PAYOUT REQUEST :
Before proceeding with this section of the document, the developer must review the basic workflow. This page explains how to submit a payment request.
Note: All requests must originate from whitelisted IP addresses. (Please ensure that your IP is whitelisted before making a request.)
Payout Request
POST baseurl/payout/api/v2/request.php
Headers
Content-Type
string
Required
application/json
X-Api-Key
string
Required
Your API Key for authentication
Request Parameters
pid
string
Required
Your provided merchant/partner ID
amount
integer
Required
Payout amount
order_id
string
Required
unique order identifier (minimum 7 characters, alphanumeric)
payment_mode
string
Required
Payment method: imps
email
string
Required
Recipient's email address
phone
string
Required
Recipient's phone number
latitude
string
Required
latitude coordinate
longitude
string
Required
longitude coordinate
signature
string
Required
SHA256 signature for request validation (Refer this for signature generation logic Signature Generation)
ip
string
Required
Customer's IP address
account_holder
string
Required
Benificiary Account holder name
account_no
string
Required
Benificiary Bank Account number
ifsc
string
Required
Benificiary Bank IFSC code
bank
string
Optional
Benificiary Bank name
bank_address
string
Optional
Benificiary Bank address
Sample Request
{
"pid": "MERCHANT123",
"amount": 50000,
"order_id": "ORDER987654321",
"payment_mode": "imps",
"email": "[email protected]",
"phone": "9876543210",
"latitude": "28.7041",
"longitude": "77.1025",
"account_holder": "Jane Smith",
"account_no": "1234567890123456",
"ifsc": "SBIN0001234",
"bank": "State Bank of India",
"bank_address": "Main Branch, New Delhi",
"signature": "b2c3d4e5f6789012345678901234567890123456789012345678901234567890a1"
"ip": "your_ip_address"
}Sample Response Body
HTTP status: 200 OK
Content-Type: application/json
{
"status": "success",
"ref_code": "a1b2c3d4e5f6789012345678901234567890",
"message": "Request accepted"
}{
"status": "error",
"message": "error message"
}Error Response Examples
HTTP status: 200 OK
Content-Type: application/json
{
"status": "error",
"message": "pid, amount, order_id, email, phone, latitude, longitude and payment_mode are required fields"
}{
"status": "error",
"message": "Invalid API key"
}{
"status": "error",
"message": "Invalid signature"
}{
"status": "error",
"message": "Low payout wallet balance , Please add fund"
}{
"status": "error",
"message": "Duplicate order_id Found"
}Important Notes
Order ID: Must be unique and atleast 7 characters long, containing only alphanumeric characters, hyphens and underscores
Phone Number: If longer than 10 digits, only the last 10 digits will be used
IP Whitelisting: Your IP address must be whitelisted for production use
Rate Limiting: API calls are rate-limited based on your merchant configuration
Signature: Always include a valid signature for request authentication
API Key: Must be included in the X-Api-Key header for all requests
Status Codes
success
Payout request accepted and processing
error
Request failed due to validation, authentication or system error
CALLBACK
We trigger your callback URL whenever there is a change in the transaction status.
Valid Transaction status are:
Approved
Declined
Pending
Processing
Failed
Refunded
The most famous transaction changes are (but not limited):
Pending => Processing
Pending => Approved
Pending => Declined/Failed
Approved => Declined/Failed
The callback landing page must be hosted on your server at a secret path, but it should still be publicly accessible from our whitelisted IPs. (Please ensure you accept data only from our IPs by implementing proper IP whitelisting.)
In the POST body, you will receive the following properties in JSON format:
order_id
string
Your order id shared
requested_amount
int
requested amount
processed_amount
int
received amount
bank_ref
string
bank reference/UTR details if available
sender_pg
string
sender account name id if available
ref_code
string
unique code for the transaction
status
string
status of payment at this time
post_hash
string
post hash for security verification
payment_type
string
payment type
request_time
string
payment request time
action_time
string
payment made time
upi_vpa
string
receiver vpa
account_no
string
receiver account number
account_holder
string
receiver name
ifsc
string
receiver IFSC code
bank_name
string
receiver bank name if available
bank_address
string
receiver bank address if available
transaction_info
array
status change log of transaction
Follow the steps to verify the integrity of received data:
Capture and Decode the Payload
Capture the raw JSON data from the POST body and decode it into an array or object.
// Capture and decode the raw POST body
$data = file_get_contents("php://input");
$array = json_decode($data, true);// In a Node.js framework like Express, this is
// handled by middleware.
// app.use(express.json());
//
// Your data will be in req.body:
const array = req.body;# In a Python framework like Flask, this is
# handled by the request object.
#
# from flask import request
# array = request.get_json()Extract and Decode the
post_hash
The post_hash field in the JSON payload is a Base64-encoded string. You must Base64-decode it to get the raw binary data.
// Get the Base64 string from the array
$post_hash_base64 = $array['post_hash'];
// Decode it to get the raw binary ciphertext
$ivHashCiphertext = base64_decode($post_hash_base64);// Get the Base64 string from the array
const postHashBase64 = array.post_hash;
// Decode it to get the raw binary buffer
const ivHashCiphertext = Buffer.from(postHashBase64, 'base64');import base64
# Get the Base64 string from the array (dict)
post_hash_base64 = array['post_hash']
# Decode it to get the raw binary data
ivHashCiphertext = base64.b64decode(post_hash_base64)Decrypt the Binary Data to get the Remote Hash
Pass the raw binary data (not the Base64 string) to the decrypt function along with your secret key.
//$secret_key is your provided SECRET KEY
$remote_hash = decrypt($ivHashCiphertext, $secret_key);// const secret_key = "YOUR_SECRET_KEY";
const remote_hash = decrypt(ivHashCiphertext, secret_key);# secret_key = "YOUR_SECRET_KEY"
remote_hash = decrypt(ivHashCiphertext, secret_key)The decrypt function for your language is provided in the reference section below.
function decrypt($ivHashCiphertext, $password)
{
$method = "AES-256-CBC";
$iv = substr($ivHashCiphertext, 0, 16);
$hash = substr($ivHashCiphertext, 16, 32);
$ciphertext = substr($ivHashCiphertext, 48);
$key = hash('sha256', $password, true);
if (!hash_equals(hash_hmac('sha256', $ciphertext . $iv, $key, true),$hash))
return null;
return openssl_decrypt($ciphertext, $method, $key,
OPENSSL_RAW_DATA, $iv);
}const crypto = require('crypto');
/**
* @param {Buffer} ivHashCiphertext - The raw binary data (Buffer) after Base64-decoding.
* @param {string} password - Your secret key.
* @returns {string|null} The decrypted plaintext or null on failure.
*/
function decrypt(ivHashCiphertext, password) {
const method = 'aes-256-cbc';
// Extract the initialization vector (first 16 bytes)
const iv = ivHashCiphertext.slice(0, 16);
// Extract the hash (next 32 bytes)
const hash = ivHashCiphertext.slice(16, 48);
// Extract the ciphertext (remaining bytes)
const ciphertext = ivHashCiphertext.slice(48);
// Generate the key using SHA-256 hash of the password
const key = crypto.createHash('sha256').update(password, 'utf8').digest();
// Compute HMAC-SHA256 of (ciphertext || iv) using the key
const hmac = crypto.createHmac('sha256', key)
.update(Buffer.concat([ciphertext, iv]))
.digest();
// Compare the computed HMAC with the extracted hash
if (!crypto.timingSafeEqual(hmac, hash)) {
return null; // Hashes do not match; return null
}
try {
// Decrypt the ciphertext using AES-256-CBC
const decipher = crypto.createDecipheriv(method, key, iv);
const plaintext = Buffer.concat([
decipher.update(ciphertext),
decipher.final()
]);
return plaintext.toString('utf8'); // Return the decrypted text as a UTF-8 string
} catch (err) {
// Decryption failed; return null
return null;
}
}import hashlib
import hmac
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
def decrypt(ivHashCiphertext, password):
"""
Decrypts the iv/hash/ciphertext blob.
:param ivHashCiphertext: The raw binary data (after base64-decoding).
:param password: Your secret key string.
:return: The decrypted plaintext string, or None on failure.
"""
try:
# 1. Extract the components
iv = ivHashCiphertext[:16]
hash_val = ivHashCiphertext[16:48]
ciphertext = ivHashCiphertext[48:]
# 2. Generate the key
key = hashlib.sha256(password.encode('utf-8')).digest()
# 3. Verify the HMAC
computed_hash = hmac.new(key, ciphertext + iv, hashlib.sha256).digest()
if not hmac.compare_digest(computed_hash, hash_val):
return None
# 4. Decrypt the ciphertext
backend = default_backend()
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=backend)
decryptor = cipher.decryptor()
# Decrypt and unpad
# Using PKCS7 unpadder explicitly is more robust
decrypted_padded_text = decryptor.update(ciphertext) + decryptor.finalize()
unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
decrypted_text = unpadder.update(decrypted_padded_text) + unpadder.finalize()
return decrypted_text.decode('utf-8')
except Exception as e:
# Catch any error (e.g., bad padding, bad key) and return None
# print(f"Decryption failed: {e}") # Optional: for debugging
return NoneCompute the Local Hash
Compute the local hash using the MD5 128-bit hashing algorithm. Use the order_id, processed_amount, and status received in the callback array.
Note: processed_amount may be a number or null. You must convert it to a string exactly as shown below to match our server's hash.
// Get the values from the same callback $array
$order_id = $array['order_id'];
$processed_amount = (string)$array['processed_amount'];
$status = $array['status'];
$local_hash = md5($order_id . $processed_amount . $status . $secret_key);const crypto = require('crypto');
// Get the values from the same callback array
const order_id = array.order_id;
// Use (value ?? '') to handle both null AND undefined
const processed_amount = String(array.processed_amount ?? '');
const status = array.status;
const local_hash = crypto
.createHash('md5')
.update(order_id + processed_amount + status + secret_key)
.digest('hex');import hashlib
# Get the values from the same callback array (dict)
order_id = array['order_id']
# Use .get() to safely handle a missing key
processed_amount_val = array.get('processed_amount')
# Convert None to "" and numbers to "100"
processed_amount = "" if processed_amount_val is None else str(processed_amount_val)
status = array['status']
local_hash = hashlib.md5((order_id + processed_amount + status + secret_key).encode('utf-8')).hexdigest()Verify Hash
Compare the decrypted remote_hash from the request and the computed local_hash.
if ($remote_hash !== null && hash_equals($local_hash, $remote_hash)) {
// Mark the transaction as success & process the order
$hash_status = "Hash Matched";
} else {
// Verification failed
$hash_status = "Hash Mismatch";
}let hash_status = "Hash Mismatch";
try {
// Both remote_hash (decrypted) and local_hash are hex strings.
// We must use 'utf8' buffers for timingSafeEqual.
const localHashBuffer = Buffer.from(local_hash, 'utf8');
const remoteHashBuffer = Buffer.from(remote_hash, 'utf8');
if (localHashBuffer.length === remoteHashBuffer.length &&
crypto.timingSafeEqual(localHashBuffer, remoteHashBuffer)) {
hash_status = "Hash Matched";
}
} catch (e) {
// hash_status remains "Hash Mismatch"
}import hmac
# Use hmac.compare_digest for secure comparison
if remote_hash and hmac.compare_digest(local_hash, remote_hash):
hash_status = "Hash Matched"
else:
hash_status = "Hash Mismatch"Acknowledge the Payment Gateway
To confirm you have received the callback and to prevent our gateway from sending retries, you must do two things:
Respond with an HTTP 200 OK status code.
Respond with a JSON body containing the key
"acknowledge"set to the string"yes".
// --- This is the required acknowledgment ---
// 1. Set the HTTP 200 OK status code
http_response_code(200);
// 2. Prepare the required JSON response body
$response_data = [
'acknowledge' => 'yes',
'hash_status' => $hash_status // You can include this for your own logs
];
// 3. Send the response
header('Content-Type: application/json; charset=utf-8');
echo json_encode($response_data);
exit;// In a Node.js framework like Express,
// this sends both the 200 status and the JSON body.
res.status(200).json({
acknowledge: 'yes',
hash_status: hash_status
});# In a Python framework like Flask
from flask import jsonify
response_data = {
'acknowledge': 'yes',
'hash_status': hash_status
}
# This returns a 200 status and the JSON body
return jsonify(response_data), 200STATUS POLLING :
The payout Status Polling API allows you to retrieve real-time status information for payout transactions. This endpoint provides comprehensive transaction details including payment status, amounts and verification hashes.
Note : All requests must include proper authentication headers and IP whitelisting may be required for production environments.
POST {baseurl}/payout/api/v2/status_polling.php
Authentication
This API required the following authentication mechanism:
Required Headers
Content-Type
string
Yes
Must be application/json
X-Api-Key
string
Yes
Your provided API key for authentication
IP Whitelisting
Your server IP address must be whitelisted in production environments. Contact your administration to ensure your IP is properly configured.
Request Parameters
pid
string
Yes
Your unique merchant/partner ID
ref_code
string
Yes
unique ref_code which is generated in payout request
post_hash
string
Yes
The Base64-encoded encrypted hash. (See steps below).
Steps to generate post_hash :
Generate the Request
post_hash
The following code shows the complete flow of creating the MD5 hash, encrypting it, and Base64-encoding it to get the final post_hash string.
$ref_code = "YOUR_REF_CODE";
$pid = "YOUR_PID";
$secret_key = "YOUR_SECRET_KEY";
// 1.1 Create Plaintext Hash
$local_hash = md5($ref_code . $pid . $secret_key);
// 1.2 Encrypt the Hash
// (Assumes encrypt() function is defined from reference)
$encrypted_hash = encrypt($local_hash, $secret_key);
// 1.3 Base64 Encode
$post_hash = base64_encode($encrypted_hash);
// $post_hash is now ready to be sentconst crypto = require('crypto');
// Assumes 'encrypt' function is imported from reference
// const { encrypt } = require('./crypto-helpers');
const ref_code = "YOUR_REF_CODE";
const pid = "YOUR_PID";
const secret_key = "YOUR_SECRET_KEY";
// 1.1 Create Plaintext Hash
const local_hash = crypto
.createHash('md5')
.update(ref_code + pid + secret_key)
.digest('hex');
// 1.2 Encrypt the Hash
// 'encrypt' function returns a raw Buffer
const encrypted_hash_buffer = encrypt(local_hash, secret_key);
// 1.3 Base64 Encode
const post_hash = encrypted_hash_buffer.toString('base64');
// post_hash is now ready to be sentimport hashlib
import base64
# Assumes 'encrypt' function is defined from reference
# from crypto_helpers import encrypt
ref_code = "YOUR_REF_CODE"
pid = "YOUR_PID"
secret_key = "YOUR_SECRET_KEY"
# 1.1 Create Plaintext Hash
local_hash = hashlib.md5((ref_code + pid + secret_key).encode('utf-8')).hexdigest()
# 1.2 Encrypt the Hash
# 'encrypt' function returns raw bytes
encrypted_hash_bytes = encrypt(local_hash, secret_key)
# 1.3 Base64 Encode
post_hash = base64.b64encode(encrypted_hash_bytes).decode('utf-8')
# post_hash is now ready to be sentRefer the encrpt function below of your language:
function encrypt($plaintext, $password)
{
$method = "AES-256-CBC";
$key = hash('sha256', $password, true);
$iv = openssl_random_pseudo_bytes(16);
$ciphertext = openssl_encrypt($plaintext, $method, $key,
OPENSSL_RAW_DATA, $iv);
$hash = hash_hmac('sha256', $ciphertext . $iv, $key, true);
return $iv . $hash . $ciphertext;
}import hashlib
import hmac
import os
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding as crypto_padding
def encrypt(plaintext, password):
key = hashlib.sha256(password.encode('utf-8')).digest()
iv = os.urandom(16)
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext_bytes = plaintext.encode('utf-8')
padded_plaintext = pad(plaintext_bytes, AES.block_size)
ciphertext = cipher.encrypt(padded_plaintext)
hash_value = hmac.new(key, ciphertext + iv, hashlib.sha256).digest()
return iv + hash_value + ciphertextconst crypto = require('crypto');
function encrypt(plaintext, password) {
const method = 'aes-256-cbc';
const key = crypto.createHash('sha256').update(password, 'utf8').digest();
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(method, key, iv);
let ciphertext = cipher.update(plaintext, 'utf8');
ciphertext = Buffer.concat([ciphertext, cipher.final()]);
const hmac = crypto.createHmac('sha256', key);
hmac.update(Buffer.concat([ciphertext, iv]));
const hash = hmac.digest();
// Return the raw binary Buffer
return Buffer.concat([iv, hash, ciphertext]);
}Send the POST Request
Send a POST request containing pid, ref_code, and post_hash as a JSON body , and you will receive a response after validating the data.
<?php
// --- Step 1: Data (from above) ---
$pid = "YOUR_PID";
$ref_code = "YOUR_REF_CODE";
$secret_key = "YOUR_SECRET_KEY";
$api_key = "YOUR_API_KEY_HERE";
$local_hash = md5($ref_code . $pid . $secret_key);
$encrypted_hash = encrypt($local_hash, $secret_key);
$post_hash = base64_encode($encrypted_hash);
// --- Step 2: Send Request ---
$api_url = "https://<domain>/payout/api/v2/status_polling.php";
$data = [
'pid' => $pid,
'ref_code' => $ref_code,
'post_hash' => $post_hash
];
$headers = [
'Content-Type: application/json',
'X-Api-Key: ' . $api_key
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $api_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$server_output = curl_exec($ch);
curl_close($ch);
// $server_output now contains the JSON response
?># Requires the 'requests' library: pip install requests
import requests
import json
# Assumes 'post_hash' was generated in Step 1
api_url = "https://<domain>/payout/api/v2/status_polling.php"
data = {
'pid': "YOUR_PID",
'ref_code': "YOUR_REF_CODE",
'post_hash': post_hash # The string from Step 1
}
headers = {
'Content-Type': 'application/json',
'X-Api-Key': 'YOUR_API_KEY_HERE'
}
try:
response = requests.post(api_url, data=json.dumps(data), headers=headers)
response.raise_for_status() # Raises an error for bad responses (4xx, 5xx)
server_output = response.json()
# server_output now contains the parsed JSON response
print(server_output)
except requests.exceptions.HTTPError as err:
print(f"HTTP error: {err.response.text}")
except Exception as err:
print(f"An error occurred: {err}")// Requires the 'axios' library: npm install axios
const axios = require('axios');
// Assumes 'post_hash' was generated in Step 1
const api_url = "https://<domain>/payout/api/v2/status_polling.php";
const data = {
pid: "YOUR_PID",
ref_code: "YOUR_REF_CODE",
post_hash: post_hash // The string from Step 1
};
const headers = {
'Content-Type': 'application/json',
'X-Api-Key': 'YOUR_API_KEY_HERE'
};
async function pollStatus() {
try {
const response = await axios.post(api_url, data, { headers: headers });
const server_output = response.data;
// server_output now contains the JSON response
console.log(server_output);
} catch (error) {
console.error("Error polling status:",
error.response ? error.response.data : error.message
);
}
}
pollStatus();Processing the API Response
The API will respond with a JSON object. If the request is successful and the ref_code is found, it will return the transaction details. If it fails (e.g., bad hash, ref_code not found), it will return an error message.
Success Response Parameters
A successful response will contain the following parameters as JSON body:
order_id
String
Your order id shared
requested_amount
Number
Requested amount (as a float)
processed_amount
Number
Processed amount (as a float, e.g., 100.0 or 0.0)
bank_reference
String
Bank reference/UTR details if available
ref_code
String
Unique code for the transaction
status
String
Status of payment at this time
time
Number
Raw Unix timestamp of the last action
payment_type
String
Payment type (e.g., UPI, IMPS)
request_time
String
Payment request time (ISO 8601 format)
action_time
String
Payment made time (ISO 8601 format)
upi_vpa
String
Receiver VPA if available
account_no
String
Receiver account number
account_holder
String
Receiver name
ifsc
String
Receiver IFSC code
bank_name
String
Receiver bank name if available
bank_address
String
Receiver bank address if available
transaction_info
Array
Status change log of the transaction
post_hash
String
Post hash for security verification
Error Response
If your request fails, the server will respond with an appropriate HTTP status code and a JSON error body.
{
"error": "error message"
}Common Errors and HTTP status
400
No input data received
The raw POST body was empty.
400
Invalid JSON format in request body
The data sent was not valid JSON.
400
Missing required parameters
Your JSON body is missing pid, ref_code, or post_hash.
400
Invalid base64 encoding in post_hash
The post_hash string was not valid Base64.
400
Invalid hash
The signature verification failed. The hashes do not match.
400
Reference code not found
The requested ref_code does not exist for this PID.
4S1
Invalid PID
The pid sent does not exist in our system.
401
X-Api-Key header is required
The X-Api-Key was not found in the request headers.
401
Invalid API key
The provided X-Api-Key is incorrect.
403
Please contact admin to request white list your IP: ...
Your server's IP is not in the payout whitelist.
429
[Dynamic rate limit message]
You have exceeded the allowed request rate.
500
Database connection failed
A server-side error occurred. Please contact support.
Verify the Response
post_hash
Before trusting any data from the response, you must verify its post_hash. This verification logic is identical to the logic used for verifying a callback.
You do not need to send an "Acknowledge" response for a status poll.
4.1 Compute the Local Hash
From the JSON response, get the order_id, processed_amount, and status. Concatenate them with your secret_key and create an MD5 hash.
Note: The processed_amount is a float in the response. The code below correctly handles converting it to a string to match our server's hash.
// $response_data is the decoded JSON response
$order_id = $response_data['order_id'];
// PHP converts the float (e.g., 100.0) to "100"
$processed_amount = $response_data['processed_amount'];
$status = $response_data['status'];
$local_hash = md5($order_id . $processed_amount . $status . $secret_key);const crypto = require('crypto');
// responseData is the parsed JSON response
const order_id = responseData.order_id;
// Node.js converts the float (e.g., 100.0) to "100"
const processed_amount = responseData.processed_amount;
const status = responseData.status;
const local_hash = crypto
.createHash('md5')
.update(order_id.toString() + processed_amount.toString() + status.toString() + secret_key)
.digest('hex');import hashlib
# response_data is the parsed JSON response (a dict)
order_id = response_data['order_id']
# CRITICAL: Use "%g" to format the float (e.g., 100.0)
# to "100", matching our server-side logic.
# Using str(100.0) would fail (it becomes "100.0").
processed_amount = "%g" % response_data['processed_amount']
status = response_data['status']
local_hash = hashlib.md5((order_id + processed_amount + status + secret_key).encode('utf-8')).hexdigest()4.2 Decrypt the Remote Hash
Get the post_hash string from the JSON response. Base64-decode it, then pass the raw binary data to the decrypt function.
// Get the Base64 string from the response
$post_hash_base64 = $response_data['post_hash'];
// Decode it to get the raw binary ciphertext
$ivHashCiphertext = base64_decode($post_hash_base64);
// Decrypt using the function from the reference section below
$remote_hash = decrypt($ivHashCiphertext, $secret_key);// Get the Base64 string from the response
const postHashBase64 = responseData.post_hash;
// Decode it to get the raw binary buffer
const ivHashCiphertext = Buffer.from(postHashBase64, 'base64');
// Decrypt using the function from the reference section below
const remote_hash = decrypt(ivHashCiphertext, secret_key);import base64
# Get the Base64 string from the response
post_hash_base64 = response_data['post_hash']
# Decode it to get the raw binary data
ivHashCiphertext = base64.b64decode(post_hash_base64)
# Decrypt using the function from the reference section below
remote_hash = decrypt(ivHashCiphertext, secret_key)The decrypt function for your language is provided in the reference section below.
function decrypt($ivHashCiphertext, $password)
{
$method = "AES-256-CBC";
$iv = substr($ivHashCiphertext, 0, 16);
$hash = substr($ivHashCiphertext, 16, 32);
$ciphertext = substr($ivHashCiphertext, 48);
$key = hash('sha256', $password, true);
if (!hash_equals(hash_hmac('sha256', $ciphertext . $iv, $key, true),$hash))
return null;
return openssl_decrypt($ciphertext, $method, $key,
OPENSSL_RAW_DATA, $iv);
}const crypto = require('crypto');
/**
* @param {Buffer} ivHashCiphertext - The raw binary data (Buffer) after Base64-decoding.
* @param {string} password - Your secret key.
* @returns {string|null} The decrypted plaintext or null on failure.
*/
function decrypt(ivHashCiphertext, password) {
const method = 'aes-256-cbc';
// Extract the initialization vector (first 16 bytes)
const iv = ivHashCiphertext.slice(0, 16);
// Extract the hash (next 32 bytes)
const hash = ivHashCiphertext.slice(16, 48);
// Extract the ciphertext (remaining bytes)
const ciphertext = ivHashCiphertext.slice(48);
// Generate the key using SHA-256 hash of the password
const key = crypto.createHash('sha256').update(password, 'utf8').digest();
// Compute HMAC-SHA256 of (ciphertext || iv) using the key
const hmac = crypto.createHmac('sha256', key)
.update(Buffer.concat([ciphertext, iv]))
.digest();
// Compare the computed HMAC with the extracted hash
if (!crypto.timingSafeEqual(hmac, hash)) {
return null; // Hashes do not match; return null
}
try {
// Decrypt the ciphertext using AES-256-CBC
const decipher = crypto.createDecipheriv(method, key, iv);
const plaintext = Buffer.concat([
decipher.update(ciphertext),
decipher.final()
]);
return plaintext.toString('utf8'); // Return the decrypted text as a UTF-8 string
} catch (err) {
// Decryption failed; return null
return null;
}
}import hashlib
import hmac
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
def decrypt(ivHashCiphertext, password):
"""
Decrypts the iv/hash/ciphertext blob.
:param ivHashCiphertext: The raw binary data (after base64-decoding).
:param password: Your secret key string.
:return: The decrypted plaintext string, or None on failure.
"""
try:
# 1. Extract the components
iv = ivHashCiphertext[:16]
hash_val = ivHashCiphertext[16:48]
ciphertext = ivHashCiphertext[48:]
# 2. Generate the key
key = hashlib.sha256(password.encode('utf-8')).digest()
# 3. Verify the HMAC
computed_hash = hmac.new(key, ciphertext + iv, hashlib.sha256).digest()
if not hmac.compare_digest(computed_hash, hash_val):
return None
# 4. Decrypt the ciphertext
backend = default_backend()
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=backend)
decryptor = cipher.decryptor()
# Decrypt and unpad
# Using PKCS7 unpadder explicitly is more robust
decrypted_padded_text = decryptor.update(ciphertext) + decryptor.finalize()
unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
decrypted_text = unpadder.update(decrypted_padded_text) + unpadder.finalize()
return decrypted_text.decode('utf-8')
except Exception as e:
# Catch any error (e.g., bad padding, bad key) and return None
# print(f"Decryption failed: {e}") # Optional: for debugging
return None4.3 Compare the Hashes
Securely compare the local_hash you just computed with the remote_hash you decrypted.
if ($remote_hash !== null && hash_equals($local_hash, $remote_hash)) {
// --- SUCCESS: Data is verified ---
// You can now trust the data and update your database.
// echo "Status: " . $response_data['status'];
} else {
// --- FAILURE: Hash mismatch! ---
// Do NOT trust this data.
}let isVerified = false;
if (remote_hash) {
try {
const localHashBuffer = Buffer.from(local_hash, 'utf8');
const remoteHashBuffer = Buffer.from(remote_hash, 'utf8');
if (localHashBuffer.length === remoteHashBuffer.length) {
isVerified = crypto.timingSafeEqual(localHashBuffer, remoteHashBuffer);
}
} catch (e) {
isVerified = false;
}
}
if (isVerified) {
// --- SUCCESS: Data is verified ---
// console.log("Status:", responseData.status);
} else {
// --- FAILURE: Hash mismatch! ---
}import hmac
isVerified = False
if remote_hash:
isVerified = hmac.compare_digest(local_hash, remote_hash)
if isVerified:
# --- SUCCESS: Data is verified ---
# print(f"Status: {response_data['status']}")
pass
else:
# --- FAILURE: Hash mismatch! ---
passTRANSACTION STATUS :
Approved: Payment has been successfully approved by our system.
Failed: Payment failed on the bank's side.
Processing: The bank is currently processing the payment.
Declined: Payment has been declined by our system.
Pending: The user session is active, and the payment is awaiting completion.
Refunded : The amount is refunded to customer
ERROR :
No valid channel found : This error raise if no active channel/provider mapped against your MID
Bank error , please contact admin : Unknown bank side error
Hash value is not defined : This error raises for status check if the provided hash is not matched with genereted one
Ref code does not exist : This error raise in the status check , if ref_code is not provided for status check
COMPLAINT
We have a dedicated Complaint Section where merchants can manage transaction-related complaints. Through this section, merchants can submit complaints with all necessary details and optional evidence. Upon submission, a unique complaint reference ID is generated, allowing merchants to track the complaint’s status and receive real-time updates via the status-check API. This ensures a smooth, secure, and efficient process for resolving any transaction issues.
RECONCILIATION
This API endpoint allows authorized users to retrieve approved payout transactions based on a specific pid (Partner ID) and date. The API performs authentication using a token and signature verification to ensure secure communication.
Last updated