Indian e-commerce closed the Diwali 2025 window at roughly ₹60,700 crore in gross merchandise value — a 23% jump on Diwali 2024, with order volumes up 24% per Datum Intelligence's wrap-up. Quick commerce alone grew 120%. We spent that week on call with eight SMB clients and saw the same four failure patterns repeat. This post is the post-mortem — and the exact fix list we are walking the next batch of clients through before Black Friday-Cyber Monday hits in 24 days.
TL;DR — the 4 bottlenecks in 60 words
Diwali 2025 broke four things on SMB stacks at scale: (1) cart pages that timed out at >2.5 sec TTFB during 7-9 pm peak, (2) Razorpay checkout 5xx bursts traced to webhook backlog and missing idempotency keys, (3) inventory oversells on flash SKUs because reservation locks were not held during checkout, (4) COD form spam from competitor bots and bored students that ate 18-31% of fulfilment capacity.
Why this matters now — Black Friday is November 28
Black Friday 2025 is Friday November 28, with Cyber Monday on December 1. For Indian SMBs serving NRI buyers (US-EU stack) and the Indian-domestic crowd that has now adopted BFCM as a real shopping window, the same four breakages will hit again. Diwali revealed them under controlled load. BFCM is a slightly different curve — longer tail, more international cards, more BNPL — but the failure modes overlap by ~80%. Fix them in the next three weeks or pay for them on the 28th.
Triple Whale's BFCM 2025 checklist and Shopify India's 25-step prep both recommend running a test transaction across every payment method and every BNPL provider — twice. Most SMBs we audit have run it once on a Tuesday morning, and never under simulated peak load.
The 4 failures, in priority order
Bottleneck #1: Cart timeouts during peak (the silent killer)
The pattern we saw in 6 of 8 client incidents: between 7:00 and 9:30 pm on the Diwali-eve drop days, cart-page TTFB drifted from a healthy 380ms baseline to 2,400-3,800ms. Conversions on those carts fell to roughly 28% of the off-peak baseline. The Razorpay dashboard showed nothing wrong because the requests never made it that far.
The root cause in every case was the same: pessimistic database locks held against the cart_items or inventory tables while the cart page rendered. Under low load, you do not notice. Under 800 concurrent cart-views per second, every read waits behind every write.
The fix we ship for this:
Bottleneck #2: Razorpay 5xx bursts and webhook backlog
Razorpay shipped through Diwali 2025 well overall, but had three observable degradation windows lasting 5-15 minutes each on the peak nights. The platform itself is large enough to absorb the surge — the failures we triaged were almost always client-side, not Razorpay-side. Three patterns:
Pattern A: missing idempotency keys. Razorpay's checkout occasionally returns a 502 when their gateway is queued. A retry without an idempotency key creates a second order and, on capture, charges the customer twice. Fix: every order_create call must include a deterministic idempotency key (we use order_<userId>_<cartHash>_<timestamp> truncated to 36 chars). Razorpay deduplicates on the server side.
Pattern B: webhook backlog under load. When you receive 200 payment.captured webhooks per second and your handler does a synchronous DB write + email send + Slack ping, your handler chokes. Razorpay retries with exponential backoff for up to 24 hours, but during the surge your customers see "payment received but order pending" for 18 minutes. Fix: webhook handler does one thing — write to a queue (Redis BullMQ, SQS, or even a Postgres unlogged table). A separate worker drains the queue and does the heavy lifting.
Pattern C: not handling 5xx as "unknown state". A 5xx from Razorpay does not mean payment failed. It means you do not know. Treat the order as "pending verification" and reconcile via Razorpay's payments API within 30 seconds. We have seen SMBs auto-mark these as "failed" and re-send the customer to checkout, who then double-pays.
| Razorpay error pattern | Wrong reaction | Right fix |
|---|---|---|
| 502 on order_create | Retry with new request body, generating a duplicate order | Retry with same idempotency_key header; Razorpay deduplicates |
| 503 on payment_capture | Mark order as failed, resend customer to checkout | Mark order as pending, poll payments API in 30s, surface "verifying" UI to user |
| Webhook handler timing out (>30s) | Increase server CPU, add more workers | Webhook writes to queue only; downstream worker handles DB+email+notification |
| Settlement T+3 mismatch | Manually correct in Tally next morning | Daily n8n flow pulls Razorpay settlements API + matches against Tally vouchers (we cover this in our Nov 8 post) |
Bottleneck #3: oversold SKUs on flash drops
The Surat apparel client lost margin on Diwali eve because a 50-unit limited drop sold 217 units in 11 minutes. The application read stock count, deducted 1, wrote back, but did not hold a lock during the 8-second checkout window between "added to cart" and "payment confirmed". 800 carts saw "stock = 12", 800 carts succeeded, 167 customers were refunded the next morning with profuse apologies.
The fix has two layers.
Layer 1: reservation locks during checkout. When a user clicks "proceed to checkout", you decrement available stock immediately and create a 10-minute reservation. If the user pays, the reservation converts to a sale. If not, a background job releases the reservation back to available stock. This is the standard pattern from booking systems (BookMyShow has used it for over a decade).
Layer 2: oversell-as-feature for some SKUs. For non-flash SKUs where you can backorder, accept the oversell and set customer expectations: "Delivery within 7-10 days due to high demand". This converted 78% of would-be-cancelled orders for a Coimbatore D2C client. But — only for SKUs you can fulfill. Apparel with limited fabric, no.
-- The reservation pattern in PostgreSQL
BEGIN;
SELECT available_stock FROM inventory WHERE sku_id = $1 FOR UPDATE;
-- application checks: if available_stock >= requested_qty
UPDATE inventory
SET available_stock = available_stock - $2,
reserved_stock = reserved_stock + $2
WHERE sku_id = $1;
INSERT INTO reservations (sku_id, qty, user_id, expires_at)
VALUES ($1, $2, $3, NOW() + INTERVAL '10 minutes');
COMMIT; The FOR UPDATE is the magic. Postgres serializes any other transaction trying to read or write that row until this transaction commits. Latency increases by 4-8ms per checkout — totally acceptable.
Bottleneck #4: COD bot spam
A Surat apparel client posted a Diwali 70%-off catalog. By 4 am the next morning their fulfilment ops manager had 1,200 orders in the COD queue. Real fulfilment capacity was 850/day. The team picked up the phone to confirm orders — 73% of the calls reached numbers that hung up, said "wrong number", or did not answer for three attempts.
This is COD spam at scale, mostly driven by competitors and bored students. The cost is not refunds — it is opportunity cost. Genuine paying customers who would have ordered later in the week saw "out of stock" because the fake COD orders had reserved inventory.
The 4-step COD spam fix:
- OTP verification on the phone number for any COD order — not just for new accounts. Costs little per OTP via MSG91 or Razorpay's WhatsApp OTP, kills 90% of spam in week one.
- Rate-limit COD orders per phone+IP combo: max 3 active COD orders per 24h. Captures the script kiddies.
- Deposit of ₹100 (refundable on delivery) on COD orders above ₹2,500. The economic friction kills 95% of remaining spam.
- Sliding-window risk score: phone numbers from a single IP, sequential, and matching a known disposable-prefix list (jio.com numbers from a single block) get auto-routed to "verify before dispatch".
The same Surat client implemented these in 4 days post-Diwali. Pre-BFCM dry run showed 8% of incoming COD orders triggering "verify before dispatch", with 1.2% being confirmed spam. Genuine confirmation rate jumped from 71% to 94%.
The 6-week pre-BFCM stack audit (do this before Nov 27)
The cost comparison: prevention vs. remediation
When NOT to spend money on these fixes
Three cases where the audit is overkill.
You did under ₹40 lakh in Diwali GMV. At that scale your peak load is genuinely manageable on a modest VPS without exotic engineering. Fix the COD spam route and the idempotency keys; skip the load testing and the edge cache.
You sell only B2B with custom-quoted invoices. None of these failure modes hit you. Your "checkout" is a CFO emailing a PO. Spend the audit budget on a CRM upgrade instead.
Your stack is on a managed Shopify or WooCommerce host. Most of the cart-page and payment-retry logic is handled by the platform. You still need to fix COD spam (your problem) and confirm webhook handling on third-party apps (their problem, but you should verify). Skip the load test — Shopify load-tests itself.
Real example — a 22-person Indore retailer
Client: a sarees + jewellery retailer in Indore, 22 staff, Shopify Plus + custom checkout addon for COD. FY 2024-25 Diwali GMV: ₹1.8 Cr. Diwali 2025 target: ₹2.6 Cr.
- What broke on Diwali eve (October 17, 2025):
- 7:42 pm: cart page TTFB hit 3.4s. Conversion on the page dropped from 9.1% baseline to 2.6%. Lost sales for 90 minutes before they noticed.
- 8:21 pm: Razorpay returned 502 for ~6 minutes. Their checkout addon retried without idempotency key. 22 customers got double-charged. Refunds processed Wednesday afternoon.
- 11:04 pm: a flash 60%-off bridal lehenga drop oversold by 14 units (52 sold against 38 in stock). 14 calls to apologise; 11 customers downgraded to a different SKU; 3 cancelled and posted on Instagram.
Total Diwali-eve damage: lost margin + refunds, plus the un-quantifiable Instagram-post reputation hit.
Post-Diwali audit: cart page moved to a Cloudflare-cached shell + Redis stock counts (4 hours of work), idempotency keys added across the addon (6 hours), Razorpay webhook moved to queue+worker (1 day), reservation locks on flash SKUs only (8 hours), MSG91 OTP on COD orders > ₹2,500 (4 hours). Total engineering: 22 hours. Pre-BFCM dry run completed November 24 — synthetic load at 3x Diwali peak held p95 under 700ms.
We crosschecked against r/IndianStartups discussions on Diwali 2025 — the same four breakages dominated the post-mortem threads from D2C founders. Patterns repeat because Indian SMB stacks repeat.
A common question we get about webhooks
"If Razorpay's webhook handler is at risk of timing out during peak, why not just increase our server timeout to 60 seconds?"
Because Razorpay's webhook system itself times out at 5 seconds. If your handler does not return 2xx within 5s, Razorpay considers the delivery failed and retries. With 200/sec incoming during peak, your handler is now processing (synchronously) the original event and 4 retry copies. You will exhaust your worker pool in under a minute.
The queue+worker pattern is the only correct shape. The webhook handler does exactly one thing: validate the signature, push the payload to Redis or SQS, return 200. Everything downstream is async. The handler responds in 12-30ms regardless of load.
A pre-BFCM checklist (print this and stick it on your wall)
- k6 synthetic load test at 3x Diwali peak — p95 cart TTFB < 800ms, p95 checkout < 1.4s
- Idempotency key on every Razorpay order_create call (deterministic, not random)
- Razorpay webhook handler writes to queue only — no synchronous DB/email/Slack work
- 5xx from payment APIs treated as "pending verification", reconciled via payments API in 30s
- Inventory uses FOR UPDATE row locks during checkout; reservation table with 10-min TTL
- Hot SKU stock counts cached in Redis with 60s TTL on reads
- Cart page HTML shell cached at CDN edge for 60s; only per-user state hits origin
- OTP verification on every COD order (via MSG91 or WhatsApp)
- COD rate limit: max 3 active COD orders per phone+IP per 24h
- Refundable ₹100 deposit on COD orders > ₹2,500
- Grafana or Better Uptime dashboard with cart TTFB, payment success rate, webhook backlog
- 2-hour war-room rehearsal completed with founder, CTO, ops, support leads
FAQ
How much load should I size for above Diwali peak?
Three times your Diwali peak is our planning number for BFCM. The rationale: Diwali demand was already ~25% above last year, BFCM brings international traffic on top, and a single viral Instagram post can add a 5x burst on a 3-minute window. 3x peak gives you headroom to absorb that burst without scrambling.
Is it worth moving from Razorpay to Cashfree for BFCM?
Not in the next 24 days. Switching gateways during peak season is the worst possible time. Cashfree's 1.6% promotional pricing is genuinely competitive, but the integration risk during a high-revenue window outweighs the saving. Plan the switch for January 2026 if at all; for now, fix your idempotency and webhook handling on the Razorpay side.
What is the cheapest viable observability stack for an SMB?
Better Uptime free tier for synthetic checks every 3 minutes, Grafana Cloud free tier for metrics + dashboards (for <10k series), and Sentry free tier for error tracking. End-to-end visibility for free at SMB scale. Pay only when you need to.
My stack is WooCommerce. Do these fixes apply?
Most of them, yes. The Razorpay idempotency-key pattern is enforced via the official WooCommerce Razorpay plugin (verify it is on v3.x or higher). The cart-page caching is harder on WooCommerce — use WP Rocket or LiteSpeed Cache. The COD spam fix you build yourself with a custom plugin or use WooCommerce Anti-Fraud.
How do I monitor cart-page TTFB in production without paying for expensive monitoring?
Three free options. (1) Cloudflare Web Analytics — free, gives you p50/p95 TTFB by URL. (2) Vercel Analytics — free if you host on Vercel, gives Core Web Vitals. (3) Synthetic checks every 60s from Better Uptime free tier. Combine all three; you will see degradations within 3 minutes.
What if my Razorpay account does not have webhooks enabled?
You are flying blind on payment state. Enable them now via Razorpay Dashboard → Settings → Webhooks. Subscribe to at least: payment.captured, payment.failed, refund.processed, settlement.processed. The webhook URL is HTTPS-only; Razorpay will refuse cleartext endpoints.
Did Diwali 2025 break Razorpay's own infrastructure?
Razorpay handled the surge well overall — internally, peer agencies tracking the wider Indian gateway space saw 99.4% uptime across the Diwali window. The 5-15 minute degradations we observed were narrow and recoverable. The losses our clients suffered were almost entirely about how the client's code reacted to those windows, not about Razorpay's own resilience.
What to read next
For our follow-up post on the auto-reconciliation pattern that prevents the next-morning "Tally vs Razorpay disagreement" headache, see n8n + Tally Prime + Razorpay: Auto-Reconcile Daily Settlements. For the BFCM-specific stack-audit checklist we run for Razorpay-powered clients, see Black Friday Tomorrow: 6 Indian SMB Stack Checks.
This post was written by Vivek Kumar, who leads our Indian-SMB engagements. The on-call team for Diwali 2025 included Hrishikesh on the Razorpay+webhook side and Manvi running the QA load tests. We work directly with our web development and AI automation teams to build pre-festival audits like the one in section 5.
For a real D2C build at this scale, see our Radiant Finance case study — same payment-resilience patterns applied to a finance vertical.
Want a Pre-Festival Audit on Your Stack Before Next Year?
We run a 6-week pre-festival stack audit for Indian SMB e-commerce — covers cart-page resilience, Razorpay integration, inventory reservation, COD spam mitigation, observability, and a war-room rehearsal. Fixed-scope engagement. Suitable for SMBs with ₹1-25 Cr in annual GMV. We share the audit report whether or not you hire us for the implementation.
Book a Pre-Festival Stack AuditEmail contact@softechinfra.com if you want the full BFCM checklist as a Notion doc before our 20-min call.
