How a payment flows
There are three ways to accept a payment, in increasing order of control:
| Approach | Code needed | Use when |
|---|---|---|
| Payment link | None | You want a URL to share on WhatsApp, Instagram or an invoice |
| Browser SDK | One script tag | You have a website and want a checkout button fast |
| Server API | One POST, one webhook | Production. Your server sets the amount, so a customer cannot change it. |
The server flow, end to end:
- Your server calls
POST /api/v1/paymentswith the order reference and amount. - Cashvya returns a
paymentIdand acheckoutUrl. - You send the customer to that URL, or open it in the SDK modal.
- The customer pays with any UPI app.
- Cashvya matches the bank credit notification to the payment and marks it successful.
- Cashvya POSTs a signed webhook to your endpoint. Your server fulfils the order.
- The money reaches your bank account in the next day's settlement.
Step 1 — Get your keys
Sign in to the dashboard and open Developers. You need two values:
| Value | Looks like | Where it goes |
|---|---|---|
| Secret API key | cashvya_live_… | Server only. Never in a web page or app. |
| Webhook signing secret | whsec_… | Server only. Used to verify webhooks. |
Generating a key displays it a single time and immediately replaces any previous key. After that the dashboard shows only a masked form, and revealing it again asks for your account password. Store it in your server's environment, not in your repository.
Step 2 — Create a payment
Send the amount in rupees, as a string, with at most two
decimals. The orderId is your own reference and must be unique per order.
curl -X POST https://cashvya.com/api/v1/payments \
-H "Authorization: Bearer cashvya_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"orderId": "ORDER-1042",
"amount": "499.00",
"currency": "INR",
"customer": {
"fullName": "Asha Kumar",
"email": "[email protected]",
"phoneNumber": "9876543210"
},
"returnUrl": "https://yourshop.com/thank-you",
"cancelUrl": "https://yourshop.com/cart"
}'
A 201 response:
{
"paymentId": "7f3c1e62-...",
"orderId": "ORDER-1042",
"amount": "499.00",
"amountPaise": "49900",
"currency": "INR",
"status": "INITIATED",
"queuePosition": 0,
"checkoutUrl": "https://cashvya.com/checkout/7f3c1e62-..."
}
Node example:
const res = await fetch('https://cashvya.com/api/v1/payments', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.CASHVYA_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ orderId: order.id, amount: order.total.toFixed(2) })
});
const payment = await res.json();
if (!res.ok) throw new Error(payment.error);
// Save the mapping now, so the webhook can find this order later.
await db.orders.update(order.id, { cashvyaPaymentId: payment.paymentId });
redirect(payment.checkoutUrl);
Step 3 — Send the customer to checkout
Either redirect to the checkoutUrl, or keep the customer on your
page and open it in a modal with the SDK:
<script src="https://cashvya.com/cashvya.js"></script>
<script>
Cashvya.pay({
paymentId: '7f3c1e62-...', // from your server
onSuccess: r => { window.location = '/thank-you?order=' + r.order_id; },
onCancelled: () => console.log('customer closed the checkout')
});
</script>
See SDKs for every option.
Step 4 — Handle the webhook (the important step)
A success event in the browser is a user-interface hint. Anyone can fake it. Treat a payment as paid only after your server has verified a signed webhook, or has read the payment back from the API with your secret key.
Set your webhook URL in Dashboard → Developers. It must be a public HTTPS
URL. Cashvya POSTs JSON and expects a 2xx within 10 seconds.
import express from 'express';
import crypto from 'node:crypto';
const app = express();
// The signature covers the exact bytes sent, so capture the RAW body.
app.post('/webhooks/cashvya', express.raw({ type: 'application/json' }), (req, res) => {
const expected = crypto
.createHmac('sha256', process.env.CASHVYA_WEBHOOK_SECRET)
.update(req.body) // Buffer, untouched
.digest('hex');
const received = String(req.get('X-Gateway-Signature') || '').replace(/^sha256=/, '');
const a = Buffer.from(expected, 'hex');
const b = Buffer.from(received, 'hex');
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return res.status(400).send('bad signature');
}
const event = JSON.parse(req.body.toString('utf8'));
if (event.event === 'SUCCESS') {
// Idempotent: this may arrive more than once.
fulfilOrder(event.orderId, { amount: event.amount, utr: event.utr });
}
res.sendStatus(200); // acknowledge fast; do slow work in the background
});
Two rules that prevent almost every integration bug:
- Be idempotent. A webhook can be delivered more than once. Key your
fulfilment on
orderIdand do nothing if it is already fulfilled. - Check the amount. Compare
event.amountPaiseagainst what the order should cost before shipping anything.
Step 5 — Test it
Cashvya verifies payments against real bank notifications, so the honest way to test is a real payment of a small amount to yourself.
- Create a payment for
"1.00". - Pay it from your own UPI app.
- Watch the dashboard move the payment to SUCCESS, then confirm your endpoint received the webhook.
- Check Developers → Webhook deliveries for the response your server gave. A non-2xx is retried.
Secret key in environment variables · webhook signature verified · fulfilment idempotent · amount checked · bank account and UPI ID saved in the dashboard so settlements are not held · two-factor authentication on your dashboard login.