How to Generate Dynamic UPI QR Codes Programmatically (with API examples)
VyaparGateway Team
Payments Editorial
Generating a UPI QR code programmatically is straightforward once you understand the two layers involved: the UPI URI specification (which defines what data goes in the QR) and the payment intent API (which manages the lifecycle of each transaction on the server). This guide walks through both layers with API examples, shows how to render the QR safely on your frontend, and explains the security boundaries you must respect to avoid leaking merchant credentials.
The UPI URI under the hood
Every UPI QR is a QR-encoded URI in the format defined by NPCI. A typical dynamic UPI URI looks like this: upi://pay?pa=merchant@bank&pn=AcmeStore&am=2499.00&tr=ORDER12345&tn=Order+12345&cu=INR. The fields are:
- pa — payee address (your merchant VPA or virtual payment address).
- pn — payee name (your business name shown to the customer).
- am — amount in rupees (must be a string with two decimal places).
- tr — transaction reference (unique per intent — your order ID or gateway intent ID).
- tn — transaction note (short description shown to the customer).
- cu — currency (always INR for UPI inside India).
In theory, you could construct this URI yourself if you have a merchant VPA, generate a QR image from it locally, and ask customers to scan. In practice, this approach falls apart almost immediately because nothing tells you when the payment lands. You need a gateway to provide intent lifecycle management and webhook delivery.
Create a payment intent via API
With VyaparGateway, you create a payment intent server-side and receive back both the QR payload and an intent ID you'll use for tracking. The minimal request is:
POST https://api.vyapargateway.com/v1/intents — body: { amount: 249900, currency: 'INR', order_id: 'ORDER12345', description: 'Order 12345' } — headers: Authorization: Bearer
Note the amount is in paise (smallest currency unit) to avoid floating-point ambiguity. The response includes an intent ID, the UPI URI string, a QR PNG URL (if you want pre-rendered), and an expiry timestamp.
Node.js example
import fetch from 'node-fetch'; async function createIntent(orderId, amountPaise) { const res = await fetch('https://api.vyapargateway.com/v1/intents', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.VG_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ amount: amountPaise, currency: 'INR', order_id: orderId, description: `Order ${orderId}` }) }); return res.json(); }
Render the QR on your frontend
Once your backend has the intent response, you have two options: serve the pre-rendered PNG URL (simplest) or pass the UPI URI to a frontend QR library and generate the image client-side (smaller payload, more flexibility for styling).
- Server-rendered PNG — fastest to ship; works in plain HTML, no client JavaScript needed.
- Client-side qrcode.js or react-qr-code — better for responsive layouts and dark-mode adjustments.
- Intent URL as a button on mobile — opens the customer's UPI app directly instead of showing a QR. Better mobile UX.
"On mobile, prefer an intent URL button ('Pay with PhonePe / GPay / Paytm') over showing a QR — the customer doesn't need to scan their own phone with their own phone."
Intent expiry and polling
Dynamic QR intents have a configurable expiry — VyaparGateway defaults to 15 minutes. After expiry, the intent can no longer be paid. This prevents two failure modes: customers paying with the wrong amount because the price changed since the QR was generated, and accidental re-use of stale QRs days later.
While the customer is on the checkout page, your frontend should poll the intent status endpoint every 3–5 seconds to update the UI (showing 'waiting for payment', 'payment received', or 'intent expired'). The polling is purely cosmetic — the authoritative source of truth is still the webhook on your backend, which fires the moment the credit lands regardless of what the frontend is doing.
Security: where to put the API key
VyaparGateway API keys are server-side credentials. They must live only in your backend environment variables — never in:
- Client-side JavaScript bundles (anyone can view-source).
- Mobile app binaries (decompilable in minutes).
- Public git repos (GitHub scans for credential leaks but assume nothing).
- Client-side environment files like .env.local imported into a React app (still bundled).
If you accidentally leak a key, rotate it immediately from the dashboard and search your logs for any suspicious intent-creation activity. The new key is live in seconds; the old one is dead.
Putting it all together
A complete dynamic UPI checkout flow is: customer clicks pay → your backend calls /intents with order details → returns intent ID and QR to frontend → frontend renders QR and polls status → customer pays in UPI app → webhook fires to your /webhooks/vyapargateway endpoint → backend marks order paid and fulfills → frontend sees status update on next poll and shows success page. The whole loop usually completes in under 20 seconds of customer time. VyaparGateway helps you issue dynamic UPI QR codes, verify payments, and notify your stack via webhooks—without charging a per-transaction platform fee on top of your plan.
Frequently asked questions
- Can I generate UPI QR codes without an API?
- Technically you can construct a UPI URI manually and generate a QR image from it using any QR library, but this only handles the QR generation — it doesn't handle payment detection, intent expiry, webhook delivery, or signature verification. For any real online business, you need an API-backed gateway like VyaparGateway.
- What should the amount field contain?
- In the UPI URI itself, the amount is a string in rupees with two decimal places (e.g. '2499.00'). In VyaparGateway's API, you pass the amount in paise (the smallest currency unit) as an integer to avoid floating-point precision bugs — so ₹2,499 is sent as 249900.
- How long does a dynamic UPI QR stay valid?
- VyaparGateway defaults to 15 minutes per intent, configurable up to 60 minutes. After expiry, the QR cannot be paid and your backend's intent status endpoint will return 'expired'. This protects against stale-QR confusion and accidental re-payment.
- Is it safe to put the UPI URI in HTML?
- Yes. The UPI URI contains only the merchant VPA, amount, and order reference — all data the customer needs to see anyway. The sensitive part of the integration is your API key, which lives in your backend and never appears in the URI or in any client-side code.
Free tools for Indian merchants
No sign-up, no ads, no data selling
Use our free, browser-only tools whenever you need them. We don't store the values you enter or track you across the web.
📱 UPI QR Code Generator
Create UPI / URL / WhatsApp / WiFi QRs. Export as SVG or PNG.
🧮 GST Calculator (CGST + SGST + IGST)
Add or remove GST across 5%, 12%, 18%, 28% slabs.