Cryptographic Webhooks and Payment Verification with Cashfree PG
Implementing atomic order reconciliation, HMAC-SHA256 signature verification, and preventing race conditions during slot checkout.
The Double-Booking Vulnerability in Payment Gateways
In e-commerce, payment gateways introduce asynchronous complexity. A customer clicks "Pay," enters their UPI PIN or card credentials, and several outcomes can occur simultaneously: - The customer's mobile browser is redirected back to our success URL. - The payment gateway's server dispatches an asynchronous webhook notification. - The customer closes their tab midway, but the bank confirms the debit 30 seconds later.
If our backend does not handle these asynchronous events with strict idempotency and cryptographic verification, two critical failures can happen: 1. **Slot Double-Booking**: Two customers paying around the same time could both be granted the same 15-minute hardware lease. 2. **Fraudulent Order Activation**: A malicious actor could spoof a browser redirect with `?verify=true` and trigger a printer slot without legitimate funds capture.
Verifying Cashfree Webhook Signatures via HMAC-SHA256
To guarantee authenticity, every webhook received from Cashfree is cryptographically validated using our secret key before database processing.
export function verifyCashfreeWebhookSignature(rawBody, signature, timestamp) { const secretKey = process.env.CASHFREE_SECRET_KEY; if (!secretKey || !signature || !timestamp) return false;
// Reconstruct exact signed payload: timestamp + rawBody const payload = `${timestamp}${rawBody}`; const expectedSignature = crypto .createHmac('sha256', secretKey) .update(payload) .digest('base64');
// Constant-time buffer comparison to prevent timing attacks return crypto.timingSafeEqual( Buffer.from(signature, 'utf8'), Buffer.from(expectedSignature, 'utf8') ); } ```
Idempotent State Transitions in PostgreSQL
When either the return callback or the background webhook arrives, the backend executes an atomic state transition:
export async function verifyOrderPayment(orderToken) {
return await prisma.$transaction(async (tx) => {
const order = await tx.order.findUnique({
where: { token: orderToken },
include: { slot: true },if (!order) throw new Error('Order not found'); // Idempotent guard: if already marked paid, return safely if (order.paymentStatus === 'PAID') { return { order, alreadyPaid: true }; }
// Query gateway API directly for definitive status const gatewayStatus = await checkCashfreeOrderStatus(order.cashfreeOrderId); if (gatewayStatus.order_status !== 'PAID') { throw new Error('Payment not confirmed by gateway'); }
// 1. Mark order paid const updatedOrder = await tx.order.update({ where: { id: order.id }, data: { paymentStatus: 'PAID', status: 'PAID', }, });
// 2. Firmly lock slot to prevent any future re-booking if (order.slotId) { await tx.slot.update({ where: { id: order.slotId }, data: { isAvailable: false, reservedUntil: null, }, }); }
return { order: updatedOrder, alreadyPaid: false }; }); } ```
This ensures that the printer slot is firmly locked, no duplicate session can be created, and the customer receives their verified 15-minute printing window immediately.
SHHHH maintains dedicated private printers across major metro cities in India. These machines are never utilized for commercial bulk runs, marketing collateral, or public print orders. Delivered nationwide across India in 5 to 6 working days in opaque, tamper-evident packaging.