Handling webhooks
Receive the webhook, confirm it with Get a payment, and handle repeats.
A webhook is a message Zoorya Pay sends to your server when a payment finishes, so you don't have to keep asking. This page shows how to receive it safely, confirm it, and avoid handling the same payment twice.
The rule: a webhook is a signal, not proof#
When a webhook arrives, call Get a payment for its paymentId and act only when status is SUCCESSFUL and amount matches your order. This keeps you safe even if someone sends a fake request to your callback URL.
Steps in your handler#
- Read
externalUniqueId(your ID) andpaymentIdfrom the body. See Webhook shape. - Reply
200straight away. Do the rest in the background. - If you already processed this
paymentId, stop. - Call Get a payment for the
paymentId. - If
statusisSUCCESSFULand the amount matches your order, mark it paid and fulfil.
app.post("/webhooks/zoorya", express.json(), (req, res) => {
res.sendStatus(200); // answer fast
const { externalUniqueId, paymentId } = req.body || {};
if (!externalUniqueId || !paymentId) return;
setImmediate(async () => {
if (await alreadyProcessed(paymentId)) return;
const order = await findOrder(externalUniqueId);
if (order && (await isPaid(auth, paymentId, order.total))) { // see Recommended pattern
await markOrderPaid(order);
}
await rememberProcessed(paymentId);
});
});Repeats: use paymentId#
The same webhook can arrive more than once. Each payment has its own paymentId — use it to detect repeats, and never deliver the same order twice.
Delivery#
- Your callback URL must be reachable from the public internet.
localhostdoes not work — use a tunnel when testing (see below). - Reply with a
2xxquickly, and do slow work in the background. - Use
https://in production.
If you miss a webhook, nothing is lost: call Get a payment at any time.
Do not rely on the webhook alone. For orders still unpaid after a few minutes, call Get a payment yourself — for example from a scheduled job every few minutes.
Test webhooks locally#
Your laptop is not on the public internet, so use a tunnel such as ngrok:
ngrok http 3000Use the https://…ngrok… address it prints as your callback URL in sandbox.

