Developer Toolkit
Embed a UPI QR, or run a checkout session
Three layers. The widget and upi://pay link are free and need no key. Hosted checkout needs a dashboard API key. This is not NPCI’s switch API and not a Razorpay replacement.
Free · no key
1. Embed widget
Iframe a live QR. Configure VPA, amount, theme below. Copy the snippet.
Free · no key
2. Native UPI link
Open PhonePe/GPay from a button. You reconcile the UTR yourself.
API key
3. Hosted checkout
Create a 24-hour session. Confirm paid from your server after the bank credit.
Using Claude or another AI assistant? Add our free MCP server and ask it for UPI QR codes, payment links, error-code help and GST in plain words.
Widget configurator
No API key. The iframe loads /embed/ with your VPA. Copy the snippet or the URL.
<iframe
src="https://www.proupiqr.in/embed/?pa=merchant%40upi&pn=Merchant+Store&am=500&tn=Invoice+%231024&theme=287a57"
width="340"
height="480"
style="border:none; background:transparent; overflow:hidden;"
title="UPI Payment QR Widget"
></iframe>Live Sandbox Preview
Scan from PhonePe, GPay, Paytm, or BHIM. Amount ₹500 to Merchant Store. This preview does not confirm a bank credit.
Merchant API key required
Hosted Checkout Sessions
Create a 24-hour checkout with an exact INR amount and public HTTPS return URL. Reusing the same order_id is idempotent. A customer-submitted UTR remains verification_pending until your server checks its bank records and confirms the session.
Get an API key
- Create an account.
- Open the merchant dashboard and copy
puqi_live_…. - Set the shop VPA on that profile. Checkout uses the saved VPA, not a VPA in the POST body.
- Call the API only from your server:
Authorization: Bearer puqi_live_….
curl · create session
curl -sS -X POST https://www.proupiqr.in/api/v1/checkout-sessions/ \
-H "Authorization: Bearer puqi_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"order_id":"ord_1001","amount":250.50,"currency":"INR","redirect_url":"https://example.com/thanks"}'1. Create
POST /api/v1/checkout-sessions/
Authorization: Bearer puqi_live_...2. Inspect
GET /api/v1/checkout-sessions/:id/
Authorization: Bearer puqi_live_...3. Confirm
PATCH /api/v1/checkout-sessions/:id/
{ "status": "paid", "confirmed": true }Embed widget query parameters
Point an iframe at /embed/?pa=…&pn=…. This is a widget, not an open redirect. There is no public endpoint that 302s to an arbitrary URL.
https://www.proupiqr.in/embed/?pa=shop@oksbi&pn=My%20Shop&am=250&tn=INV-1&theme=287a57
| Parameter | Type | Description | Example |
|---|---|---|---|
| pa | String (Required) | Your registered merchant/personal UPI Virtual Payment Address (VPA) | payee@bank |
| pn | String (Required) | The display name of the payee/business | Merchant Store |
| am | Number (Optional) | Requested transaction amount. (Locks the amount field on scanning apps) | 250.50 |
| tn | String (Optional) | Transaction note or reference ID (shows up on the customer's payment statement) | Order-8390 |
| theme | String (Optional) | Custom hex color (without # prefix) to theme the payment widget button | ef4444 |
| logo | String (Optional) | Brand overlay logo. Options: phonepe, gpay, paytm, bhim | phonepe |
Backend-only verification
Payment Response API
This API is for your server, not for the customer's browser. The browser can open a checkout screen or UPI link, but only your backend can receive and trust the final payment result from a verified gateway webhook or secure relay.
Simple client flow
- 1. Your app creates a payment request or shows a checkout screen.
- 2. The customer pays in their UPI app.
- 3. Your backend stores the verified result and your UI polls for status.
1. Receive the verified gateway result
Configure your gateway webhook to reach your server first. Your backend verifies the provider signature, uses a globally unique order ID, normalizes the result, and then calls this endpoint server-to-server.
POST /api/payment-response/
Content-Type: application/json
x-pro-upi-timestamp: 1787832000000
x-pro-upi-signature: sha256=...
{ "orderId": "order_8390", "status": "SUCCESS", "transactionId": "UTR…", "amountPaise": 25000, "provider": "your-gateway" }2. Read the saved response
Your frontend can show a payment pending screen while your backend polls this endpoint or updates your order database. The result is retained for 90 days in Vercel KV.
GET /api/payment-response/?orderId=order_8390
x-pro-upi-timestamp: 1787832000000
x-pro-upi-signature: sha256=...
{ "payment": { "status": "SUCCESS", "transactionId": "UTR…", "receivedAt": "…" } }PAYMENT_WEBHOOK_SECRET, KV_REST_API_URL, and KV_REST_API_TOKEN in Vercel. Sign POST as POST.timestamp.rawBody and GET as GET.timestamp.sortedQuery. The secret is only for server-to-server calls. Keep it out of browser JavaScript and embedded widgets. This response store is separate from hosted checkout sessions and does not change their status automatically.Native UPI Intent Link
Open the standard UPI URI directly from your mobile checkout. Pro UPI QR does not provide an unrestricted redirect endpoint for arbitrary destinations.
upi://pay?pa=merchant@upi&pn=Pro%20UPI%20QR&am=250.00&cu=INREncode parameter values and verify the payee and amount in the UPI app before authorizing payment.
No gateway cut on the widget
The iframe and upi://pay link are not a payment gateway. Money goes bank-to-bank. That is not the same as “always 0% MDR”: from 15 Oct 2026 some P2M UPI above ₹2,000 may take 0.4% (cap ₹300). P2P, tickets of ₹2,000 or less, and small QR (P2PM) stay free.
Open-source utility
UPI URI Parser & Builder
A single 4 kB TypeScript module that both constructs valid upi://pay?... links from typed fields and parses them back into structured data. Copy it into any JavaScript, React, Next.js, or Node project. Zero dependencies. Based on NPCI UPI Linking Specification v1.6.
Build a UPI URI
import { buildUpiUri } from './lib/upi-uri';
const uri = buildUpiUri({
pa: 'shop@upi',
pn: 'My Shop',
am: '250.50'
});
// "upi://pay?pa=shop%40upi&pn=My%20Shop&am=250.50&cu=INR"Parse a UPI URI
import { parseUpiUri } from './lib/upi-uri';
const r = parseUpiUri('upi://pay?...');
r.pa // "shop@upi"
r.pn // "My Shop"
r.amountPaise // 25050
r.valid // truebuildUpiUri(fields)
Accepts { pa, pn, am?, tn?, cu?, mc?, tr?, ref? }. Returns an encoded UPI URI string. Omits optional fields when empty.
parseUpiUri(uri)
Returns { pa, pn, am, tn, cu, amountPaise, amountNumeric, valid }. amountPaise is integer paise or null.
isValidUpiUri(uri)
Returns boolean. Validates VPA format (regex), payee name presence, amount decimal places, and char safety.
/src/lib/upi-uri.ts from the site source. No npm package yet. The parser uses URLSearchParams, which is available in browsers, Node 18+, React Native, and Android/iOS webviews. Full source is ~120 lines, MIT-licensed.Platform Integration Examples
Copy-paste ready code for four platforms. All examples use the free UPI intent — no API key, no SDK, no server.
React Component
import React from 'react';
function UpiPayButton({ vpa, name, amount }: { vpa: string; name: string; amount?: number }) {
const pay = () => {
const params = new URLSearchParams({ pa: vpa, pn: name, cu: "INR" });
if (amount) params.set("am", amount.toFixed(2));
window.location.href = `upi://pay?${params}`;
};
return (
<button onClick={pay}
className="rounded-[10px] bg-emerald-800 px-6 py-3 text-white font-bold hover:bg-emerald-700">
Pay ₹{amount ? amount.toFixed(2) : "..."}
</button>
);
}
export default UpiPayButton;Next.js Server + Client
// app/api/upi-link/route.ts — Server Component
import { NextResponse } from 'next/server';
export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const vpa = searchParams.get('vpa') || 'shop@upi';
const name = searchParams.get('name') || 'My Shop';
const amt = searchParams.get('amount');
const params = new URLSearchParams({ pa: vpa, pn: name, cu: 'INR' });
if (amt) params.set('am', amt);
return NextResponse.redirect(`upi://pay?${params}`);
}
// Usage: /api/upi-link?vpa=shop@upi&name=Store&amount=250Android (Kotlin)
// Open a UPI payment intent in any installed UPI app
fun payViaUpi(context: Context, vpa: String, name: String, amount: String?) {
val uri = Uri.Builder()
.scheme("upi")
.authority("pay")
.appendQueryParameter("pa", vpa)
.appendQueryParameter("pn", name)
.appendQueryParameter("cu", "INR")
.apply { amount?.let { appendQueryParameter("am", it) } }
.build()
val intent = Intent(Intent.ACTION_VIEW, uri).apply {
setPackage("com.google.android.apps.nbu.paisa.user") // GPay fallback
}
try {
context.startActivity(intent)
} catch (e: ActivityNotFoundException) {
// No UPI app installed — show install prompt
}
}iOS (Swift)
// Open UPI payment via URL scheme (Swift)
func payViaUpi(vpa: String, name: String, amount: String?) {
var components = URLComponents()
components.scheme = "upi"
components.host = "pay"
components.queryItems = [
URLQueryItem(name: "pa", value: vpa),
URLQueryItem(name: "pn", value: name),
URLQueryItem(name: "cu", value: "INR")
]
if let amt = amount {
components.queryItems?.append(URLQueryItem(name: "am", value: amt))
}
guard let url = components.url else { return }
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
} else {
// Fallback: open App Store UPI app
}
}Attribution: Embedding the Pro UPI QR widget is free for any commercial or non-commercial use. Widget attribution ("Powered by Pro UPI QR") may be disabled by setting ?attribution=none on the embed URL. We ask that you do not use the widget in link schemes, keyword-stuffed footer links, or hidden embeds — this violates Google's link-spam policy and can hurt your site's search rankings.
Developer FAQ
Do I need an API key to put a UPI QR on my website?
No. The iframe embed and the upi://pay link are free and run in the browser. An API key is only for hosted checkout sessions after you sign in.
Is this the official NPCI or UPI API?
No. NPCI switch APIs are for banks and PSPs. This site exposes the public upi://pay intent, an embeddable QR widget, and optional merchant checkout sessions. Payments settle bank-to-bank; we do not hold funds.
How do I get a merchant API key?
Create an account, open the dashboard, and copy the puqi_live_ key. Send it only as Authorization: Bearer on server-side calls. Never put it in frontend JavaScript or the embed URL.
How do I know the customer actually paid?
A green tile in PhonePe is not proof. Match the 12-digit UTR in your bank SMS, or confirm a hosted checkout session from your server after you have verified the credit. The widget does not fire a payment webhook by itself.
Longer write-up: free UPI API without a payment gateway· Test a payload with the UPI QR decoder· Print a standee with the UPI QR generator.