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.

VT VyaparGateway Team Developer Relations 3 min read
UPI Webhook Integration Guide for Merchants — Order Confirmation & Payment Flow guide
UPI webhook webhook integration payment confirmation HMAC SHA256 Node.js payment VyaparGateway API idempotency

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:

  1. NPCI confirms the UPI transaction.
  2. VyaparGateway receives the settlement confirmation.
  3. VyaparGateway sends an HTTP POST request to your configured webhook URL.
  4. 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:

  1. Go to Settings → Webhooks.
  2. Enter your endpoint URL (e.g., https://yourdomain.com/webhooks/vyapargateway).
  3. Select the events you want to receive: payment.success, payment.failed, payment.refunded.
  4. 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 event
  • order_id — the order reference you set when creating the payment link
  • utr — 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

ProblemCauseFix
Signature mismatchUsing req.body (already parsed JSON) instead of raw bytesUse express.raw() middleware
Connection refusedServer firewall blocks inbound 443/80Open inbound port or use a reverse proxy
SSL errorSelf-signed certificate on your endpointUse a valid TLS certificate (Let’s Encrypt is free)
TimeoutBusiness logic running synchronously in handlerQueue the work, return 200 immediately
Duplicate fulfilmentNo idempotency checkStore 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.