Partner Payments UPI TPV Integration
Partner Payments UPI TPV (Third-Party Verification) enables partners to initiate UPI payments with beneficiary account validation. Unlike standard UPI Intent, TPV ensures that the payment is made from a specific verified bank account by validating the customer's UPI account against the beneficiary details you provide.
This is critical for regulatory compliance scenarios where you need to ensure funds originate from an authorized account, such as loan repayments where the borrower must pay from their registered account, or vendor payments where the vendor must use their verified business account.
Key Benefits:
- Account validation — Ensures payment comes from the authorized beneficiary account
- Regulatory compliance — Meets KYC and anti-money laundering requirements
- Fraud prevention — Prevents payments from unauthorized accounts
- Audit trail — Complete record of which account was used for payment
- Secure S2S flow — Direct UPI app invocation with account verification
This integration is ideal for:
- Loan repayments — EMI collections from borrower's registered account
- Vendor payments — Ensure payments come from vendor's verified business account
- Refund collections — Collect refunds to the original payment account
- Compliance-heavy industries — NBFC, lending, insurance, government payments
How It Works
The Partner Payments UPI TPV flow follows these steps:
-
Get the Access Token — Obtain an access token with scope:
hub_session -
Initiate UPI TPV Payment — POST a payment request with:
txn_s2s_flow=4(enables UPI S2S flow)- Beneficiary account details (
beneficiarydetailwith IFSC, account number, account holder name) - Customer device details (
s2s_client_ip,s2s_device_info) - Computed hash
-
Receive intentURIData — PayU returns a UPI deep link string and sets
bankcode=INTTPV,api_version=6internally -
Invoke UPI App — Customer's UPI app opens with pre-filled payment details
-
Account Validation — PayU validates the customer's UPI account against beneficiary details provided
-
Customer Authenticates — If account matches, customer enters UPI PIN; if mismatch, payment is rejected
-
Receive Webhook — PayU sends payment status notification with
bankcode=INTTPV -
Verify Payment — Call Verify Payment API to confirm final transaction status
Prerequisites
Before you begin, ensure you have:
- Partner OAuth Application registered with PayU with the above scopes enabled
- OAuth Credentials:
client_idandclient_secret - Merchant Credentials:
merchant_id(PayU merchant ID) andreseller_id(partner UUID) - UPI TPV Enabled — Your account must be enabled for UPI TPV transactions (contact PayU)
- S2S Flow Enabled — Support for
txn_s2s_flow=4 - Partner Webhook URLs configured (
partner_webhook_success,partner_webhook_failure,partner_webhook_cancelled) - Beneficiary Account Details:
- IFSC code
- Account number
- Account holder name
- Ability to Capture:
- Customer IP address (
s2s_client_ip) - Device user-agent string (
s2s_device_info)
- Customer IP address (
- Test Environment Access to
https://test-partnerapilayer.payu.in
Step 1: Get the Access Token
Step 1.1: Receiving the auth_code on the redirect URI
The auth_code is received on the configured redirect URI.
Step 1.2: Validate this auth_code
Validate this auth_code using the Validate Auth Code and Client API.
You will receive an access_token.
Request Parameters
| Parameter | Required | Description | Example value |
|---|---|---|---|
client_id | Yes | Client identifier. | {{client_id}} |
client_secret | Yes | Client secret code. | {{client_secret}} |
grant_type | Yes | Grant type used to obtain an access token in this flow. Must be authorization_code. | authorization_code |
code | Yes | Authorization code received on the redirect URI. | {{authorization_code}} |
redirect_uri | Yes | Redirect URL associated with the authorization request. It must match the redirect URI used for the authorization request. | {{redirect_uri}} |
Samp[e request
curl --location '{{accounts_base_url}}/oauth/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id={{client_id}}' \
--data-urlencode 'client_secret={{client_secret}}' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'code={{authorization_code}}' \
--data-urlencode 'redirect_uri={{redirect_uri}}'Sample response
{
"access_token": "{{access_token}}",
"token_type": "Bearer",
"expires_in": {{expires_in}},
"refresh_token": "{{refresh_token}}",
"scope": "{{scope}}",
"created_at": {{created_at}},
"user_uuid": "{USER_UUID}"
}Step 1.3: Use the access_token
Send the access token as a bearer token when calling Partner Integration APIs.
Step 2: Initiate UPI TPV Payment
Step 2.1: Prepare Request Parameters
Endpoint URLs:
| Environment | URL |
|---|---|
| Test | https://test-partnerapilayer.payu.in/apilayer/partner/payments |
| Production | https://api.payu.in/partner/payments |
HTTP Method: POST
Headers:
Authorization: Bearer <your_access_token>
Content-Type: application/json
Request Body Parameters:
Mandatory Parameters
| Parameter | Type & Description | Example |
|---|---|---|
| txnid | string - Unique transaction ID generated by the partner. | TPVUPI20240315001 |
| amount | string - Transaction amount in decimal format. | 518.02 |
| productinfo | string - Product or service description. | Loan EMI Payment |
| phone | string - Customer phone number with country code (10 digits). | 919876543210 |
| merchant_id | integer - PayU merchant ID. | 8739528 |
| reseller_id | string - Partner/reseller UUID. | 11ee-0e7e-5403fde2-9523-0a696b110fde |
| txn_s2s_flow | string - Server-to-server flow identifier. Must be 4 for UPI TPV. |
4 |
| s2s_client_ip | string - Customer's IP address. | 157.240.22.9 |
| s2s_device_info | string - Customer's device user-agent string. | Mozilla/5.0 (iPhone) AppleWebKit/602.4.6 |
| beneficiarydetail | string (JSON) - Beneficiary account details for TPV validation. Must contain: ifscCode, accountNumber, accountHolderName |
{"ifscCode":"ICIC0001234","accountNumber":"123456789012","accountHolderName":"Test User"} |
| hash | string - SHA-512 hash for request authentication, encoded as a lowercase hex string. Note: beneficiarydetail is NOT included in hash calculation. | a3f7c92e1b... |
Optional Parameters
| Parameter | Type & Description | Example |
|---|---|---|
| firstname | string - Customer's first name. | Amit |
| string - Customer's email address. | [email protected] | |
| udf1 | string - User-defined field 1 for custom data. | loan_account_123 |
| udf2 | string - User-defined field 2 for custom data. | emi_month_06 |
| udf3 | string - User-defined field 3 for custom data. | tpv_reference_001 |
| udf4 | string - User-defined field 4 for custom data. | borrower_id_456 |
| udf5 | string - User-defined field 5, often used for partner or channel ID. | partner_tpv_channel |
| surl | string - Success callback URL (optional for S2S flow). | https://yoursite.com/payment/success |
| furl | string - Failure callback URL (optional for S2S flow). | https://yoursite.com/payment/failure |
| curl | string - Cancel callback URL (optional for S2S flow). | https://yoursite.com/payment/cancel |
Beneficiarydetail Structure:
{
"ifscCode": "ICIC0001234",
"accountNumber": "123456789012",
"accountHolderName": "Test User"
}Send this as a JSON string (not an object) in the beneficiarydetail parameter.
Step 2.2: Generate Payment Request Hash
The payment request hash authenticates your API call using SHA-512. Important: The beneficiarydetail parameter is NOT included in the hash calculation.
Hash Formula:
merchant_id|txnid|amount|productinfo|firstname|email|udf1|udf2|udf3|udf4|udf5||||||client_secret
Sample Hash Generation Code:
Python:
import hashlib
def generate_upi_tpv_hash(merchant_id, txnid, amount, productinfo, firstname, email, udf1, udf2, udf3, udf4, udf5, client_secret):
# Note: beneficiarydetail is NOT included in hash
hash_string = f"{merchant_id}|{txnid}|{amount}|{productinfo}|{firstname}|{email}|{udf1}|{udf2}|{udf3}|{udf4}|{udf5}||||||{client_secret}"
return hashlib.sha512(hash_string.encode('utf-8')).hexdigest()
# Example
payment_hash = generate_upi_tpv_hash(
merchant_id=8739528,
txnid="TPVUPI20240315001",
amount="518.02",
productinfo="Loan EMI Payment",
firstname="Amit",
email="[email protected]",
udf1="loan_account_123",
udf2="emi_month_06",
udf3="tpv_reference_001",
udf4="",
udf5="partner_tpv_channel",
client_secret="your_client_secret_here"
)
print(f"Payment Hash: {payment_hash}")Java:
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class UPITPVHashGenerator {
public static String generateHash(
int merchantId, String txnid, String amount, String productinfo,
String firstname, String email, String udf1, String udf2,
String udf3, String udf4, String udf5, String clientSecret
) throws NoSuchAlgorithmException {
// Note: beneficiarydetail is NOT included in hash
String hashString = merchantId + "|" + txnid + "|" + amount + "|" +
productinfo + "|" + firstname + "|" + email + "|" +
udf1 + "|" + udf2 + "|" + udf3 + "|" + udf4 + "|" +
udf5 + "||||||" + clientSecret;
MessageDigest md = MessageDigest.getInstance("SHA-512");
byte[] hashBytes = md.digest(hashString.getBytes());
StringBuilder hexString = new StringBuilder();
for (byte b : hashBytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString();
}
}PHP:
<?php
function generateUPITPVHash($merchantId, $txnid, $amount, $productinfo,
$firstname, $email, $udf1, $udf2, $udf3,
$udf4, $udf5, $clientSecret) {
// Note: beneficiarydetail is NOT included in hash
$hashString = $merchantId . "|" . $txnid . "|" . $amount . "|" .
$productinfo . "|" . $firstname . "|" . $email . "|" .
$udf1 . "|" . $udf2 . "|" . $udf3 . "|" . $udf4 . "|" .
$udf5 . "||||||" . $clientSecret;
return hash('sha512', $hashString);
}
$hash = generateUPITPVHash(
8739528,
"TPVUPI20240315001",
"518.02",
"Loan EMI Payment",
"Amit",
"[email protected]",
"loan_account_123",
"emi_month_06",
"tpv_reference_001",
"",
"partner_tpv_channel",
"your_client_secret_here"
);
echo "Payment Hash: " . $hash;
?>Step 2.3: POST the Payment Request
Sample Request (cURL):
curl --location 'https://test-partnerapilayer.payu.in/apilayer/partner/payments' \
--header 'Authorization: Bearer your_access_token_here' \
--header 'Content-Type: application/json' \
--data '{
"txnid": "TPVUPI20240315001",
"amount": "518.02",
"productinfo": "Loan EMI Payment",
"firstname": "Amit",
"email": "[email protected]",
"phone": "919876543210",
"merchant_id": 8739528,
"reseller_id": "11ee-0e7e-5403fde2-9523-0a696b110fde",
"txn_s2s_flow": "4",
"s2s_client_ip": "157.240.22.9",
"s2s_device_info": "Mozilla/5.0 (iPhone) AppleWebKit/602.4.6",
"beneficiarydetail": "{\"ifscCode\":\"ICIC0001234\",\"accountNumber\":\"123456789012\",\"accountHolderName\":\"Test User\"}",
"udf1": "loan_account_123",
"udf2": "emi_month_06",
"udf3": "tpv_reference_001",
"udf5": "partner_tpv_channel",
"hash": "computed_sha512_hash_here"
}'Sample Request (Python):
import requests
import json
url = "https://test-partnerapilayer.payu.in/apilayer/partner/payments"
headers = {
'Authorization': 'Bearer your_access_token_here',
'Content-Type': 'application/json'
}
# Beneficiary details as JSON string
beneficiary_detail = json.dumps({
"ifscCode": "ICIC0001234",
"accountNumber": "123456789012",
"accountHolderName": "Test User"
})
payload = {
"txnid": "TPVUPI20240315001",
"amount": "518.02",
"productinfo": "Loan EMI Payment",
"firstname": "Amit",
"email": "[email protected]",
"phone": "919876543210",
"merchant_id": 8739528,
"reseller_id": "11ee-0e7e-5403fde2-9523-0a696b110fde",
"txn_s2s_flow": "4",
"s2s_client_ip": "157.240.22.9",
"s2s_device_info": "Mozilla/5.0 (iPhone) AppleWebKit/602.4.6",
"beneficiarydetail": beneficiary_detail,
"udf1": "loan_account_123",
"udf2": "emi_month_06",
"udf3": "tpv_reference_001",
"udf5": "partner_tpv_channel",
"hash": "computed_sha512_hash_here"
}
try:
response = requests.post(url, headers=headers, data=json.dumps(payload))
print(f"Status Code: {response.status_code}")
print(f"Response: {response.text}")
except Exception as e:
print(f"Error: {str(e)}")Sample Request (Java):
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class CreateUPITPVPayment {
public static void main(String[] args) throws Exception {
String url = "https://test-partnerapilayer.payu.in/apilayer/partner/payments";
// Escape the beneficiarydetail JSON properly
String payload = "{\"txnid\":\"TPVUPI20240315001\",\"amount\":\"518.02\",\"productinfo\":\"Loan EMI Payment\",\"firstname\":\"Amit\",\"email\":\"[email protected]\",\"phone\":\"919876543210\",\"merchant_id\":8739528,\"reseller_id\":\"11ee-0e7e-5403fde2-9523-0a696b110fde\",\"txn_s2s_flow\":\"4\",\"s2s_client_ip\":\"157.240.22.9\",\"s2s_device_info\":\"Mozilla/5.0 (iPhone) AppleWebKit/602.4.6\",\"beneficiarydetail\":\"{\\\"ifscCode\\\":\\\"ICIC0001234\\\",\\\"accountNumber\\\":\\\"123456789012\\\",\\\"accountHolderName\\\":\\\"Test User\\\"}\",\"udf1\":\"loan_account_123\",\"udf2\":\"emi_month_06\",\"udf3\":\"tpv_reference_001\",\"udf5\":\"partner_tpv_channel\",\"hash\":\"computed_sha512_hash_here\"}";
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Authorization", "Bearer your_access_token_here")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("Status Code: " + response.statusCode());
System.out.println("Response Body: " + response.body());
}
}Sample Request (PHP):
<?php
$url = "https://test-partnerapilayer.payu.in/apilayer/partner/payments";
$headers = array(
'Authorization: Bearer your_access_token_here',
'Content-Type: application/json'
);
// Beneficiary details as JSON string
$beneficiaryDetail = json_encode(array(
"ifscCode" => "ICIC0001234",
"accountNumber" => "123456789012",
"accountHolderName" => "Test User"
));
$payload = json_encode(array(
"txnid" => "TPVUPI20240315001",
"amount" => "518.02",
"productinfo" => "Loan EMI Payment",
"firstname" => "Amit",
"email" => "[email protected]",
"phone" => "919876543210",
"merchant_id" => 8739528,
"reseller_id" => "11ee-0e7e-5403fde2-9523-0a696b110fde",
"txn_s2s_flow" => "4",
"s2s_client_ip" => "157.240.22.9",
"s2s_device_info" => "Mozilla/5.0 (iPhone) AppleWebKit/602.4.6",
"beneficiarydetail" => $beneficiaryDetail,
"udf1" => "loan_account_123",
"udf2" => "emi_month_06",
"udf3" => "tpv_reference_001",
"udf5" => "partner_tpv_channel",
"hash" => "computed_sha512_hash_here"
));
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
echo "Status Code: " . $statusCode . "\n";
echo "Response: " . $response;
?>Step 2.4: Handle Payment Response
Success Response:
{
"metaData": {
"message": null,
"referenceId": "7a3060b7462bd2ce6d025c9997220e01",
"statusCode": null,
"txnId": "TPVUPI20240315001",
"txnStatus": "pending",
"unmappedStatus": "pending"
},
"result": {
"paymentId": "30478359671",
"merchantName": "YourMerchantName",
"merchantVpa": "merchant.payu@indus",
"amount": "518.02",
"intentURIData": "pa=merchant.payu@indus&pn=YOUR MERCHANT NAME&tr=30478359671&tid=TPVUPI20240315001&am=518.02&cu=INR&tn=UPIIntent",
"acsTemplate": null,
"otpPostUrl": null
}
}Key Response Fields:
| Field | Description |
|---|---|
metaData.txnStatus | Initial status (typically "pending" for UPI TPV) |
result.paymentId | PayU's internal payment ID (mihpayid) |
result.merchantVpa | Merchant's UPI VPA for this transaction |
result.intentURIData | Critical — UPI deep link string to invoke UPI apps |
Step 3: Receive Payment Notification
Step 3.1: Partner Webhook
After the customer completes payment (or if account validation fails), PayU sends a webhook notification to your configured partner webhook URL.
Sample Success Webhook Payload (UPI TPV):
{
"key": "JPM7Fg",
"txnid": "TPVUPI20240315001",
"mihpayid": "30478359671",
"status": "success",
"unmappedstatus": "captured",
"mode": "UPI",
"bankcode": "INTTPV",
"amount": "518.02",
"productinfo": "Loan EMI Payment",
"firstname": "Amit",
"email": "[email protected]",
"phone": "919876543210",
"udf1": "loan_account_123",
"udf2": "emi_month_06",
"udf3": "tpv_reference_001",
"udf4": "",
"udf5": "partner_tpv_channel",
"merchant_id": "8739528",
"error": "No Error",
"error_Message": "No Error",
"hash": "webhook_hash_from_payu"
}UPI TPV-Specific Fields:
| Field | Value for UPI TPV |
|---|---|
mode | "UPI" |
bankcode | "INTTPV" (automatically set by PayU) |
unmappedstatus | "captured" (success) or "failed" (failure/validation failed) |
Step 3.2: Verify Webhook Hash
Always verify the webhook hash before processing.
Reverse Hash Formula:
client_secret|status|||||udf5|udf4|udf3|udf2|udf1|email|firstname|productinfo|amount|txnid|merchant_id
Sample Verification Code:
Python:
import hashlib
def verify_upi_tpv_webhook_hash(webhook_payload, client_secret):
status = webhook_payload.get('status', '')
udf5 = webhook_payload.get('udf5', '')
udf4 = webhook_payload.get('udf4', '')
udf3 = webhook_payload.get('udf3', '')
udf2 = webhook_payload.get('udf2', '')
udf1 = webhook_payload.get('udf1', '')
email = webhook_payload.get('email', '')
firstname = webhook_payload.get('firstname', '')
productinfo = webhook_payload.get('productinfo', '')
amount = webhook_payload.get('amount', '')
txnid = webhook_payload.get('txnid', '')
merchant_id = webhook_payload.get('merchant_id', '')
received_hash = webhook_payload.get('hash', '')
hash_string = f"{client_secret}|{status}|||||{udf5}|{udf4}|{udf3}|{udf2}|{udf1}|{email}|{firstname}|{productinfo}|{amount}|{txnid}|{merchant_id}"
computed_hash = hashlib.sha512(hash_string.encode('utf-8')).hexdigest()
return computed_hash.lower() == received_hash.lower()
# Example
is_valid = verify_upi_tpv_webhook_hash(webhook_data, "your_client_secret")
if is_valid:
print("✅ UPI TPV webhook verified — account validation successful")
else:
print("❌ Invalid webhook hash — reject")Java:
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class UPITPVWebhookVerifier {
public static boolean verifyHash(
String status, String udf5, String udf4, String udf3, String udf2, String udf1,
String email, String firstname, String productinfo, String amount,
String txnid, String merchantId, String receivedHash, String clientSecret
) throws NoSuchAlgorithmException {
String hashString = clientSecret + "|" + status + "|||||" +
udf5 + "|" + udf4 + "|" + udf3 + "|" + udf2 + "|" + udf1 + "|" +
email + "|" + firstname + "|" + productinfo + "|" +
amount + "|" + txnid + "|" + merchantId;
MessageDigest md = MessageDigest.getInstance("SHA-512");
byte[] hashBytes = md.digest(hashString.getBytes());
StringBuilder hexString = new StringBuilder();
for (byte b : hashBytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString().equalsIgnoreCase(receivedHash);
}
}Step 3.3: Process Webhook
Python Flask Webhook Handler:
from flask import Flask, request, jsonify
import hashlib
app = Flask(__name__)
@app.route('/partner/webhook/success', methods=['POST'])
def handle_upi_tpv_webhook():
webhook_data = request.json
# Verify hash
if not verify_upi_tpv_webhook_hash(webhook_data, "your_client_secret"):
return jsonify({"error": "Invalid hash"}), 400
# Extract details
txnid = webhook_data.get('txnid')
mihpayid = webhook_data.get('mihpayid')
status = webhook_data.get('status')
bankcode = webhook_data.get('bankcode')
amount = webhook_data.get('amount')
# Verify it's a TPV transaction
if bankcode == "INTTPV":
print(f"✅ UPI TPV Payment: {status} | {txnid} | PayU ID: {mihpayid} | Amount: ₹{amount}")
print(f" Account validation successful - payment from verified beneficiary account")
# Update database
# db.update_payment_status(txnid=txnid, mihpayid=mihpayid, status=status, bankcode=bankcode)
# Respond with 200 OK
return jsonify({"message": "Webhook received"}), 200
if __name__ == '__main__':
app.run(port=5000)Step 4: Verify Payment
Step 4.1: Generate Verify Payment Hash
Hash Formula:
merchant_id|verify_payment|txnid|client_secret
Python:
import hashlib
def generate_verify_hash(merchant_id, txnid, client_secret):
hash_string = f"{merchant_id}|verify_payment|{txnid}|{client_secret}"
return hashlib.sha512(hash_string.encode('utf-8')).hexdigest()
verify_hash = generate_verify_hash(8739528, "TPVUPI20240315001", "your_client_secret")Step 4.2: Call Verify Payment API
Endpoint:
| Environment | URL |
|---|---|
| Test | https://test-partnerapilayer.payu.in/apilayer/partner/verifyPayment |
| Production | https://api.payu.in/partner/verifyPayment |
Request:
curl --location 'https://test-partnerapilayer.payu.in/apilayer/partner/verifyPayment' \
--header 'Authorization: Bearer your_access_token_here' \
--header 'Content-Type: application/json' \
--data '{
"txnid": "TPVUPI20240315001",
"merchant_id": 8739528,
"reseller_id": "11ee-0e7e-5403fde2-9523-0a696b110fde",
"hash": "computed_verify_hash_here"
}'Response:
{
"status": "success",
"unmappedstatus": "captured",
"mihpayid": "30478359671",
"txnid": "TPVUPI20240315001",
"amount": "518.02",
"mode": "UPI",
"bankcode": "INTTPV",
"productinfo": "Loan EMI Payment",
"firstname": "Amit",
"email": "[email protected]",
"phone": "919876543210"
}Step 4.3: Process Verification Response
Reconciliation Checklist:
✅ mihpayid matches
✅ txnid matches
✅ amount matches
✅ status is "success"
✅ unmappedstatus is "captured"
✅ mode is "UPI"
✅ bankcode is "INTTPV" (confirms TPV transaction)
If all match, the beneficiary account validation was successful and payment is confirmed.
Use Cases
Partner Payments UPI TPV is ideal for:
Loan Repayments
Ensure EMI payments come from the borrower's registered account. Prevents fraud where someone else tries to pay on behalf of the borrower.
Vendor Payments
Verify that vendor payments originate from the vendor's verified business account, not personal or third-party accounts.
Refund Collections
Collect refunds specifically to the account that made the original payment, ensuring compliance with refund regulations.
Compliance-Heavy Industries
NBFC, lending, insurance, government payments where regulatory compliance requires verified account transactions.
Error Handling
| Error | Cause | Resolution |
|---|---|---|
s2s_client_ip is mandatory | Missing customer IP when txn_s2s_flow=4 | Capture real customer IP from request headers (check X-Forwarded-For if behind proxy) |
s2s_device_info is mandatory | Missing device user-agent when txn_s2s_flow=4 | Capture device user-agent from HTTP User-Agent header |
beneficiarydetail is mandatory | Missing beneficiary account details for TPV | Include beneficiarydetail JSON string with ifscCode, accountNumber, accountHolderName |
Invalid beneficiarydetail format | beneficiarydetail JSON is malformed | Ensure JSON is properly formatted and contains all three required fields. Send as JSON string, not object |
Account validation failed | Customer's UPI account doesn't match beneficiary details | Payment rejected by PayU. Customer must use the registered account. Inform customer of the required account |
Invalid hash | Hash computation mismatch | Verify NOT including beneficiarydetail in hash. Use client_secret, check 6-pipe sequence, ensure SHA-512 lowercase hex |
Invalid access token | OAuth token expired | Refresh OAuth token. Implement auto-refresh logic |
HMAC validation failure | Webhook hash verification failed | Check reverse hash formula (5 pipes after status). Use case-insensitive comparison |
Testing
Test Environment
API Base URL: https://test-partnerapilayer.payu.in/apilayer/partner
OAuth URLs:
- Auth Code:
https://uat-partner.payu.in/api/v1/merchants/auth_code - Access Token:
https://uat-accounts.payu.in/oauth/token
Test Workflow
- Generate OAuth access token
- Create UPI TPV payment with test beneficiary details
- Verify
intentURIDatais returned - Test UPI app invocation (on mobile device)
- Complete payment using test UPI account matching beneficiary details
- Verify webhook received with
bankcode=INTTPV - Call Verify Payment API
- Reconcile all data points
Validation Checklist
✅ OAuth token generation succeeds
✅ Payment API returns intentURIData
✅ beneficiarydetail sent correctly (not in hash)
✅ UPI app opens with pre-filled details
✅ Account validation succeeds
✅ Test payment succeeds
✅ Webhook received with bankcode=INTTPV
✅ Webhook hash verified
✅ Verify Payment API confirms TPV status
✅ Reconciliation successful
Best Practices
Beneficiary Data Management
- ✅ Validate IFSC code format before sending (11 characters, alphanumeric)
- ✅ Validate account number (typically 9-18 digits)
- ✅ Match account holder name exactly as per bank records
- ✅ Store beneficiary details securely (encrypted database)
- ✅ Implement beneficiary verification during account linking
Security
- ✅ Never include
beneficiarydetailin hash calculation - ✅ Always verify webhook hash before processing
- ✅ Store
client_secretsecurely - ✅ Use HTTPS for all webhook endpoints
- ✅ Encrypt beneficiary account details in database
Reliability
- ✅ Implement idempotency using
txnid - ✅ Use unique
txnidper transaction - ✅ Handle account validation failures gracefully
- ✅ Provide clear error messages to customers
- ✅ Implement retry logic for Verify Payment API
Customer Experience
- ✅ Clearly communicate which account should be used for payment
- ✅ Show beneficiary account details (last 4 digits) before payment
- ✅ Provide helpful error messages if account doesn't match
- ✅ Explain why TPV is required (compliance/security)
- ✅ Allow customers to update registered account if needed
Compliance
- ✅ Maintain audit trail of all TPV transactions
- ✅ Store account validation results securely
- ✅ Implement data retention policies per regulations
- ✅ Ensure GDPR/data privacy compliance for beneficiary data
Next Steps
- Partner Payments Hosted Checkout — Multi-method payment gateway integration
- Partner Payment UPI Intent Integration — Standard UPI S2S without TPV
- Verify Payment API Reference — Complete verification documentation
- Partner Webhook Guide — Advanced webhook patterns
Updated about 1 hour ago
