UPIQRPay Integration Guide
UPIQRPay provides enterprise Zero-MDR direct settlement payment gateway infrastructure. Using our APIs, you can generate dynamic UPI QR codes, sanitized 1-tap mobile deep links that eliminate bank "Risk Policy" warnings, and automatically reconcile customer settlements with cryptographic webhooks.
Authentication & Security
All order creation and management endpoints authenticate requests using a Merchant API Key passed in the Authorization HTTP header.
Authorization: Bearer upiqr_live_your_merchant_api_key_here
You can inspect or regenerate your production API key anytime under Dashboard ➔ API & Webhooks.
Standard Response Structure
All responses from the UPIQRPay API are formatted as standard JSON with an envelope containing success, data or message, and error codes when applicable.
{
"success": true,
"data": { ... },
"message": "Operation successful"
}
{
"success": false,
"message": "Invalid API Key or expired",
"error_code": "UNAUTHORIZED"
}
/api/v1/order/create
Creates a dynamic UPI payment session. Generates a unique 12-character payment ID, calculates collision-free payable amount, generates Base64 QR code image, and provides sanitized Risk-Policy bypass deep links.
Request Headers & Body
| Field | Type | Required | Description |
|---|---|---|---|
| amount | Number | Yes | Order amount in INR (min 1.00). |
| order_id | String | Yes | Your custom merchant order/invoice reference. |
| customer_name | String | Optional | Customer's full name. |
| customer_email | String | Optional | Customer's email address. |
| redirect_url | String | Optional | URL to return customer to upon successful payment. |
curl -X POST http://localhost:5001/api/v1/order/create \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_MERCHANT_API_KEY" \
-d '{
"amount": 299.00,
"order_id": "INV_881923",
"customer_name": "Vikram Singh",
"customer_email": "vikram@example.com",
"redirect_url": "https://yourstore.com/thankyou"
}'
const axios = require('axios');
async function createPaymentOrder() {
const response = await axios.post('http://localhost:5001/api/v1/order/create', {
amount: 299.00,
order_id: 'INV_881923',
customer_name: 'Vikram Singh',
customer_email: 'vikram@example.com',
redirect_url: 'https://yourstore.com/thankyou'
}, {
headers: { 'Authorization': 'Bearer YOUR_MERCHANT_API_KEY' }
});
const { payment_id, pay_url, intent_links } = response.data.data;
console.log(`Order created: ${payment_id}`);
console.log(`Hosted Checkout URL: ${pay_url}`);
}
<?php
$payload = json_encode([
'amount' => 299.00,
'order_id' => 'INV_881923',
'customer_name' => 'Vikram Singh',
'customer_email' => 'vikram@example.com',
'redirect_url' => 'https://yourstore.com/thankyou'
]);
$ch = curl_init('http://localhost:5001/api/v1/order/create');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer YOUR_MERCHANT_API_KEY'
]);
$response = curl_exec($ch);
curl_close($ch);
$order = json_decode($response, true);
header('Location: ' . $order['data']['pay_url']);
exit;
import requests
url = "http://localhost:5001/api/v1/order/create"
headers = {
"Authorization": "Bearer YOUR_MERCHANT_API_KEY",
"Content-Type": "application/json"
}
payload = {
"amount": 299.00,
"order_id": "INV_881923",
"customer_name": "Vikram Singh",
"redirect_url": "https://yourstore.com/thankyou"
}
response = requests.post(url, json=payload, headers=headers)
order_data = response.json()
print("Pay URL:", order_data["data"]["pay_url"])
{
"success": true,
"message": "Order session created successfully",
"data": {
"payment_id": "UPG4A8C91E5B20F",
"order_id": "INV_881923",
"amount": 299.00,
"payable_amount": 299.04,
"status": "pending",
"qr_code": "data:image/png;base64,iVBORw0KGgoAAA...",
"upi_link": "upi://pay?pa=paytmqr...@paytm&pn=Store&am=299.04&tr=UPG4A8C91E5B20F&tn=UPG4A8C91E5B20F&cu=INR",
"pay_url": "http://localhost:5001/pay/UPG4A8C91E5B20F",
"intent_links": {
"paytm": "paytmmp://cash_wallet?pa=paytmqr...@paytm&pn=Store&am=299.04&cu=INR&tn=UPG4A8C91E5B20F&featuretype=money_transfer",
"phonepe": "phonepe://pay?pa=paytmqr...@paytm&pn=Store&am=299.04&cu=INR&tn=UPG4A8C91E5B20F",
"gpay": "tez://upi/pay?pa=paytmqr...@paytm&pn=Store&am=299.04&cu=INR&tn=UPG4A8C91E5B20F"
},
"expires_at": "2026-10-27T12:05:00.000Z"
}
}
/api/v1/order/status/:payment_id
Polls or checks the live settlement state of a transaction. Automatically triggers real-time reconciliation against the connected provider APIs if the order is still pending.
{
"success": true,
"data": {
"payment_id": "UPG4A8C91E5B20F",
"order_id": "INV_881923",
"payable_amount": 299.04,
"status": "success",
"utr_number": "428190019283",
"paid_at": "2026-10-27 11:58:34"
}
}
Webhooks & Signature Verification
Whenever a customer completes a payment, UPIQRPay sends an instantaneous HTTP POST request to your configured webhook URL. Every webhook includes an X-UPIQRPay-Signature header containing an HMAC-SHA256 signature calculated using your secret key.
{
"event": "payment.success",
"timestamp": "2026-10-27T11:58:34.000Z",
"data": {
"payment_id": "UPG4A8C91E5B20F",
"order_id": "INV_881923",
"amount": 299.00,
"payable_amount": 299.04,
"status": "success",
"utr_number": "428190019283",
"paid_at": "2026-10-27 11:58:34"
}
}
How to Verify Signatures on Your Server
const crypto = require('crypto');
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-upiqrpay-signature']; // "sha256=..."
const secret = process.env.UPIQRPAY_WEBHOOK_SECRET;
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(req.body).digest('hex');
if (signature !== expected) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
if (event.event === 'payment.success') {
console.log(`Payment confirmed for Order: ${event.data.order_id}, UTR: ${event.data.utr_number}`);
}
res.status(200).send('OK');
});
$rawPayload = file_get_contents('php://input');
$signatureHeader = $_SERVER['HTTP_X_UPIQRPAY_SIGNATURE'] ?? '';
$secret = getenv('UPIQRPAY_WEBHOOK_SECRET');
$expectedSignature = 'sha256=' . hash_hmac('sha256', $rawPayload, $secret);
if (!hash_equals($expectedSignature, $signatureHeader)) {
http_response_code(401);
exit('Invalid signature');
}
$event = json_decode($rawPayload, true);
// Fulfill customer order in database...
http_response_code(200);
echo 'OK';
Android Soundbox & Notification Reader API
POST /api/v1/webhook/soundbox-reader
The Soundbox Reader endpoint enables Android companion devices, MacroDroid, Tasker, or Soundbox APKs to push incoming payment notifications directly to your UPIQRPay gateway. The engine automatically parses amounts and 12-digit bank UTR numbers, matches pending merchant orders, updates payment status to SUCCESS, and fires signed HMAC webhooks.
- Header:
Authorization: Bearer YOUR_API_KEY - Header:
X-API-KEY: YOUR_API_KEY - Query parameter:
?api_key=YOUR_API_KEY - Body property:
"api_key": "YOUR_API_KEY"
{
"package_name": "net.one97.paytm",
"title": "Payment of Rs. 150.01 received",
"text": "Received Rs. 150.01 successfully on Paytm for Business. UPI Ref no 429811998877"
}
{
"success": true,
"matched": true,
"message": "Payment notification verified successfully and order completed!",
"data": {
"matched": true,
"payment_id": "UPG68EE2C57C74B",
"order_id": "ORD_1002",
"amount": 150.01,
"utr": "429811998877",
"provider": "paytm",
"status": "success"
}
}
Android Kotlin Risk-Policy Bypass Kit
Zero Risk-Policy Warnings • Seamless Auto-Return to App
The Problem: When launching generic upi://pay?tr=... links, Paytm, PhonePe, and Google Pay show a red banner stating "Risk Policy Error: Unverified Transaction" because the merchant is not a multi-crore enterprise signed banking aggregator.
Our Solution: By transforming links into paytmmp://cash_wallet and placing the payment ID in tn (Transaction Note) while omitting tr, the UPI apps recognize it as a verified peer-to-merchant payment without raising security alarms!
package com.yourcompany.app.payment
import android.content.Context
import android.content.Intent
import android.net.Uri
object UpiPaymentHelper {
/**
* Launches Paytm with Sanitized Cash Wallet Scheme to bypass banking risk policies.
*/
fun openPaytmBypass(
context: Context,
upiVpa: String,
merchantName: String,
amount: Double,
paymentId: String
): Boolean {
val sanitizedUri = Uri.parse(
"paytmmp://cash_wallet?pa=$upiVpa&pn=${Uri.encode(merchantName)}&am=$amount&cu=INR&tn=$paymentId&featuretype=money_transfer"
)
val intent = Intent(Intent.ACTION_VIEW, sanitizedUri).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
}
return try {
context.startActivity(intent)
true
} catch (e: Exception) {
false
}
}
/**
* Launches PhonePe or Google Pay with sanitized intent note.
*/
fun openPhonePeOrGPay(context: Context, deepLink: String): Boolean {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(deepLink)).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
}
return try {
context.startActivity(intent)
true
} catch (e: Exception) {
false
}
}
}
package com.yourcompany.app.payment
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.appcompat.app.AppCompatActivity
class PaymentActivity : AppCompatActivity() {
private lateinit var webView: WebView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
webView = WebView(this).apply {
settings.javaScriptEnabled = true
settings.domStorageEnabled = true
webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
val url = request?.url?.toString() ?: return false
// 1. Intercept Paytm, PhonePe, GPay, and BHIM UPI schemes
if (url.startsWith("paytmmp://") || url.startsWith("phonepe://") ||
url.startsWith("tez://") || url.startsWith("upi://")) {
try {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply {
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
}
startActivity(intent)
return true
} catch (e: Exception) {
return false // Fall back to rendering hosted QR in WebView
}
}
// 2. Intercept Success Callback to bring app back to foreground automatically
if (url.contains("status=success")) {
val returnIntent = Intent().apply {
putExtra("EXTRA_PAYMENT_STATUS", "SUCCESS")
putExtra("EXTRA_UTR", request.url?.getQueryParameter("utr"))
}
setResult(Activity.RESULT_OK, returnIntent)
finish() // Auto-close checkout & resume merchant app
return true
}
return false
}
}
}
setContentView(webView)
// Load the hosted pay_url returned by /api/v1/order/create
val payUrl = intent.getStringExtra("EXTRA_PAY_URL") ?: return finish()
webView.loadUrl(payUrl)
}
}
PHP & WooCommerce Gateway Snippet
Drop-in WordPress / WooCommerce Payment Gateway
<?php
/**
* Plugin Name: UPIQRPay Zero-MDR UPI Gateway for WooCommerce
* Description: Direct Bank Settlements via Paytm & BharatPe with 0% Fee.
* Version: 1.0.0
*/
if (!defined('ABSPATH')) exit;
add_action('plugins_loaded', 'init_upiqrpay_gateway');
function init_upiqrpay_gateway() {
if (!class_exists('WC_Payment_Gateway')) return;
class WC_Gateway_UPIQRPay extends WC_Payment_Gateway {
public function __construct() {
$this->id = 'upiqrpay';
$this->method_title = '0% Fee UPI Gateway (Paytm/BharatPe)';
$this->method_description = 'Accept direct bank settlements via scannable QR and 1-tap mobile apps.';
$this->has_fields = false;
$this->init_form_fields();
$this->init_settings();
$this->title = $this->get_option('title', 'UPI / QR Code (0% Extra Fee)');
$this->api_key = $this->get_option('api_key');
$this->webhook_secret = $this->get_option('webhook_secret');
add_action('woocommerce_update_options_payment_gateways_' . $this->id, array($this, 'process_admin_options'));
add_action('woocommerce_api_upiqrpay_webhook', array($this, 'handle_webhook'));
}
public function init_form_fields() {
$this->form_fields = array(
'enabled' => array('title' => 'Enable/Disable', 'type' => 'checkbox', 'label' => 'Enable UPIQRPay', 'default' => 'yes'),
'title' => array('title' => 'Title', 'type' => 'text', 'default' => 'Pay with UPI (GPay, PhonePe, Paytm)'),
'api_key' => array('title' => 'Merchant API Key', 'type' => 'text', 'description' => 'From your UPIQRPay Dashboard'),
'webhook_secret' => array('title' => 'Webhook HMAC Secret', 'type' => 'text')
);
}
public function process_payment($order_id) {
$order = wc_get_order($order_id);
$apiUrl = 'http://localhost:5001/api/v1/order/create';
$response = wp_remote_post($apiUrl, array(
'headers' => array(
'Authorization' => 'Bearer ' . $this->api_key,
'Content-Type' => 'application/json'
),
'body' => json_encode(array(
'order_id' => (string)$order->get_id(),
'amount' => (float)$order->get_total(),
'customer_name' => $order->get_billing_first_name(),
'customer_email' => $order->get_billing_email(),
'redirect_url' => $this->get_return_url($order)
)),
'timeout' => 15
));
if (is_wp_error($response)) {
wc_add_notice('Gateway connection error.', 'error');
return;
}
$body = json_decode(wp_remote_retrieve_body($response), true);
if (!empty($body['success'])) {
return array('result' => 'success', 'redirect' => $body['data']['pay_url']);
}
wc_add_notice('Error: ' . ($body['message'] ?? 'Could not create order'), 'error');
return;
}
public function handle_webhook() {
$raw = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_UPIQRPAY_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $raw, $this->webhook_secret);
if (!hash_equals($expected, $signature)) {
status_header(401);
exit('Invalid Signature');
}
$data = json_decode($raw, true);
if ($data['event'] === 'payment.success') {
$order = wc_get_order($data['data']['order_id']);
if ($order && !$order->is_paid()) {
$order->payment_complete($data['data']['utr_number']);
$order->add_order_note('Payment verified via UPIQRPay. Bank UTR: ' . $data['data']['utr_number']);
}
}
status_header(200);
exit('OK');
}
}
}
add_filter('woocommerce_payment_gateways', function($gateways) {
$gateways[] = 'WC_Gateway_UPIQRPay';
return $gateways;
});