developer
UPI Webhook Integration Guide for Merchants — Order Confirmation & Payment Flow
Integrate VyaparGateway UPI webhooks step by step. Covers payload structure, HMAC-SHA256 verification, idempotency, retry logic, and a complete Node.js handler.
Polling for payment status is a common but inefficient pattern. Webhooks — server-to-server callbacks fired the moment a payment event occurs — are the professional way to confirm UPI payments and trigger downstream workflows. This guide walks through the complete VyaparGateway webhook integration, from setup to production-ready Node.js code.
What a Webhook Does
When a customer completes a UPI payment through your VyaparGateway payment link or QR, the following sequence happens in milliseconds:
- NPCI confirms the UPI transaction.
- VyaparGateway receives the settlement confirmation.
- VyaparGateway sends an HTTP POST request to your configured webhook URL.
- Your server receives the payload, verifies it, and marks the order as paid.
Total elapsed time from customer’s PIN confirmation to your server knowing about it: typically under 3 seconds.
Setting Up Your Webhook Endpoint
In the VyaparGateway dashboard:
- Go to Settings → Webhooks.
- Enter your endpoint URL (e.g.,
https://yourdomain.com/webhooks/vyapargateway). - Select the events you want to receive:
payment.success,payment.failed,payment.refunded. - Save. VyaparGateway displays your Webhook Secret — copy this and store it securely in your environment variables. Do not commit it to source code.
Webhook Payload Structure
VyaparGateway sends a JSON POST body with this structure:
{
"event": "payment.success",
"transaction_id": "VG_TXN_20260816_abc123xyz",
"order_id": "YOUR_ORDER_1042",
"amount": 4500.00,
"currency": "INR",
"vpa": "customer@okicici",
"payer_name": "Rahul Sharma",
"timestamp": "2026-08-16T10:23:45Z",
"status": "SUCCESS",
"utr": "612345678901"
}
Key fields:
transaction_id— VyaparGateway’s unique ID for this payment eventorder_id— the order reference you set when creating the payment linkutr— UPI Transaction Reference (the NPCI-level identifier, useful for bank reconciliation)vpa— the customer’s UPI ID
Signature Verification with HMAC-SHA256
VyaparGateway includes a signature in the X-VyaparGateway-Signature header of every webhook request. You must verify this before processing.
The signature is computed as:
HMAC-SHA256(webhook_secret, raw_request_body)
Here is a complete Node.js/Express webhook handler with signature verification:
const express = require('express');
const crypto = require('crypto');
const app = express();
const WEBHOOK_SECRET = process.env.VG_WEBHOOK_SECRET;
// IMPORTANT: Use raw body parser for signature verification
app.use('/webhooks/vyapargateway', express.raw({ type: 'application/json' }));
app.post('/webhooks/vyapargateway', async (req, res) => {
const signature = req.headers['x-vyapargateway-signature'];
const rawBody = req.body; // Buffer
// 1. Verify signature
const expectedSig = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(rawBody)
.digest('hex');
if (signature !== expectedSig) {
console.warn('Webhook signature mismatch — rejecting');
return res.status(401).json({ error: 'Invalid signature' });
}
// 2. Parse payload
const payload = JSON.parse(rawBody.toString());
const { event, transaction_id, order_id, amount, status } = payload;
// 3. Idempotency check — prevent duplicate processing
const alreadyProcessed = await db.transactions.findOne({ transaction_id });
if (alreadyProcessed) {
return res.status(200).json({ message: 'Already processed' });
}
// 4. Handle the event
if (event === 'payment.success' && status === 'SUCCESS') {
await db.orders.updateOne(
{ order_id },
{ $set: { status: 'PAID', paid_at: new Date(), vg_transaction_id: transaction_id } }
);
await db.transactions.insertOne({ transaction_id, processed_at: new Date() });
// Trigger downstream: send confirmation email, update inventory, etc.
await sendOrderConfirmationEmail(order_id);
}
// 5. Always respond 200 quickly to prevent retries
return res.status(200).json({ received: true });
});
app.listen(3000);
Idempotency — Handling Duplicate Webhooks
UPI networks and VyaparGateway’s retry logic can cause the same payment event to be delivered more than once. Your webhook handler must be idempotent — processing the same event twice must not double-fulfil an order or double-charge inventory.
The pattern above uses transaction_id as a deduplication key stored in a transactions collection. Before processing any event, check if transaction_id already exists. If yes, return 200 immediately without re-running business logic.
Retry Logic
VyaparGateway retries webhook delivery if your server returns:
- Any non-2xx HTTP status code
- No response within 10 seconds (timeout)
Retry schedule: ~1 minute → ~5 minutes → ~30 minutes. After 3 failed attempts, the webhook is marked as failed and you can resend manually from the dashboard.
Best practice: Always return 200 as quickly as possible (within 1–2 seconds) and process business logic asynchronously (queue it). A slow database write inside the request handler risks timeouts.
Testing Webhooks Locally with ngrok
Your local development server (localhost:3000) is not reachable from the internet. Use ngrok to expose it temporarily:
ngrok http 3000
# Outputs: https://abc123.ngrok.io → localhost:3000
Set https://abc123.ngrok.io/webhooks/vyapargateway as your webhook URL in the VyaparGateway dashboard for development. Use the Send Test Event button in dashboard settings to fire a sample payment.success payload and verify your handler works end-to-end.
Common Failure Causes
| Problem | Cause | Fix |
|---|---|---|
| Signature mismatch | Using req.body (already parsed JSON) instead of raw bytes | Use express.raw() middleware |
| Connection refused | Server firewall blocks inbound 443/80 | Open inbound port or use a reverse proxy |
| SSL error | Self-signed certificate on your endpoint | Use a valid TLS certificate (Let’s Encrypt is free) |
| Timeout | Business logic running synchronously in handler | Queue the work, return 200 immediately |
| Duplicate fulfilment | No idempotency check | Store processed transaction_id values |
With this setup, your backend will reliably confirm UPI payments in near real-time without polling, without missed updates, and without duplicate order processing.
Direct answers
Frequently asked questions
- What is the difference between a webhook and polling for payment status?
- Polling means your server repeatedly asks VyaparGateway 'has this payment arrived?' every few seconds — wasteful and adds latency. A webhook is a push notification: VyaparGateway calls your server the moment a payment event occurs. Webhooks are faster, more reliable, and eliminate unnecessary API calls.
- Why must I verify the webhook signature?
- Without signature verification, anyone who knows your webhook URL could send fake payment events to your server and trigger order fulfilment without actual payment. HMAC-SHA256 verification ensures the request came from VyaparGateway and the payload was not tampered with in transit.
- What happens if my server is down when VyaparGateway sends a webhook?
- VyaparGateway retries failed webhooks up to 3 times with exponential backoff (approximately 1 min, 5 min, 30 min intervals). If all retries fail, you can manually trigger a webhook resend from the dashboard or query the transaction status via the GET /transactions API.
Build your payment flow
Explore the API and browser-only merchant tools.
Create UPI checkout orders, verify signed events, or test the free calculators and generators without exposing credentials.