A Hyderabad FMCG super-distributor moves ₹140 crore of goods a year across 37 brands — soaps, biscuits, packaged foods, beverages, personal care — to ~3,400 retailer customers from kirana stores to mid-tier supermarkets across Telangana, Andhra Pradesh, and northern Karnataka. Until April 2025 they ran the entire orderbook on 37 brand-by-brand WhatsApp groups, an Excel master, and a 9-person tele-calling team typing orders into a desktop ERP. We shipped a multi-vendor B2B marketplace that consolidated all 37 brands into one app, one checkout, one settlement engine.
Build cost ₹62 lakh. Order capture time went from 11 minutes per order (call + retype) to 90 seconds (retailer self-serve). This is the architecture and the trade-offs.
37
Brands on One Marketplace
~3,400
Active Retailer Buyers
Rs 140Cr
Annual GMV Routed Through Stack
Rs 62L
Fixed-Price Build (Phase 1, 18 Weeks)
## The Answer in 60 Words
We built a Next.js + React Native marketplace, a Node.js + Fastify API backed by PostgreSQL 16, a settlement engine that routes payments across 37 brand virtual accounts via Razorpay Route, BullMQ-backed reconciliation workers, and a Metabase analytics layer. ~3,400 active retailers, ~94,000 orders/month, ₹140 cr annual GMV. Build ₹62 lakh, run ₹2.4 lakh/month including payment processing fees on the distributor side.
## Why This Matters Now
Indian FMCG distribution has been pulled in three directions since 2023. ONDC (Open Network for Digital Commerce) hit ~12 million transactions/month by mid-2025 and forced even traditional distributors to think about API-first ordering. Brand-direct platforms (Udaan, Jumbotail, ElasticRun) have squeezed super-distributor margins. And kirana retailers — the demand side — got smartphone-first during COVID and never went back to paper. A 37-brand super-distributor running on WhatsApp groups in 2025 is leaving 8-12% of GMV on the table to faster competitors. The build was defensive as much as offensive.
The other shift is on the payment side. [Razorpay Route and Razorpay X](https://razorpay.com/blog/instant-settlement-switching-india) (and the equivalent products from Cashfree, Easebuzz) made multi-vendor settlement programmable in a way it was not three years ago. Pre-Route, splitting a ₹14,000 retailer order across 5 brands meant manual T+2 reconciliation. Post-Route, it is automatic per-transaction.
## The Client (Specific Details)
-
Sector: FMCG super-distribution (multi-brand stockist + sub-distributor model)
-
Location: HQ in Secunderabad, 4 warehouses across Telangana + AP + N. Karnataka
-
Brand mix: 37 brands — 22 daily-essentials (soap, biscuit, atta), 8 personal care, 4 beverages, 3 emerging (D2C food brands), all FMCG goods, no perishables
-
Retailer customer base: ~3,400 active (ordered in last 90 days), ~6,200 registered
-
Order shape: average ticket ₹4,800; 24% of orders > ₹10,000; 8% of orders > ₹25,000
-
The trigger: A brand principal (large soaps player) issued a Q1 2025 ultimatum: digital orderbook by August 1 or the brand moves to a competitor distributor. The founder gave us 18 weeks.
## The Architecture (One Diagram Worth Drawing)
RN
Retailer App: React Native
Browse 37 brand catalogues in one feed, add to cart, place order. Hindi + Telugu + English. Voice search for brand names (kirana shopkeepers prefer voice over typing). ~78% of orders now come via the app.
FE
Distributor + Brand Console: Next.js
Distributor sees the full pipe. Each brand sees only their SKUs, their inventory, their settlement. RBAC at the API layer with brand_id scoping enforced everywhere.
PAY
Payment Routing: Razorpay Route
Each brand has a Razorpay Route account. A retailer order is split per-brand at checkout time. Razorpay handles the per-brand settlement; our settlement engine reconciles against expected splits nightly.
DB
DB + Workers: Postgres 16 + BullMQ
Postgres for the truth-of-record. BullMQ for: order-routing (warehouse selection), inventory-reservation, payment-reconciliation, brand-settlement-attribution, daily MIS rollup. ~340,000 jobs/day at peak.
## The Settlement Engine (The Hardest Part)
A typical retailer order has 12 SKUs across 4 brands. Some brands have a 7-day credit term, some 14, some immediate. Some brands settle to the distributor, some direct to the brand. The distributor takes a per-brand commission, plus a cash-discount adjustment, plus GST handling. Pre-marketplace, this was a 4-person back-office task running on Excel for 8 hours a day. Post-marketplace, it runs as a pipeline that takes 14 minutes a day for the back-office to review and approve.
The pipeline:
1
Order placed at checkout
Retailer hits "place order" with 12 SKUs. We compute the per-brand sub-total, per-brand GST, per-brand commission, per-brand settlement instruction. Razorpay Route order created with N split rules (one per brand). Total computed time: ~280ms.
2
Payment captured (or credit note issued)
Cash payments via UPI/card route to the brand sub-accounts directly. Credit-term orders generate an invoice with the appropriate due-date and trigger no immediate movement. Webhook from Razorpay confirms capture; we mark the order as paid.
3
Reconciliation worker (nightly)
For every order in the day, fetch the actual settlement record from Razorpay's settlement API. Compare against expected splits stored at checkout time. Flag discrepancies (typically: refunds, partial captures, Razorpay fee absorption rules). Output a 30-row review queue for the back-office.
4
Brand settlement attribution
Per brand, per day, generate a settlement statement. Show: gross sales, returns, commission deducted, net payable, GST liability. Brand console download as PDF or Excel. Auto-emailed at 9 am daily.
5
Audit log immutable
Every state transition (order placed, paid, dispatched, delivered, returned, refunded) writes to an append-only audit table. ~14 GB/year. Used for the brand-side audits and for our own incident forensics.
## Webhook Idempotency (Where We Earned Our Money)
Razorpay's webhook spec is honest about it:
events may be delivered more than once. [Their best-practices doc](https://razorpay.com/docs/webhooks/best-practices/) is explicit: build idempotent handlers, dedupe on event id, expect out-of-order delivery, respond 2xx on success.
Our handler enforces three invariants:
-
Dedupe by Razorpay event id. Postgres unique index on
razorpay_event_id. Re-delivery is a no-op.
-
Order-state transitions are idempotent. Setting order.status = 'paid' twice is fine. Moving it from 'paid' to 'pending' is not allowed; the handler refuses.
-
Settlement amounts are computed, never overwritten by webhook payload. The webhook tells us a payment was captured. The amount we record comes from our own checkout-time computation, not from the webhook body. This protects against a bug or a malicious actor setting a different amount in the payload.
We have processed ~1.4 million webhook events without a single double-spend or missed-payment incident. The discipline matters at this scale.
## Multi-Brand Catalogue: The Other Hard Part
37 brands x ~140 SKUs per brand = ~5,200 SKUs in the catalogue, each with its own pricing, packaging unit, schemes, MOQ, and brand-side promotional rules. The catalogue is the second-most-complicated part of the system after settlement.
Our approach:
-
Brand-controlled product master. Each brand updates their own SKU master via a CSV upload or API. The distributor cannot edit brand products directly — they can only enable/disable for their region.
-
Distributor-controlled regional pricing. The distributor sets per-region pricing on top of brand MRP, within brand-set guard rails (cannot go below brand floor price).
-
Time-bound schemes. "10+1 free on Surf Excel 1kg from 1-15 August" expressed as a scheme rule with a start date, end date, condition, and outcome. Applied at cart time. Promo logic alone is 480 lines of code.
-
MOQ enforcement at cart. A retailer cannot place an order below the brand-set minimum. If they have 3 brands at MOQ-OK and 1 brand below, the cart shows the warning per-brand, not at the cart level.
## The 18-Week Plan
- Weeks 1-2: Discovery — interview 8 retailers, 12 brand contacts, 4 warehouse managers; map the existing WhatsApp + Excel + ERP flow end-to-end
- Weeks 3-5: Schema design (the catalogue and settlement schemas alone took 10 days of debate), authentication, brand-onboarding tools
- Weeks 6-8: Retailer app v1 — browse, cart, checkout in English first; payment integration with Razorpay Route
- Week 9: Settlement engine v1, reconciliation worker, brand console
- Weeks 10-11: Multi-language (Hindi, Telugu) for the retailer app; voice search; offline-cache for spotty rural networks
- Week 12: Brand catalogue self-serve uploads, scheme rules engine, MOQ guards
- Week 13: Warehouse fulfillment workflow — picking lists, dispatch confirmations, delivery proof
- Week 14: Returns + credit notes — including the messy partial-return-of-bundled-scheme case
- Weeks 15-16: UAT with 2 brand teams + 12 hand-picked retailers; iterate on scheme rule edge cases
- Week 17: Soft launch with 4 brands, 200 retailers, ~₹4 cr GMV in 5 days
- Week 18: Full launch — all 37 brands, all 3,400 active retailers
## Common Mistakes (Each One Hurts)
Symptom: "Brand settlement is off by Rs 14,000." Cause: Razorpay fee deducted from the settlement but expected-split computation did not subtract it. Fix: read the Razorpay settlement API carefully — fees can be deducted from the platform side or from the linked-account side depending on configuration. Match your config exactly.
Symptom: "Retailer placed an order then complained 'I did not pay'." Cause: webhook arrived late and the app showed a stale "pending" state. Fix: poll the order status from the app on a 30-second interval until terminal state. Do not rely on push alone for payment confirmation.
Symptom: "Two brands are arguing over a returned crate." Cause: returns hit a single inventory bucket and were attributed to the wrong brand. Fix: every return record carries the brand_id from the original order. Audit log shows attribution. Run a reconciliation report weekly with brand-side counterparts.
Symptom: "Some retailers cannot complete UPI payment." Cause: Razorpay's UPI flow occasionally fails on older Android handsets. Fix: enable both Razorpay Standard Checkout (in-app SDK) and a hosted Web Checkout fallback. Detect failure modes and switch automatically.
Symptom: "Daily MIS report takes 14 minutes to load." Cause: aggregation across 5,200 SKUs x 3,400 retailers x 30 days is slow on the live transactional DB. Fix: use a Postgres logical replica for analytics, materialise the daily-rollup on a cron, serve from the materialised table for sub-second loads.
## When NOT to Build a Multi-Vendor Marketplace
Skip the build if (a) you carry under 8 brands and your order volume is under 200/day — a vanilla B2B WhatsApp commerce setup will deliver 80% of the value at 5% of the build cost, (b) your brand principals refuse to accept programmable settlements — a Razorpay-Route style stack requires brand-side bank-account onboarding which can take 2-4 weeks per brand, or (c) your sales team is your competitive advantage and digital self-serve will erode that. Some categories (premium spirits, regulated goods) need the conversation.
For our earlier work on a Diwali-cracker multi-vendor marketplace, see [Diwali Crackers Multi-Vendor Marketplace](/blog/diwali-crackers-multi-vendor-marketplace-9-sellers-1-checkout). Same architectural pattern at smaller scale (9 sellers vs 37 brands). The Razorpay Route integration carries over almost identically.
The honest caveat on multi-vendor: the operational burden post-launch is real. Brand onboarding takes 2-4 weeks each. Scheme rules drift constantly. Returns reconciliation needs a dedicated person. Budget 2 FTE on the distributor side for ongoing operations from day one. Pretending the platform "runs itself" is the most common reason these projects underperform.
## Real Outcome (10 Months Post-Launch)
90 sec
Median Order Capture (was 11 min)
+18%
GMV Growth Over Pre-Launch Baseline
14 min
Daily Reconciliation (was 8 hr)
94%
Retailer Adoption (Active Last 30 Days)
The +18% GMV is the number the brand principals cared about — proof the digital orderbook captured incremental orders, not just shifted existing ones. The 90-second median capture time meant the tele-calling team of 9 dropped to 3 (the others moved to brand-acquisition and credit-collection roles, no layoffs).
## A Detail That Saved Us On Day 41
On day 41 of UAT, a brand-side promo rule was misread by the scheme engine: "10+1 free on Surf Excel 1kg" was applied as "+1 unit at MRP" instead of "+1 unit free." 14 retailers were billed an extra ₹136 each. We caught it in our daily back-office review (the 14-minute one). We refunded all 14 within 2 hours, posted credit notes by EOD, and added a property-based test that synthesises 1,000 random scheme combinations and asserts the cart total never exceeds the sum of (unit_price x quantity). Eight months later we have not had a similar incident.
The lesson:
marketplace promo rules will eventually betray you. Test the cart total invariant, not just the rule logic.
## The Stack Cost Per Month (Real Numbers)
| Component | Monthly cost | Notes |
|---|---|---|
| Hetzner CCX43 (app + Postgres primary) | Rs 13,200 | 16 vCPU / 64 GB RAM, oversized for headroom |
| Hetzner CCX23 (read replica + Metabase) | Rs 3,800 | Analytics workload isolated |
| Hetzner CX22 ×3 (BullMQ workers) | Rs 2,220 | Settlement, reconciliation, MIS workers |
| Backup S3 (Cloudflare R2) | Rs 4,800 | 240 GB hot + 7 years archive |
| Razorpay Route fees | Rs 2.1 Lakh | ~1.7% of ~Rs 11.6 cr/month GMV (transactional) |
| Twilio + Gupshup (SMS + WhatsApp) | Rs 18,400 | Order updates, payment reminders |
| Sentry + Logtail | Rs 5,800 | Error + log infrastructure |
| Engineering retainer (0.6 FTE) | Rs 42,000 | Higher than a CRM because of payment-side complexity |
|
Total |
~Rs 2.4 Lakh | Of which ~85% is Razorpay transaction fees |
The transaction fees scale with GMV. The fixed infrastructure cost is ~₹35,000/month — the rest is variable on volume.
## FAQ
### Why Razorpay Route and not Cashfree Easy Split?
We evaluated both. Both work for this use case. Razorpay Route had stronger settlement-API documentation at the time we built (Q2 2025) and the founder already had a Razorpay account. Cashfree's Easy Split has a slightly cleaner sub-account onboarding flow which we would consider for a new build today.
### How did you handle GST for cross-state retailers?
State-wise GSTIN validation at retailer onboarding. CGST/SGST split for intra-state orders, IGST for inter-state. The brand's GSTIN is the seller of record on the invoice; the distributor's GSTIN appears as the consignor. Two-tier invoice generation with a dedicated GST library. We use [ClearTax's e-invoicing API](https://cleartax.in/) for IRN generation when above the threshold.
### What about credit-terms management?
Credit-term orders generate an unsettled invoice with a due date. A separate credit-collection workflow runs at T+5, T+12, T+20 days for 7-day terms (escalating). Credit limits are enforced at order placement — a retailer at credit limit cannot place a new credit-term order until they pay down.
### How did you onboard 37 brands?
A 4-step onboarding: (1) brand legal contract + commission rate schedule, (2) Razorpay Route sub-account creation with brand bank details, (3) brand catalogue upload via CSV template + brand UAT in sandbox, (4) brand-team training on the brand console. Median brand onboarding time: 18 days from contract to live.
### Did you replace the existing ERP?
No. The ERP stays for accounting, GST, and finance functions. The marketplace pushes daily summary entries into the ERP via a webhook. The two systems live alongside; the marketplace is the truth of orders, the ERP is the truth of books.
### What about offline orders from tele-calling?
The 3-person tele-calling team uses the distributor console to enter orders on behalf of retailers. Same checkout, same payment flow. ~22% of orders still come via the tele channel — typically larger orders or new retailers being onboarded.
### Do retailers actually use the voice search?
Yes — about 31% of search queries are voice. Especially for brand names where the spelling is non-obvious in Telugu transliteration. We use the device's native speech-to-text, not a server-side STT, to keep latency low and avoid cloud cost.
### What was the team composition?
Three full-stack engineers, one engineer dedicated to payment + settlement (the hardest piece), one mobile engineer (React Native), [Manvi](/team/manvi) at 0.6 FTE for QA on the financial flows, our designer at 0.4 FTE, and our founder [Vivek](/team/vivek-kumar) at 0.2 FTE for client direction. Total 5.4 FTE for 18 weeks.
## Related Reading
Need a Multi-Vendor Marketplace for Your Distribution Business?
We ship multi-vendor B2B marketplaces with Razorpay Route settlement, brand-self-serve catalogues, and reconciliation workers for Indian distributors and aggregators in 14-20 weeks, fixed price. Typical project: ₹38-90 lakh. First call is technical, with the engineer who would lead your build. Suitable for 5+ brand operators with ₹20+ cr annual GMV.
Book a Marketplace Scoping Call