developer
UPI Webhook Integration for Merchants: Current Payload and HMAC
Implement the current VyaparGateway webhook contract with timestamped raw-body HMAC, event ID deduplication, amount checks, delivery history, and recovery.
Fact-checked and updated
A webhook endpoint is a public server boundary. The URL alone is not authentication. The current VyaparGateway contract signs the timestamp and exact JSON bytes, includes a stable event ID, and records delivery attempts so merchants can distinguish transport failures from application processing failures.
This article is the current merchant overview. For framework-ready code and failure cases, use the deeper signed payment webhook guide.
Current Webhook Contract
Current preferred headers:
X-VyaparGateway-SignatureX-VyaparGateway-TimestampX-VyaparGateway-Order-IdX-VyaparGateway-Version
The JSON payload contains fields such as:
{
"event_id": "payment-intent-id:payment.success",
"event": "payment.success",
"order_id": "gateway-order-reference",
"client_txn_id": "merchant-order-reference",
"status": "success",
"amount": 4500.0,
"currency": "INR",
"p_info": "Website order",
"merchant_name": "Connected merchant"
}
Exact optional fields depend on order and provider data. Do not design fulfilment around payer name, VPA, or customer contact fields. Use the merchant order reference, gateway order ID, verified amount, event ID, and final state.
Timestamped HMAC Verification
The signed message is:
timestamp + "." + exact_raw_body
The digest is HMAC-SHA256 using the webhook secret and hexadecimal output. A Node.js verifier:
import crypto from "node:crypto";
function verifyWebhook({ rawBody, timestamp, received, secret }) {
const ageSeconds = Math.abs(Date.now() / 1000 - Number(timestamp));
if (!Number.isFinite(ageSeconds) || ageSeconds > 300) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(timestamp + ".")
.update(rawBody)
.digest("hex");
return expected.length === received.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));
}
Capture raw bytes before JSON middleware transforms them. Verify timestamp and signature before parsing or trusting event_id.
Idempotent Order Transition
After authentication:
- Parse the JSON.
- Insert
(tenant_id, event_id)under a unique database constraint. - Lock the mapped local order.
- Compare
client_txn_id, expected amount, and allowed current state. - Transition the order once.
- Create a durable fulfilment job or outbox record.
- Commit, then acknowledge 2xx.
Do not use a separate “find then insert” check without a unique constraint; concurrent copies can race. Do not send inventory, email, or access before the transaction commits. The duplicate event guide provides the database pattern.
Delivery and Recovery
The API Credentials page provides a Webhook Workbench with URL validation, one-click real test events, HTTP response code, response latency, delivery timestamp, attempt number, event ID, safe response or error summary, and delivery history.
A test passes only after the destination returns an actual 2xx response. A 2xx proves endpoint acceptance, not downstream fulfilment; carry event_id through your application logs and database records.
Diagnose responses by class rather than retrying every failure blindly:
| Result | Likely meaning | Merchant action |
|---|---|---|
| 2xx | Endpoint accepted the event | Confirm durable receipt and one order transition |
| 401 or 403 | Authentication or authorization rejected | Check raw body, timestamp, secret, and proxy headers |
| 404 | Saved URL or deployment route is wrong | Correct the public webhook URL |
| 408 or timeout | Endpoint did not acknowledge in time | Shorten synchronous work and inspect dependencies |
| 429 | Merchant rate limit rejected delivery | Reserve capacity for authenticated payment events |
| 5xx | Application or dependency failed | Use event ID to investigate and recover safely |
Do not return 2xx merely to silence failed-delivery alerts. Return success only when the authentic event has been durably accepted or recognized as an already-processed duplicate.
Do not rely on a hard-coded public retry count or schedule. Treat repeated delivery as possible and keep the consumer idempotent. For delayed order confirmation, inspect delivery history and use POST /api/v1/check_order_status from the backend for the known order. Never place X-API-Key in the browser.
Production Checklist
- Public HTTPS endpoint reaches the intended POST route.
- Proxy forwards preferred signature and timestamp headers.
- Raw body is captured before JSON parsing.
- Server clock is synchronized.
- Webhook secret is separate from the API key.
- Invalid and stale signatures are rejected.
event_idhas a tenant-scoped unique constraint.- Amount and order reference are compared before fulfilment.
- Duplicate authentic event returns 2xx with no repeated effect.
- Delivery ID appears in protected application logs.
- A delayed-event recovery path is tested.
- Logs and analytics exclude secrets and unnecessary payment data.
Use the payment webhook go-live checklist to test valid, invalid, duplicate, unavailable, and recovery cases before accepting live orders.
Direct answers
Frequently asked questions
- What exact message does VyaparGateway sign?
- The signed message is the timestamp header, a period, and the exact raw JSON request body. The digest is HMAC-SHA256 encoded as hexadecimal.
- What should a duplicate authentic webhook return?
- When its event ID was already durably processed, return 2xx without applying the order or fulfilment effect again.
- How can I recover when a webhook is delayed?
- Use delivery history to diagnose the endpoint and call the authenticated check_order_status API from your backend for a known unresolved order.
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.