Hosted Checkout with UPI TPV - Partner Payments
Partner Payments Hosted Checkout with UPI TPV combines the convenience of PayU's multi-payment hosted gateway with the security of UPI Third-Party Verification (TPV). This integration enables you to collect payments through PayU's hosted checkout page while ensuring that UPI payments originate from a specific verified bank account, meeting regulatory compliance and fraud prevention requirements.
Unlike standard Hosted Checkout (which accepts UPI payments from any account) or standalone UPI TPV (which is S2S-only), this hybrid integration allows customers to:
- Be redirected to PayU's hosted checkout page
- Choose from multiple payment methods (cards, UPI, net banking, wallets)
- If UPI is selected, have their account validated against the beneficiary details you provided
- Complete payment only if the UPI account matches the authorized beneficiary account
Key Benefits:
- Multi-method payment support — Cards, UPI (with TPV), net banking, wallets in one integration
- UPI account validation — Ensures UPI payments come from the authorized beneficiary account
- PCI-DSS compliance — PayU handles all card data; you never touch sensitive information
- Regulatory compliance — Meets KYC, anti-money laundering, and beneficiary verification requirements
- Fraud prevention — Prevents UPI payments from unauthorized accounts
- Zero maintenance — PayU manages payment methods, bank integrations, and TPV validation
- Flexible fallback — Customers can use cards/net banking/wallets if UPI account doesn't match
This integration is ideal for:
- Loan repayment platforms requiring EMI collections from borrower's registered account
- NBFC/Lending platforms with regulatory beneficiary verification requirements
- Vendor payment portals ensuring payments from verified business accounts
- Insurance premium collection requiring account validation
- Government payment portals with strict compliance needs
- Multi-tenant platforms serving compliance-heavy industries
How It Works
The Partner Payments Hosted Checkout with UPI TPV flow follows these steps:
-
OAuth Authentication — Obtain an access token with scopes:
create_payment_links,partner_payment_links,partner_payments -
Initiate Payment with Beneficiary Details — POST a payment request with:
- Standard hosted checkout parameters (transaction details, callback URLs)
- Beneficiary account details (
beneficiarydetailJSON string) - Optional: S2S flow parameters if you want UPI-only TPV enforcement
- Computed hash (beneficiarydetail is NOT included in hash)
-
Receive Redirect URL — PayU returns a
redirectUripointing to the hosted checkout page -
Redirect Customer — Customer is redirected to PayU's hosted checkout in their browser
-
Customer Selects Payment Method on hosted page:
- If Cards/Net Banking/Wallets selected: Standard payment flow (no TPV validation)
- If UPI selected: PayU validates customer's UPI account against beneficiary details
-
UPI TPV Validation (when UPI is selected):
- PayU internally sets
bankcode=INTTPVandapi_version=6 - Customer's UPI account is matched against the beneficiary details you provided
- If account matches: Payment proceeds
- If account mismatch: Payment is rejected with validation error
- PayU internally sets
-
Customer Redirected Back — PayU redirects to your success/failure/cancel URL based on outcome
-
Receive Webhook — PayU sends payment status notification to your configured partner webhook URL
- For successful UPI TPV payments:
bankcode: "INTTPV"is included
- For successful UPI TPV payments:
-
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 Feature Enabled — Your account must be enabled for UPI TPV transactions (contact PayU support)
- Beneficiary Account Details for each transaction:
- IFSC code
- Account number
- Account holder name
- Callback URLs Ready:
surl— Success redirect URLfurl— Failure redirect URLcurl— Cancel redirect URL
- Partner Webhook URLs configured in PayU's system (
partner_webhook_success,partner_webhook_failure,partner_webhook_cancelled) - Test Environment Access to
https://test-partnerapilayer.payu.in
Step 1: Generate OAuth Access Token
Partner Payments API requires a 3-step OAuth 2.0 authentication flow to obtain the final Bearer token.
Step 1: Get the Access Token
The auth_code is received on the configured redirect URI. Validate this auth_code using the Validate Auth Code and Client API. If you already have the access_token, skip this step and proceed to Step 2.
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}} |
Sample 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}"
}Send the access token as a bearer token when calling Partner Integration APIs.
Step 2: Initiate Hosted Checkout Payment with UPI TPV
Step 2.1: Prepare Request Parameters
Construct your payment request with transaction details and beneficiary account information.
Request Parameters
Mandatory Parameters
| Parameter | Description | Example |
|---|---|---|
| merchant_id | PayU merchant ID. | 8739528 |
| reseller_id | Partner UUID/reseller ID. | 11ee-0e7e-5403fde2-9523-0a696b110fde |
| txnid | Unique transaction ID (alphanumeric, max 50 chars). | HC_TPV_20240315_001 |
| amount | Transaction amount (decimal, 2 places). | 1500.00 |
| productinfo | Product/service description. | Loan EMI Payment - March 2024 |
| surl | Success redirect URL (HTTPS). | https://yoursite.com/success |
| furl | Failure redirect URL (HTTPS). | https://yoursite.com/failure |
| curl | Cancel redirect URL (HTTPS). | https://yoursite.com/cancel |
| beneficiarydetail | Beneficiary account details as JSON string. Required for TPV transactions. See the Beneficiary Detail Schema below. | See below |
| hash | SHA-512 hash computed as: sha512(key|txnid|amount|productinfo|firstname|email|udf1|udf2|udf3|udf4|udf5||||||salt) | Computed (see Step 2.2) |
Optional Parameters
| Parameter | Description | Example |
|---|---|---|
| firstname | Customer first name. | Rajesh |
| Customer email. | [email protected] | |
| phone | Customer phone (10 digits). | 9876543210 |
| udf1 | User-defined field 1. | session_12345 |
| udf2 | User-defined field 2. | 1370625260 |
| udf3 | User-defined field 3. | loan-ref-ABC123 |
| udf4 | User-defined field 4. | |
| udf5 | User-defined field 5. |
Beneficiary Detail Schema:
The beneficiarydetail parameter must be a JSON string containing the authorized beneficiary account details:
{
"ifscCode": "ICIC0001234",
"accountNumber": "123456789012",
"accountHolderName": "RAJESH KUMAR"
}Mandatory Parameters
| Parameter | Description | Example |
|---|---|---|
| ifscCode | 11-character IFSC code of beneficiary's bank. | ICIC0001234 |
| accountNumber | Beneficiary's bank account number. | 123456789012 |
| accountHolderName | Account holder name (as per bank records). | RAJESH KUMAR |
Example Request Body:
{
"merchant_id": "8739528",
"reseller_id": "11ee-0e7e-5403fde2-9523-0a696b110fde",
"txnid": "HC_TPV_20240315_001",
"amount": "1500.00",
"productinfo": "Loan EMI Payment - March 2024",
"firstname": "Rajesh",
"email": "[email protected]",
"phone": "9876543210",
"surl": "https://yoursite.com/success",
"furl": "https://yoursite.com/failure",
"curl": "https://yoursite.com/cancel",
"udf1": "session_12345",
"udf2": "1370625260",
"udf3": "loan-ref-ABC123",
"udf4": "",
"udf5": "whatsapp",
"beneficiarydetail": "{\"ifscCode\":\"ICIC0001234\",\"accountNumber\":\"123456789012\",\"accountHolderName\":\"RAJESH KUMAR\"}",
"hash": "<COMPUTED_HASH>"
}Step 2.2: Generate Payment Request Hash
Compute the SHA-512 hash for request authentication.
Hash Formula:
merchant_id|txnid|amount|productinfo|firstname|email|udf1|udf2|udf3|udf4|udf5||||||client_secret
Step-by-Step Hash Generation:
- Concatenate fields with pipe separators (in exact order above)
- Use empty strings for missing/empty fields (resulting in consecutive pipes)
- Add six pipes between
udf5andclient_secret - Compute SHA-512 digest
- Convert to lowercase hexadecimal
Example Hash String:
8739528|HC_TPV_20240315_001|1500.00|Loan EMI Payment - March 2024|Rajesh|[email protected]|session_12345|1370625260|loan-ref-ABC123||whatsapp||||||YOUR_CLIENT_SECRET
Sample Hash Generation Code
import hashlib
def generate_payment_hash(merchant_id, txnid, amount, productinfo, firstname, email, udf1, udf2, udf3, udf4, udf5, client_secret):
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 usage
payment_hash = generate_payment_hash(
merchant_id=8739528,
txnid="PPHOST20240315001",
amount="1500.00",
productinfo="Premium Subscription - Monthly",
firstname="Priya",
email="[email protected]",
udf1="subscription_plan_premium",
udf2="monthly_billing",
udf3="",
udf4="",
udf5="partner_web_checkout",
client_secret="your_client_secret_here"
)
print(f"Payment Hash: {payment_hash}")import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class HostedCheckoutHashGenerator {
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 {
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
function generateHostedCheckoutHash($merchantId, $txnid, $amount, $productinfo,
$firstname, $email, $udf1, $udf2, $udf3,
$udf4, $udf5, $clientSecret) {
$hashString = $merchantId . "|" . $txnid . "|" . $amount . "|" .
$productinfo . "|" . $firstname . "|" . $email . "|" .
$udf1 . "|" . $udf2 . "|" . $udf3 . "|" . $udf4 . "|" .
$udf5 . "||||||" . $clientSecret;
return hash('sha512', $hashString);
}
// Example
$hash = generateHostedCheckoutHash(
8739528,
"PPHOST20240315001",
"1500.00",
"Premium Subscription - Monthly",
"Priya",
"[email protected]",
"subscription_plan_premium",
"monthly_billing",
"",
"",
"partner_web_checkout",
"your_client_secret_here"
);
echo "Payment Hash: " . $hash;
?>Step 2.3: POST the Payment Request
Send the payment request to PayU's Partner Payments API.
Endpoint:
| Environment | URL |
|---|---|
| Test | https://test-partnerapilayer.payu.in/apilayer/partner/payments |
| Production | https://api.payu.in/partner/payments |
Headers:
Content-Type: application/json
Authorization: Bearer <FINAL_ACCESS_TOKEN>
Sample Request:
curl --location 'https://test-partnerapilayer.payu.in/apilayer/partner/payments' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...' \
--data-raw '{
"merchant_id": "8739528",
"reseller_id": "11ee-0e7e-5403fde2-9523-0a696b110fde",
"txnid": "HC_TPV_20240315_001",
"amount": "1500.00",
"productinfo": "Loan EMI Payment - March 2024",
"firstname": "Rajesh",
"email": "[email protected]",
"phone": "9876543210",
"surl": "https://yoursite.com/success",
"furl": "https://yoursite.com/failure",
"curl": "https://yoursite.com/cancel",
"udf1": "session_12345",
"udf2": "1370625260",
"udf3": "loan-ref-ABC123",
"udf4": "",
"udf5": "whatsapp",
"beneficiarydetail": "{\"ifscCode\":\"ICIC0001234\",\"accountNumber\":\"123456789012\",\"accountHolderName\":\"RAJESH KUMAR\"}",
"hash": "a1b2c3d4e5f6789..."
}'import requests
import json
url = "https://test-partnerapilayer.payu.in/apilayer/partner/payments"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {final_access_token}"
}
beneficiary_details = {
"ifscCode": "ICIC0001234",
"accountNumber": "123456789012",
"accountHolderName": "RAJESH KUMAR"
}
payload = {
"merchant_id": "8739528",
"reseller_id": "11ee-0e7e-5403fde2-9523-0a696b110fde",
"txnid": "HC_TPV_20240315_001",
"amount": "1500.00",
"productinfo": "Loan EMI Payment - March 2024",
"firstname": "Rajesh",
"email": "[email protected]",
"phone": "9876543210",
"surl": "https://yoursite.com/success",
"furl": "https://yoursite.com/failure",
"curl": "https://yoursite.com/cancel",
"udf1": "session_12345",
"udf2": "1370625260",
"udf3": "loan-ref-ABC123",
"udf4": "",
"udf5": "whatsapp",
"beneficiarydetail": json.dumps(beneficiary_details),
"hash": payment_hash
}
response = requests.post(url, headers=headers, json=payload)
print(f"Status Code: {response.status_code}")
print(f"Response: {response.json()}")
if response.status_code == 200:
redirect_uri = response.json().get("redirectUri")
print(f"Redirect URI: {redirect_uri}")import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import org.json.JSONObject;
public class InitiateHostedCheckoutTPV {
public static void main(String[] args) throws Exception {
String url = "https://test-partnerapilayer.payu.in/apilayer/partner/payments";
JSONObject beneficiaryDetail = new JSONObject();
beneficiaryDetail.put("ifscCode", "ICIC0001234");
beneficiaryDetail.put("accountNumber", "123456789012");
beneficiaryDetail.put("accountHolderName", "RAJESH KUMAR");
JSONObject payload = new JSONObject();
payload.put("merchant_id", "8739528");
payload.put("reseller_id", "11ee-0e7e-5403fde2-9523-0a696b110fde");
payload.put("txnid", "HC_TPV_20240315_001");
payload.put("amount", "1500.00");
payload.put("productinfo", "Loan EMI Payment - March 2024");
payload.put("firstname", "Rajesh");
payload.put("email", "[email protected]");
payload.put("phone", "9876543210");
payload.put("surl", "https://yoursite.com/success");
payload.put("furl", "https://yoursite.com/failure");
payload.put("curl", "https://yoursite.com/cancel");
payload.put("udf1", "session_12345");
payload.put("udf2", "1370625260");
payload.put("udf3", "loan-ref-ABC123");
payload.put("udf4", "");
payload.put("udf5", "whatsapp");
payload.put("beneficiarydetail", beneficiaryDetail.toString());
payload.put("hash", paymentHash);
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + finalAccessToken)
.POST(HttpRequest.BodyPublishers.ofString(payload.toString()))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("Status Code: " + response.statusCode());
System.out.println("Response: " + response.body());
if (response.statusCode() == 200) {
JSONObject responseJson = new JSONObject(response.body());
String redirectUri = responseJson.getString("redirectUri");
System.out.println("Redirect URI: " + redirectUri);
}
}
}<?php
$url = "https://test-partnerapilayer.payu.in/apilayer/partner/payments";
$beneficiaryDetails = array(
"ifscCode" => "ICIC0001234",
"accountNumber" => "123456789012",
"accountHolderName" => "RAJESH KUMAR"
);
$payload = array(
"merchant_id" => "8739528",
"reseller_id" => "11ee-0e7e-5403fde2-9523-0a696b110fde",
"txnid" => "HC_TPV_20240315_001",
"amount" => "1500.00",
"productinfo" => "Loan EMI Payment - March 2024",
"firstname" => "Rajesh",
"email" => "[email protected]",
"phone" => "9876543210",
"surl" => "https://yoursite.com/success",
"furl" => "https://yoursite.com/failure",
"curl" => "https://yoursite.com/cancel",
"udf1" => "session_12345",
"udf2" => "1370625260",
"udf3" => "loan-ref-ABC123",
"udf4" => "",
"udf5" => "whatsapp",
"beneficiarydetail" => json_encode($beneficiaryDetails),
"hash" => $paymentHash
);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
"Content-Type: application/json",
"Authorization: Bearer " . $finalAccessToken
));
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
echo "Status Code: " . $httpCode . "\n";
echo "Response: " . $response . "\n";
if ($httpCode == 200) {
$data = json_decode($response, true);
$redirectUri = $data['redirectUri'];
echo "Redirect URI: " . $redirectUri . "\n";
}
?>Step 2.4: Handle Payment Response & Redirect Customer
Success Response:
{
"status": "success",
"redirectUri": "https://secure.payu.in/_payment?token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"txnid": "HC_TPV_20240315_001",
"merchant_id": "8739528"
}Response Fields:
| Field | Description |
|---|---|
| status | Request status ("success" or "failure") |
| redirectUri | PayU hosted checkout URL (redirect customer here immediately) |
| txnid | Transaction ID from request |
| merchant_id | Merchant ID from request |
Next Steps:
- Extract
redirectUrifrom the response - Immediately redirect the customer to this URL in their browser:
window.location.href = redirectUri; - Customer lands on PayU's hosted checkout page
- Customer sees payment method options (cards, UPI, net banking, wallets)
- If customer selects UPI:
- PayU internally activates TPV mode (
bankcode=INTTPV,api_version=6) - Customer completes UPI authentication
- PayU validates customer's UPI account against
beneficiarydetail - Payment succeeds only if account matches
- PayU internally activates TPV mode (
- If customer selects Cards/Net Banking/Wallets:
- Standard payment flow (no TPV validation)
- Payment proceeds normally
Step 3: Customer Completes Payment on Hosted Checkout
Hosted Checkout Flow with TPV
-
Customer lands on PayU's hosted checkout page
- Merchant branding displayed (logo, colors)
- Transaction details shown (amount, product description)
- Payment method options presented
-
Customer selects payment method:
Option A: UPI Selected (TPV Validation Applies)
- Customer chooses UPI payment
- PayU presents UPI app selection or VPA entry
- Customer authenticates with UPI PIN
- PayU validates UPI account against beneficiarydetail:
- If account matches: Payment succeeds
- If account mismatch: Error message displayed, payment fails
Option B: Card/Net Banking/Wallet Selected (No TPV)
- Standard payment authentication
- 3D Secure OTP for cards
- Bank login for net banking
- Wallet PIN/OTP for wallets
- Payment proceeds without account validation
-
Payment completion
- PayU processes the transaction
- Transaction status determined (success/failure)
-
Customer redirect
- Success → Redirected to
surl - Failure → Redirected to
furl - Cancel → Redirected to
curl
- Success → Redirected to
Step 4: Receive Callback on Success/Failure/Cancel URL
When PayU redirects the customer to your callback URL, transaction details are appended as POST parameters.
Success URL (surl) Parameters
Expected POST Parameters:
| Parameter | Description | Example |
|---|---|---|
| mihpayid | PayU transaction ID | "403993715529111111" |
| txnid | Your transaction ID | "HC_TPV_20240315_001" |
| status | Transaction status | "success" |
| amount | Transaction amount | "1500.00" |
| productinfo | Product description | "Loan EMI Payment - March 2024" |
| firstname | Customer first name | "Rajesh" |
| Customer email | "[email protected]" | |
| phone | Customer phone | "9876543210" |
| mode | Payment mode | "UPI" (for TPV), "CC" (card), "NB" (net banking) |
| bankcode | Bank code | "INTTPV" (for successful UPI TPV payments) |
| unmappedstatus | Payment status | "captured" (success), "failed", "bounced" |
| hash | Response hash | SHA-512 hash for verification |
| udf1-udf5 | User-defined fields | Values from request |
Sample Success Callback (POST to surl):
mihpayid=403993715529111111
txnid=HC_TPV_20240315_001
status=success
amount=1500.00
productinfo=Loan EMI Payment - March 2024
firstname=Rajesh
[email protected]
phone=9876543210
mode=UPI
bankcode=INTTPV
unmappedstatus=captured
hash=a1b2c3d4e5f6...
udf1=session_12345
udf2=1370625260
udf3=loan-ref-ABC123
udf4=
udf5=whatsapp
Failure URL (furl) Parameters
When payment fails (including UPI TPV account mismatch), customer is redirected to furl with:
mihpayid=403993715529222222
txnid=HC_TPV_20240315_001
status=failure
amount=1500.00
error=TPV_ACCOUNT_MISMATCH
error_Message=Beneficiary account validation failed
mode=UPI
bankcode=INTTPV
unmappedstatus=bounced
hash=x1y2z3...
Common TPV Failure Reasons:
| Error Code | Description | Resolution |
|---|---|---|
TPV_ACCOUNT_MISMATCH | Customer's UPI account doesn't match beneficiary details | Verify beneficiary account details, ask customer to pay from correct account |
TPV_VALIDATION_FAILED | Beneficiary details validation error | Check IFSC code, account number format |
ACCOUNT_HOLDER_NAME_MISMATCH | Name on UPI account doesn't match accountHolderName | Ensure exact name match (case-insensitive, spaces ignored) |
Cancel URL (curl) Parameters
When customer cancels payment:
mihpayid=
txnid=HC_TPV_20240315_001
status=cancel
amount=1500.00
unmappedstatus=userCancelled
Step 5: Receive and Verify Partner Webhook
PayU sends real-time payment status notifications to your configured partner webhook URLs.
Step 5.1: Partner Webhook Delivery
Webhook URLs (configured in PayU system):
partner_webhook_success— Triggered on successful paymentpartner_webhook_failure— Triggered on failed paymentpartner_webhook_cancelled— Triggered when customer cancels
Webhook Payload (POST request):
{
"mihpayid": "403993715529111111",
"txnid": "HC_TPV_20240315_001",
"status": "success",
"amount": "1500.00",
"productinfo": "Loan EMI Payment - March 2024",
"firstname": "Rajesh",
"email": "[email protected]",
"phone": "9876543210",
"mode": "UPI",
"bankcode": "INTTPV",
"unmappedstatus": "captured",
"merchant_id": "8739528",
"udf1": "session_12345",
"udf2": "1370625260",
"udf3": "loan-ref-ABC123",
"udf4": "",
"udf5": "whatsapp",
"hash": "a1b2c3d4e5f6...",
"payment_source": "payu"
}Key TPV Fields in Webhook:
| Field | Value for TPV | Description |
|---|---|---|
| mode | "UPI" | Payment method used |
| bankcode | "INTTPV" | Confirms TPV validation passed |
| unmappedstatus | "captured" (success) or "bounced" (failure) | Final payment status |
Step 5.2: Verify Webhook Hash
Always verify the webhook hash before processing payment status.
Reverse Hash Formula:
client_secret|status||||||udf5|udf4|udf3|udf2|udf1|email|firstname|productinfo|amount|txnid|merchant_id
Notes:
- Five pipes after
status - Fields in reverse order compared to request hash
- Use OAuth
client_secret(same as request hash)
Reverse Hash Formula:
client_secret|status|||||udf5|udf4|udf3|udf2|udf1|email|firstname|productinfo|amount|txnid|merchant_id
Sample Verification Payload
import hashlib
def verify_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_webhook_hash(webhook_data, "your_client_secret")
if is_valid:
print("✅ Webhook verified — safe to process")
else:
print("❌ Invalid webhook hash — reject")import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class WebhookVerifier {
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 6: Verify Payment Status
Always call the Verify Payment API as the final source of truth for transaction status.
Endpoint:
| Environment | URL |
|---|---|
| Test | https://test-partnerapilayer.payu.in/apilayer/partner/verifyPayment |
| Production | https://api.payu.in/partner/verifyPayment |
Headers:
Content-Type: application/json
Authorization: Bearer <FINAL_ACCESS_TOKEN>
Request Body:
{
"merchant_id": "8739528",
"txnid": "HC_TPV_20240315_001"
}Sample Request:
curl --location 'https://test-partnerapilayer.payu.in/apilayer/partner/verifyPayment' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...' \
--data-raw '{
"merchant_id": "8739528",
"txnid": "HC_TPV_20240315_001"
}'import requests
url = "https://test-partnerapilayer.payu.in/apilayer/partner/verifyPayment"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {final_access_token}"
}
payload = {
"merchant_id": "8739528",
"txnid": "HC_TPV_20240315_001"
}
response = requests.post(url, headers=headers, json=payload)
if response.status_code == 200:
verify_data = response.json()
print(f"Payment Status: {verify_data.get('status')}")
print(f"Bank Code: {verify_data.get('bankcode')}")
print(f"Amount: {verify_data.get('amount')}")
print(f"PayU ID: {verify_data.get('mihpayid')}")
if verify_data.get('bankcode') == 'INTTPV':
print("✅ UPI TPV payment confirmed")Success Response:
{
"status": "success",
"mihpayid": "403993715529111111",
"txnid": "HC_TPV_20240315_001",
"amount": "1500.00",
"productinfo": "Loan EMI Payment - March 2024",
"firstname": "Rajesh",
"email": "[email protected]",
"phone": "9876543210",
"mode": "UPI",
"bankcode": "INTTPV",
"unmappedstatus": "captured",
"payment_source": "payu",
"merchant_id": "8739528"
}Response Fields for UPI TPV:
| Field | Value | Description |
|---|---|---|
| status | "success" | Payment succeeded |
| bankcode | "INTTPV" | Confirms UPI TPV validation passed |
| mode | "UPI" | Payment method |
| unmappedstatus | "captured" | Payment captured successfully |
| mihpayid | PayU transaction ID | Unique PayU reference |
Testing Hosted Checkout with UPI TPV
Test Environment Setup
Use UAT credentials and test beneficiary account details provided by PayU.
Test Scenarios
Scenario 1: UPI TPV Success (Account Match)
Test Flow:
- Initiate payment with test beneficiary details
- Redirect customer to hosted checkout
- Customer selects UPI
- Customer pays from UPI account linked to test beneficiary account
- Expected: Payment succeeds,
bankcode=INTTPVin webhook
Expected Results:
- ✅ Webhook received with
status=success,bankcode=INTTPV - ✅ Verify Payment API confirms
unmappedstatus=captured - ✅ Customer redirected to
surl
Scenario 2: UPI TPV Failure (Account Mismatch)
Test Flow:
- Initiate payment with test beneficiary details
- Redirect customer to hosted checkout
- Customer selects UPI
- Customer pays from different UPI account (not linked to beneficiary account)
- Expected: Payment fails with TPV validation error
Expected Results:
- ❌ Webhook received with
status=failure,error=TPV_ACCOUNT_MISMATCH - ❌ Verify Payment API confirms
unmappedstatus=bounced - ❌ Customer redirected to
furl
Scenario 3: Card Payment (No TPV Validation)
Test Flow:
- Initiate payment with beneficiary details included
- Redirect customer to hosted checkout
- Customer selects Credit Card
- Customer completes card payment
- Expected: Payment succeeds without TPV validation
Expected Results:
- ✅ Webhook received with
status=success,mode=CC,bankcode≠INTTPV - ✅ TPV validation bypassed for card payments
- ✅ Customer redirected to
surl
Test Cards:
| Card Number | Expiry | CVV | Expected Result |
|---|---|---|---|
| 5123456789012346 | 05/2026 | 123 | Success |
| 4012001037141112 | 12/2025 | 123 | Success |
| 6011111111111117 | 06/2027 | 999 | Failure |
Scenario 4: Net Banking (No TPV Validation)
Test Flow:
- Initiate payment with beneficiary details
- Customer selects Net Banking
- Customer completes net banking authentication
- Expected: Payment succeeds without TPV validation
Expected Results:
- ✅ Webhook received with
status=success,mode=NB - ✅ TPV validation bypassed for net banking
- ✅ Customer redirected to
surl
Reconciliation
Daily Reconciliation Checklist
For each UPI TPV transaction:
- Match
txnidbetween your system, webhook, and Verify Payment API - Confirm
mihpayid(PayU transaction ID) is consistent - Verify
amountmatches original request - Check
bankcode=INTTPVfor UPI TPV transactions - Validate
unmappedstatus=capturedfor successful payments - Cross-reference with PayU dashboard settlement reports
- Flag any discrepancies for manual review
Key Reconciliation Fields
| Field | Source | Use |
|---|---|---|
| txnid | Your system | Primary key for matching |
| mihpayid | PayU | PayU's unique reference |
| amount | Request vs response | Amount verification |
| bankcode | Webhook/Verify API | TPV confirmation (must be INTTPV) |
| unmappedstatus | Webhook/Verify API | Final payment status |
Common Errors and Troubleshooting
Error: Invalid hash
Cause: Hash mismatch between request and PayU's computed hash
Resolution:
- Verify hash formula (6 pipes between
udf5andclient_secret) - Ensure
beneficiarydetailis NOT included in hash - Use OAuth
client_secret(not merchant salt) - Check for empty fields (use empty strings, not null)
- Ensure SHA-512 lowercase hexadecimal output
Error: TPV_ACCOUNT_MISMATCH
Cause: Customer's UPI account doesn't match beneficiary details
Resolution:
- Verify
beneficiarydetailIFSC code is correct - Check
accountNumberformat (no spaces or special characters) - Ensure
accountHolderNamematches exactly (case-insensitive) - Ask customer to pay from the registered beneficiary account
- For testing: Use test beneficiary accounts provided by PayU
Error: Beneficiary detail validation failed
Cause: Invalid beneficiary account details format
Resolution:
- Check IFSC code is 11 characters (e.g.,
ICIC0001234) - Verify account number is numeric
- Ensure
beneficiarydetailis a valid JSON string - Confirm account holder name matches bank records
- Test with PayU-provided test beneficiary data first
Error: UPI TPV feature not enabled
Cause: Your merchant account is not enabled for UPI TPV
Resolution:
- Contact PayU support to enable UPI TPV feature
- Provide your
merchant_idandreseller_id - Specify use case and compliance requirements
- Wait for confirmation before testing
Error: Auth token is not valid
Cause: OAuth token expired or invalid
Resolution:
- Regenerate OAuth token (complete 3-step flow)
- Verify token has required scopes (
partner_payments) - Check token expiry (default: 3600 seconds)
- Ensure Authorization header format:
Bearer <token>
Going Live Checklist
Before switching to production:
Credentials Update
- Production OAuth
client_idandclient_secretobtained - Production
merchant_idandreseller_idconfigured - Production endpoints updated in code
- UPI TPV feature confirmed enabled in production
Beneficiary Data
- Real beneficiary account details collection process in place
- IFSC code validation implemented
- Account holder name normalization logic added
- Beneficiary data storage secured (encrypted at rest)
Testing Complete
- All test scenarios passed in UAT
- UPI TPV success and failure flows tested
- Card/net banking bypass confirmed
- Webhook hash verification working
- Verify Payment API integration tested
Compliance
- Legal review of TPV usage completed
- Customer consent for beneficiary validation obtained
- Privacy policy updated with account validation disclosure
- Audit trail for TPV transactions in place
Production Validation
- Conduct live transaction with small amount
- Verify production webhook delivery
- Confirm production Verify Payment API works
- Check production settlement in PayU dashboard
Related Documentation
- Partner Payments Overview
- Partner Payments UPI Intent Integration
- Partner Payments UPI TPV Integration
- Partner Payments Hosted Checkout Integration
- Testing and Troubleshooting Guide
- OAuth Authentication Guide
- Verify Payment API
Support
For UPI TPV feature enablement, test beneficiary accounts, or technical issues, contact PayU Partner Support with:
- Your
reseller_id(partner UUID) - Merchant ID(s) involved
- Detailed use case description
- Sample
txnidand timestamp (for transaction issues) - Error messages and logs
Support Channels:
- Partner Portal: https://partner.payu.in/support
- Email: [email protected]
Updated 13 days ago
