developer
HDFC SmartHub Vyapar API Integration Tutorial for Online UPI
Understand HDFC SmartHub Vyapar's public capabilities and build a secure online UPI checkout using VyaparGateway orders, webhooks, and reconciliation.
Fact-checked and updated
HDFC Bank’s SmartHub Vyapar offering is designed to help eligible merchants accept and manage digital payments. HDFC’s public material describes capabilities including interoperable QR acceptance, UPI Collect, transaction reporting, and real-time UPI settlement for supported merchant arrangements.
That does not automatically mean every SmartHub merchant receives a public ecommerce API. The public SmartHub payment-solutions page, UPI Collect page, and SmartHub Vyapar FAQ explain the merchant product, but do not publish a complete self-service SmartHub Vyapar online checkout API contract.
This tutorial therefore separates verified public facts from the integration layer you can build. It shows how an eligible merchant can connect a supported provider account in VyaparGateway, create online UPI orders from a server, receive signed payment events, and reconcile status without inventing undocumented HDFC endpoints.
Understand the HDFC product boundaries
Three similarly named surfaces can appear during research:
| Surface | Public purpose | What developers should assume |
|---|---|---|
| SmartHub Vyapar | Merchant app for accepting and managing payments | A merchant product, not automatically a public ecommerce API |
| HDFC developer portal | HDFC’s broader API discovery and partner surface | Access, products, and approval depend on the bank’s current programme |
| SmartGateway | HDFC’s online payment-gateway product | A separate product with separate onboarding and documentation |
HDFC operates an official developer portal, and it separately publishes SmartGateway integration documentation. The existence of either surface is not proof that SmartHub Vyapar credentials are interchangeable with those products.
Before writing direct bank code, obtain written answers from HDFC or your authorised acquiring contact:
- Is online API access enabled for this exact merchant account?
- Which product name and merchant ID are provisioned?
- Is the integration UPI Collect, intent, dynamic QR, a hosted page, or a payment gateway?
- Which test and live hosts apply?
- How are requests authenticated and responses signed?
- Which webhook source, retry policy, and status enquiry API apply?
- What settlement, refund, dispute, and reconciliation reports are available?
Ensure you rely exclusively on officially supported API patterns for checkout integration.
Confirm merchant eligibility and access
HDFC’s public SmartHub material describes eligibility and account relationships that can change by offer, geography, business type, and bank policy. Treat the current HDFC onboarding response—not a third-party tutorial—as authoritative.
A practical readiness checklist is:
- Complete business KYC with the provider or bank.
- Confirm that the settlement account and merchant UPI identity belong to the business.
- Enable the intended acceptance method in the official merchant channel.
- Confirm limits, settlement timing, refund handling, and any applicable fees in writing.
- Obtain test credentials or a documented partner workflow when direct API access is approved.
- Record who owns support for missing, reversed, or disputed transactions.
For a VyaparGateway-assisted flow, connect the merchant account through the dashboard only when that provider connection is offered for your workspace. Confirm that the displayed merchant name and UPI ID are correct before accepting a real payment.
Keep environments separate
Use distinct test and live secrets:
Development
VG_BASE_URL=https://documented-test-host.example
VG_API_KEY=vg_test_...
VG_WEBHOOK_SECRET=test-secret-from-dashboard
Live
VG_BASE_URL=https://documented-live-host.example
VG_API_KEY=vg_live_...
VG_WEBHOOK_SECRET=live-secret-from-dashboard
The host values above are labels, not endpoint claims. Copy the exact base URL presented in your merchant dashboard or current integration documentation. Add a startup assertion that prevents a live key from being sent to a test host and vice versa.
Design the online payment architecture
The safe boundary is straightforward: secrets and payment state live on your backend; the browser displays checkout.
Customer browser
│ POST /checkout (cart reference)
▼
Merchant backend
│ validates cart, calculates amount, creates local pending order
│ POST /api/v1/create_order with X-API-Key
▼
VyaparGateway order layer
│ returns hosted payment URL, UPI intent and QR data
▼
Connected merchant UPI acceptance route
│
├──────── settlement follows provider/bank arrangement
│
└──────── signed payment event ───────► merchant webhook
│
▼
idempotent order transition
This architecture avoids four common mistakes:
- exposing the API key in browser JavaScript;
- trusting an unverified redirect;
- using a screenshot as payment proof;
- assuming that order creation means payment success.
The merchant backend should own two identifiers:
- an internal immutable order ID, such as
ord_01K...; - a unique
client_txn_idsent to the payment layer for idempotency.
Store money as paise in your application. Convert to a fixed two-decimal rupee string only at the API boundary.
function paiseToRupees(amountPaise: bigint): string {
const rupees = amountPaise / 100n;
const paise = amountPaise % 100n;
return `${rupees}.${paise.toString().padStart(2, "0")}`;
}
Create a payment order
The VyaparGateway order endpoint accepts a server-side X-API-Key. A minimal Node/TypeScript client can be written without an SDK:
type CreatePaymentInput = {
orderId: string;
amountPaise: bigint;
customerName?: string;
customerEmail?: string;
customerMobile?: string;
};
type VyaparGatewayOrder = {
status: boolean;
msg: string;
data: {
order_id: string;
client_txn_id: string;
amount: number;
currency: "INR";
status: string;
payment_url: string;
expires_at: string;
qr_code: string;
upi_string: string;
upi_intent: Record<string, string>;
};
};
function requiredEnv(name: string): string {
const value = process.env[name];
if (!value) throw new Error(`Missing ${name}`);
return value;
}
export async function createUpiPayment(
input: CreatePaymentInput,
): Promise<VyaparGatewayOrder["data"]> {
const response = await fetch(
`${requiredEnv("VG_BASE_URL")}/api/v1/create_order`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": requiredEnv("VG_API_KEY"),
},
body: JSON.stringify({
client_txn_id: input.orderId,
amount: paiseToRupees(input.amountPaise),
p_info: `Payment for ${input.orderId}`,
customer_name: input.customerName,
customer_email: input.customerEmail,
customer_mobile: input.customerMobile,
redirect_url:
`${requiredEnv("STORE_ORIGIN")}/orders/${encodeURIComponent(input.orderId)}`,
callback_url:
`${requiredEnv("STORE_ORIGIN")}/webhooks/vyapargateway`,
}),
signal: AbortSignal.timeout(10_000),
},
);
const payload = (await response.json()) as
| VyaparGatewayOrder
| { detail?: string };
if (!response.ok || !("status" in payload) || !payload.status) {
const reason =
"detail" in payload ? payload.detail : "Payment order was rejected";
throw new Error(String(reason));
}
if (
payload.data.client_txn_id !== input.orderId ||
payload.data.currency !== "INR"
) {
throw new Error("Unexpected payment order response");
}
return payload.data;
}
Return payment_url to the browser and navigate the customer to the hosted payment page. On mobile, the response may also provide app-specific UPI intents. On desktop, render the returned QR image or hosted page; do not generate a second payment address with different order data.
Make retries safe
Network timeouts are ambiguous: the server may have created the order even if your application did not receive the response. Reuse the same client_txn_id when retrying the same local order.
Never generate a fresh transaction ID merely because fetch() timed out. A new ID can create two payable orders for one cart.
Persist at least:
CREATE TABLE payment_orders (
id TEXT PRIMARY KEY,
gateway_order_id TEXT UNIQUE,
client_txn_id TEXT NOT NULL UNIQUE,
amount_paise BIGINT NOT NULL CHECK (amount_paise > 0),
currency TEXT NOT NULL CHECK (currency = 'INR'),
status TEXT NOT NULL CHECK (
status IN ('pending', 'paid', 'expired', 'failed', 'refunded')
),
paid_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
Verify webhooks and update orders
VyaparGateway signs the canonical request as:
hex(HMAC-SHA256(webhook_secret, timestamp + "." + raw_request_body))
Verify the exact raw bytes before parsing JSON. The current headers are:
X-VyaparGateway-Signature
X-VyaparGateway-Timestamp
X-VyaparGateway-Order-Id
X-VyaparGateway-Version
An Express handler:
import crypto from "node:crypto";
import express from "express";
const app = express();
app.post(
"/webhooks/vyapargateway",
express.raw({ type: "application/json", limit: "256kb" }),
async (req, res) => {
const rawBody = req.body as Buffer;
const signature = String(
req.header("x-vyapargateway-signature") ?? "",
);
const timestamp = String(
req.header("x-vyapargateway-timestamp") ?? "",
);
const timestampSeconds = Number(timestamp);
const nowSeconds = Math.floor(Date.now() / 1000);
if (
!Number.isSafeInteger(timestampSeconds) ||
Math.abs(nowSeconds - timestampSeconds) > 300
) {
return res.status(401).send("Stale webhook");
}
const expected = crypto
.createHmac("sha256", requiredEnv("VG_WEBHOOK_SECRET"))
.update(timestamp)
.update(".")
.update(rawBody)
.digest();
let received: Buffer;
try {
received = Buffer.from(signature, "hex");
} catch {
return res.status(401).send("Invalid signature");
}
if (
received.length !== expected.length ||
!crypto.timingSafeEqual(received, expected)
) {
return res.status(401).send("Invalid signature");
}
const event = JSON.parse(rawBody.toString("utf8")) as {
event: string;
order_id: string;
client_txn_id: string;
status: "success" | "expired" | "failed";
amount: number;
currency: string;
upi_txn_id?: string;
timestamp: number;
};
await applyPaymentEventIdempotently(event);
return res.status(204).send();
},
);
Inside applyPaymentEventIdempotently, run one database transaction:
- Lock the local order by
client_txn_id. - Compare
order_id, currency, and amount with stored values. - Insert a deduplication record with a unique event key.
- Apply only a legal state transition.
- Write an immutable payment audit record.
- Commit before triggering fulfilment asynchronously.
JSON numbers can lose exactness. Normalise the received amount to paise with a decimal library or compare against a fixed-decimal string supplied by the current API contract. Do not use an unrounded binary floating-point multiplication for ledger decisions.
Reconcile and operate the integration
Webhooks are a notification channel, not your only recovery mechanism. If an event is delayed or your endpoint is unavailable, query the order by gateway order ID or client transaction ID from a server:
export async function checkPaymentStatus(input: {
gatewayOrderId?: string;
clientTxnId?: string;
}) {
if (!input.gatewayOrderId && !input.clientTxnId) {
throw new Error("An order identifier is required");
}
const response = await fetch(
`${requiredEnv("VG_BASE_URL")}/api/v1/check_order_status`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": requiredEnv("VG_API_KEY"),
},
body: JSON.stringify({
order_id: input.gatewayOrderId,
client_txn_id: input.clientTxnId,
}),
signal: AbortSignal.timeout(10_000),
},
);
if (!response.ok) {
throw new Error(`Status check failed with HTTP ${response.status}`);
}
return response.json() as Promise<VyaparGatewayOrder>;
}
Reconcile pending orders after the customer-facing expiry window, and continue checking ambiguous payments according to your documented operational policy. A customer can authorise near the boundary while notifications are in flight.
Monitor the right signals
Track:
- order-creation success and latency;
- the ratio of created orders to verified successful payments;
- webhook delivery delay and signature failures;
- duplicate webhook rate;
- pending orders beyond their expected decision window;
- amount or identifier mismatches;
- settlement and transaction-report mismatches;
- provider-account connection health.
Alert on a sustained change, not a single customer cancellation.
Build a support-safe audit trail
Support staff should be able to search by:
- internal order ID;
client_txn_id;- gateway
order_id; - provider UPI transaction reference when available;
- amount and approximate payment time.
Redact API keys, webhook secrets, full customer contact data, and unnecessary payer details. Restrict refunds and manual payment overrides with role-based access and an audit log.
Test before accepting customer traffic
Exercise these cases in the approved development environment:
- successful payment;
- customer abandons payment;
- expired QR or payment link;
- duplicate order-creation retry;
- duplicate webhook delivery;
- webhook before browser redirect;
- redirect before webhook;
- invalid and stale signatures;
- wrong amount or order reference;
- temporary webhook endpoint failure followed by retry;
- status reconciliation after a missed event;
- provider connection disabled or unavailable.
The durable integration is not the one with the shortest happy-path snippet. It is the one that makes product boundaries explicit, keeps secrets on the server, verifies every payment signal, and can reconcile an ambiguous order without relying on a screenshot.
Direct answers
Frequently asked questions
- Does HDFC publish a self-service SmartHub Vyapar ecommerce API?
- The public SmartHub Vyapar product pages describe merchant acceptance, UPI Collect, QR, reporting, and settlement, but they do not expose a complete self-service ecommerce API specification. Ask HDFC for the current partner documentation and eligibility terms before designing a direct integration.
- Is HDFC SmartGateway the same product as SmartHub Vyapar?
- No. HDFC presents SmartGateway as an online payment-gateway product with its own integration documentation, while SmartHub Vyapar is a merchant acceptance application. Do not copy SmartGateway endpoints or credentials into a SmartHub integration unless HDFC explicitly provisions that product.
- Can VyaparGateway move the merchant's settlement money?
- In the direct-settlement model described here, the customer pays the connected merchant UPI identity and settlement follows the merchant's provider or bank arrangement. VyaparGateway creates the checkout order and automates status notification; it is not a substitute for the bank account or provider contract.
- Should the browser call the payment order API with an API key?
- No. Keep the API key on your server. Your browser calls your own checkout endpoint, your server creates the VyaparGateway order, and the browser receives only the hosted payment URL or other customer-safe checkout data.
- How should an HDFC-linked UPI payment be marked as paid?
- Use a verified server event or a server-side order-status check, then compare order ID, merchant transaction ID, currency, amount, and final status. Never fulfil an order from a browser redirect or screenshot alone.
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.