~learn.

Side quest 32Open commerce network

ONDC.

One Shop, Many Buyer Apps, No Shared Database: Commerce as a Protocol Between Companies

EXPERT54 MIN READ6 PHASES · 45 MIN INTERVIEW+120 XP

Mission brief

Design an open commerce network where buyers, sellers and couriers each pick their own app

Quest log0/7 stages · 0/9 checkpoints · 0/8 quick checks
transactions on the network by 25 July 2026, four years after the first order on 29 April 2022 (ONDC)
500 million
transactions in FY 2025–26 alone (ONDC, FY 2025–26 annual newsletter)
218 million
retail sellers live, in 630+ cities and towns, as of 9 December 2025 (Lok Sabha written reply, PIB)
1.16 lakh+
delivery executives in 250+ cities on the network's logistics (ONDC, FY 2025–26)
50,000+

Company names and logos are trademarks of their owners, shown only to identify each company. This course isn't affiliated with or endorsed by any of them.

watch it first · newThe whole design in a 16-minute animated lecture4 quiz checkpoints · ask the AI about any moment · captions, chapters and speed
Optional · timed run · 6 phases · 45 min

Want it harder? Hide the model answer and work each phase in a scratchpad against its time budget, then reveal and score yourself. Or simply read on: the checkpoints below work either way.

Intro · 5 min

How Do You Run One Order Through Four Companies That Don't Trust Each Other's Databases?

It is Dhanteras evening in Moradabad, the night families buy brass and silver for luck. Rehana Ansari, 41, runs Ansari Brass Works from a narrow shop near the old market: puja thalis, diyas, small idols, polished by her brother in the back room. Two years ago she listed her stock on a seller app her trade association recommended. She has never built a website, and she has never heard of most of the apps her orders come from.

At 7:41 pm her tablet chimes: a puja thali, ordered from Pune through a payments app. At 7:43, diyas for a family in Kolkata, through a bank's shopping tab. At 7:44, two people tap "Add to cart" on her last brass Lakshmi–Ganesh set within a second: one in Lucknow on a grocery app, one in Jaipur on a loyalty marketplace. Rehana sees one order for it; the Jaipur buyer sees "Sorry, this item is no longer available".

The Pune buyer has a worse minute. She pays, the screen says "Confirming your order with the seller…", and the spinner keeps spinning. She taps "Place order" again. Two minutes later: "Order placed". Rehana's tablet shows one thali order from Pune, not two. At 8:10 a rider from a small Moradabad delivery company reads out a pickup code and takes the parcels. None of these companies runs the others. They agree on nothing except a protocol, a registry of who's who, and rules for money and complaints.

ONDC, from published numbers
  • 29 April 2022: the first order on the network, "a handful of dhaniya, delivered in Bengaluru"; 1 million+ transactions by May 2023, 10 million+ by August 2023, 100 million+ by August 2024, 250 million+ by June 2025 and 500 million on 25 July 2026 (ONDC, "Our journey")
  • 218 million transactions in FY 2025–26 (ONDC, FY 2025–26 in Focus, 2026)
  • 15.4 million+ orders in December 2024, about 4.9 lakh a day on average; 154.4 million+ cumulative by then; 7.64 lakh+ sellers and service providers registered across 616+ cities, as of January 2025 (PIB factsheet, February 2025)
  • 1.16 lakh+ retail sellers live from 630+ cities and towns, as of 9 December 2025 (Minister of State for Commerce and Industry, Lok Sabha written reply, PIB, 16 December 2025)
  • ~1.2 million transactions in the Diwali week of 6–13 November 2023, from 600+ cities (Business Today, 17 November 2023)
  • 50,000+ delivery executives across 250+ cities; regional and small fleet operators carry 35% of fulfilled logistics orders (ONDC, FY 2025–26); 50+ logistics partners live by June 2026 (ONDC)

For context:
A "transaction" on ONDC is not only a retail order: it includes ride-hailing, metro and bus tickets and financial products. In October 2024, for example, Inc42, citing a Moneycontrol report based on an unnamed industry source, put the month at 14 million transactions, 5.5 million of them mobility. ONDC's FY 2025–26 newsletter reports "over 300,000 tickets per day" across metro and bus services
Rehana, her shop and her orders are illustrative; the buyer apps in her story are described, not named
ONDC publishes its protocol, its API contracts, its settlement and grievance frameworks and its onboarding process openly on GitHub and its website. Those are the published parts. How each participant builds its own app is not published, and everything labelled as our design or an assumption is illustrative

This is the system we'll design today.

ExplainerThe whole quest: ONDC

ONDC reached 500 million transactions on 25 July 2026, with 218 million in FY 2025–26 (rides, tickets and financial products included) and 1.16 lakh+ retail sellers live in 630+ cities and towns. That is only ~7 transactions a second on average; on stated assumptions an order takes ~25 signed calls, and the real load is search, a broadcast to ~150 seller apps per city. A gateway multicasts searches only; select, init, confirm and everything after go peer to peer between buyer and seller apps as an ACK now and an on_ callback later, every message signed with Ed25519 over a BLAKE-512 digest and verified against the registry. Buyer apps serve search from catalogue caches fed by full refreshes and 1–30 minute pushes. The buyer app names each order with a network-unique id; the seller app holds it and releases it to the shop only when its on_confirm is acknowledged, and silence ends in a 999 or 998 cancellation. The seller app buys delivery on the same network and cascades its states, and a settlement agency moves money only when both sides' /settle instructions match: in ONDC's example a ₹1,050 order sends ₹800 to the seller. The edge cases are a festival evening, a complaint routed over IGM and a seller app that ACKs confirms but never answers.

The Interview Begins#

You're interviewing at a company that wants to join an open commerce network as a seller-side platform. The interviewer shares two screenshots. The first is a seller's order list on a tablet: four orders in four minutes, each tagged with a different buyer app's name. The second is a buyer's phone showing "Confirming your order with the seller…" under a spinner.

Interviewer

"These four orders came through four different companies' apps, into a fifth company's app, and they'll be delivered by a sixth. Nobody in this picture shares a database with anyone else. Design the network: how a buyer on any app finds this seller, how an order comes to exist on both sides, how delivery is bought, how the money moves, and what happens when something breaks."

Design an open, unbundled commerce network

Buyer-side apps (any company with users: a payments app, a bank, a grocery app) and seller-side apps (platforms that onboard sellers and hold their catalogues) are run by different companies. Any buyer app must be able to discover any seller on any seller app and place an order. Delivery is bought by the seller app from logistics providers on the same network. Payment is collected by one party and settled to the others. Complaints raised on the buyer's app must reach whoever can fix them. A network operator runs only a small set of shared services: a registry of participants, gateways for discovery, and the rules.

Requirements
  • One protocol for search, select, init, confirm, status, track, update, cancel and return, between any two participants
  • Discovery across all seller apps for a domain and city, without every search hitting every seller app
  • Every message authenticated and tamper-evident, between parties that have never met
  • An order that both sides agree exists, with the same items, price and state, through retries and failures
  • Logistics as another participant, with delivery status flowing back to the buyer
  • Settlement and reconciliation of each order's money between collector, receiver and seller, and refunds
  • Grievances that cross company boundaries, and a view of the network's health that leaks nobody's data
Interviewer

"Payments themselves are UPI, cards and wallets; assume they exist. The UPI problem is a separate one. I want the network: the protocol, the trust, the consistency across companies, and the money after the payment."

People often call ONDC "UPI for commerce", and the analogy was there from the start: Nandan Nilekani, an adviser to ONDC, wanted to do for e-commerce what UPI had done for payments (Bloomberg, April 2022). The analogy is useful for the shape (many apps on one set of open rails, with a neutral operator in the middle) and misleading for the mechanics. A UPI payment is one atomic debit and credit routed through a central switch; we designed that in UPI (Bonus Problem 1). An ONDC order is a conversation between companies that lasts from a search to a return window days later, with no central switch in the middle of it at all.

Phase 1 · Stage 1 of 7 · 5 min budget · 3 min read

Phase 1: Requirements Clarification (5 minutes)#

You

"Before I draw anything I want to know what's central and what isn't, because that decides where consistency can come from."

Your Questions#

Transcript
You

"Does the network operator see every order?"

Interviewer

"No. ONDC runs a registry of participants and gateways for search. ONDC's own FAQ says only discovery messages go through a gateway; after that, /select, /init, /confirm and the rest go directly between the buyer app and the seller app. The gateway multicasts searches and 'does not send responses back': seller apps reply directly to the buyer app."

You

"So there's no shared order database anywhere. How does a seller app know a request really came from the buyer app it claims?"

Interviewer

"Right, none. Every request and callback is signed with the sender's Ed25519 key over a BLAKE-512 digest of the body, and the receiver looks up the sender's public key in the registry. ONDC's signing document treats each request/callback pair as a 'contract' between two parties."

You

"How big is it?"

Interviewer

"218 million transactions in FY 2025–26, across retail, mobility, transit tickets and financial services; the government put December 2024 at 15.4 million orders, about 4.9 lakh a day. On the retail side, 1.16 lakh+ sellers were live in 630+ cities and towns in December 2025. The Diwali week of 2023 saw about 1.2 million transactions; assume festival evenings are your peak."

You

"Who collects the buyer's money?"

Interviewer

"It's negotiated per order: the buyer app or the seller app can collect, and for cash on delivery the logistics agent does. In retail the buyer app usually collects and settles with the seller app through a settlement agency, under ONDC's Reconciliation and Settlement Framework."

Functional Requirements#

Functional requirements
  • Onboarding: a participant registers its domain name, roles, domains, cities and public keys with the registry, proves it controls the domain, and is certified by its logs before going live
  • Discovery: a buyer app's search reaches every seller app registered for that domain and city; seller apps answer with catalogues directly to the buyer app; catalogues can be pulled in full and refreshed incrementally
  • Ordering: select (cart and quote, including delivery charge), init (billing, fulfilment and payment terms), confirm (the order), all as request and callback pairs
  • Post-order: status, track, update (part cancel, return), cancel; the seller app pushes every state change unsolicited
  • Logistics: the seller app buys delivery from logistics providers through the same protocol, and their states flow back into the retail order
  • Money: collector and receiver submit settlement instructions to a settlement agency; mismatches are reconciled between them; refunds and disputes have a path
  • Complaints: an issue raised on the buyer app is routed to the seller app and, if needed, the logistics provider, with response and resolution deadlines

Non-Functional Requirements#

Non-functional requirements
Trust
  • Every message signed by its sender and verified by its receiver; keys rotate without downtime
  • No participant can read another participant's data except what the transaction needs
Correctness across companies
  • An order exists on both sides with the same id, items, quote and state, or on neither
  • Every request and callback is idempotent; retries never create a second order, charge or settlement
  • Money for each order matches on both sides before it moves
Resilience and latency (design targets)
  • A slow or broken participant affects only its own transactions
  • Callbacks within the message's ttl (the contract's examples use PT30S); search results usable within ~2 s from cached catalogues
  • Settlement daily; complaints resolved within the legal limit of 30 days

Phase 2 · Stage 2 of 7 · 5 min budget · 4 min read

Phase 2: Back of the Envelope Estimation (5 minutes)#

You

"My anchors are published: ONDC's 218 million transactions in FY 2025–26, the government's 4.9 lakh orders a day in December 2024 and 1.16 lakh retail sellers live, and ONDC's recommended registry rate limits. ONDC doesn't publish per-participant traffic, seller-app counts per city, catalogue sizes or message counts, so those are assumptions, and I'll label them."

Your turn · checkpoint 1not answered

Before the numbers: how busy is the network, really?

218 million transactions in a year: how many is that a second, on average? Suppose a festival evening runs at 10× the average. Then count the messages: a retail order needs select, init and confirm, each with a callback, plus about five status updates, and the seller app runs its own search, init and confirm with a logistics provider. Roughly how many signed HTTP calls does one retail order take? Where, then, is the real load?

Think it through, or jot it down. Then compare.

One strong answer

  • Orders: 218 million ÷ 365 ≈ 600,000 a day ≈ 7 a second on average. A 10× festival evening is ~70 a second across the whole network. Tiny by the standards of a single big marketplace.
  • Messages per order (assumed): retail select, init, confirm and their callbacks (6) + ~5 unsolicited status callbacks + logistics search with ~4 replies, init, confirm and their callbacks (~9) + ~5 logistics status callbacks ≈ ~25 signed calls, each answered first by an ACK.
  • So a 70-orders-a-second evening is ~1,750 signed calls a second spread over hundreds of participants. Orders aren't the load.
  • The load is search and catalogues: a single live search is multicast to every seller app in that domain and city. That fan-out, not ordering, is what the design has to tame.

Orders and Messages#

WorksheetThe ordering path (assumptions labelled)
Value
Transactions, FY 2025–26 (ONDC) 218 million
Average a day / a second (our arithmetic) ~600,000 / ~7
December 2024 orders, average a day (PIB) ~4.9 lakh
Festival-evening peak (assumed 10× average) ~70 a second
Design target (assumed 3× that peak) ~200 orders a second
Signed calls per retail order, incl. logistics (assumed) ~25, each with an ACK
Signed calls at the design target 200 × 25 ≈ ~5,000 a second, network-wide

Conclusion: the ordering path is small in volume. What makes it hard is that every one of those calls crosses a company boundary, can be lost, retried or arrive late, and has to leave both sides agreeing.

Search Fan-Out and Catalogues#

WorksheetWhy live search can't be the default (assumptions labelled)
  • Seller apps per domain and city: not published; assume ~150 for a large city's grocery domain
  • Live search: each buyer search goes through a gateway to all ~150 and comes back as up to ~150 on_search callbacks. At an assumed 1,000 searches a second on a festival evening, that's 150,000 requests a second into seller apps, and every search waits for the slowest
  • Catalogue size: 1.16 lakh retail sellers × an assumed ~300 items ≈ 35 million items; at ~3 KB of JSON each ≈ ~100 GB
  • Caching instead: pull each city's catalogue in full once a day, then take changes: if ~2% of items change a day (assumed), that's ~700,000 item updates a day per caching buyer app

Conclusion: search is a broadcast, and broadcasts don't scale per query. Pay the broadcast once per city per refresh, cache the catalogue at the buyer app, and serve searches from the cache. That is what ONDC's retail contract does.

Keys, Registry and Money#

WorksheetTrust and settlement (published limits; volumes assumed)
  • Registry lookups: ONDC recommends a limit of 7,600 a minute for /lookup (~127 a second). A seller app taking 1,000 signed calls a second must cache public keys by (subscriber_id, unique_key_id)
  • Signature checks: an Ed25519 verification costs tens of microseconds on one core (a general figure); crypto isn't the bottleneck, key distribution is
  • Settlement: one daily cycle. If ~half of ~600,000 daily transactions settle through the framework (assumed), the settlement agency matches ~600,000 order lines a day, one from each side per order, then nets them into one batch

Conclusion: trust is cheap per message unless every message calls the registry; money is a daily batch whose difficulty is matching two companies' books, not throughput.

Key Metrics Summary#

Metric Value
Transactions in FY 2025–26 (ONDC) 218 million, ~7 a second on average
Orders in December 2024 (PIB) 15.4 million+, ~4.9 lakh a day
Retail sellers live (Lok Sabha reply, December 2025) 1.16 lakh+, 630+ cities and towns
Design target for orders (assumed) ~200 a second, ~25 signed calls each
Live search fan-out (assumed) ~150 seller apps per domain and city
Registry /lookup limit, recommended (ONDC) 7,600 a minute
Settlement cycle (ONDC RSF) daily
Quick check3 questions · +5 XP each

Requirements and estimation

  1. Q1/3 ONDC reported 218 million transactions in FY 2025–26. Roughly how many is that a second on average?

    Answer & explanation

    Answer: B

    218 million ÷ 365 ÷ 86,400 ≈ 7 a second. Even a 10× festival evening is ~70 a second.

  2. Q2/3 Why can't every buyer's search be a live broadcast to all seller apps?

    Answer & explanation

    Answer: B

    Broadcast cost grows with searches × seller apps. Paying it once per catalogue refresh and serving searches from a cache removes it from the query path.

  3. Q3/3 The registry's recommended /lookup limit is 7,600 a minute. What follows for a busy seller app?

    Answer & explanation

    Answer: C

    ~127 lookups a second can't cover thousands of verifications a second; a key cache keyed by subscriber and key id can.

Phase 3 · Stage 3 of 7 · 10 min budget · 4 min read

Phase 3: High-Level Design (10 minutes)#

Your turn · checkpoint 2not answered

Sketch it first: the Pune thali, from search to rider

The buyer in Pune searches "brass puja thali" in her payments app. Where do the results come from, and did her search reach Rehana's seller app at that moment? She adds the thali to her cart: who quotes the delivery charge, and who did they ask? She pays: which company holds her money, and who tells Rehana's seller app it's paid? Her app sends "confirm" and hears nothing for 30 seconds: what must not happen? And at 8:10 the rider arrives: whose order is he fulfilling?

Think it through, or jot it down. Then compare.

One strong answer

  • Results come from the payments app's own catalogue cache, filled earlier by catalogue refreshes from Rehana's seller app. Her search didn't touch Rehana's seller app.
  • The quote: /select goes straight to Rehana's seller app, which checks live stock and, acting as a logistics buyer, asks logistics providers for a Moradabad–Pune delivery. /on_select returns item price plus delivery charge.
  • The money: the payments app collects it and says so in /confirm; it reaches Rehana days later, through the settlement agency.
  • The silent confirm: the retry must not create a second order. The buyer app created a network-unique order id and resends the same /confirm.
  • The rider fulfils a logistics order that Rehana's seller app placed, linked to the retail order; his updates are cascaded to the buyer app.
You

"Four kinds of party (buyer apps, seller apps, logistics apps, the settlement agency) and a few thin shared services (registry, gateways, grievance and observability). Everything that matters in an order happens on edges between two parties, signed at one end and verified at the other. No box in the middle knows the order."

System Architecture#

DiagramAn open network: parties talk directly; the shared services stay thin
%% title: An open network: parties talk directly; the shared services stay thin
flowchart TD
  subgraph L1["Layer 1 · Buyers"]
    direction LR
    BUY@{ icon: "sd:users", label: "Buyers<br/>Pune, Kolkata, Lucknow, Jaipur" }
    BAP["Buyer apps · BAP<br/>catalogue cache, cart, checkout, collector"]:::core
  end
  subgraph L2["Layer 2 · Shared network services"]
    direction LR
    REG@{ icon: "sd:id", label: "Registry<br/>subscribers, keys, domains, cities" }
    GW@{ icon: "sd:gateway", label: "Gateway<br/>multicasts search only" }
  end
  subgraph L3["Layer 3 · Supply"]
    direction LR
    BPP["Seller apps · BPP<br/>catalogues, quotes, orders"]:::hot
    SOR@{ icon: "sd:store", label: "Sellers on record<br/>Ansari Brass Works" }
    LSP@{ icon: "sd:delivery", label: "Logistics apps<br/>couriers, small fleets" }
  end
  subgraph L4["Layer 4 · Money and trust"]
    direction LR
    SA@{ icon: "sd:bank", label: "Settlement agency<br/>match, net, bank instructions" }
    IGM@{ icon: "sd:chat", label: "Issues and grievances<br/>IGM, GROs, ODR" }
    OBS@{ icon: "sd:dashboard", label: "Network observability<br/>masked logs, scorecards" }
  end
  BUY ==> BAP
  BAP ==>|"search"| GW
  GW ==>|"multicast by domain, city"| BPP
  BPP -.->|"on_search, direct"| BAP
  BAP ==>|"select, init, confirm, status"| BPP
  BPP ==>|"logistics search to confirm"| LSP
  BPP --> SOR
  BAP -.->|"key lookup, cached"| REG
  BPP -.->|"key lookup, cached"| REG
  BAP -->|"settle"| SA
  BPP -->|"settle"| SA
  BAP --> IGM
  BPP -.->|"logs"| OBS
  class BUY client
  class REG,GW edge
  class SOR store
  class LSP external
  class SA store
  class IGM service
  class OBS service

KEY INSIGHTS:

  • The network is a protocol, not a platform. The registry and gateways carry identities and searches; orders, quotes, statuses and money instructions go peer to peer
  • Consistency comes from the protocol's rules, not from a database. Ids, idempotent request and callback pairs, ordered states and deterministic failure rules make two sets of books agree
  • Every edge is a trust boundary. Each call is signed by its sender and verified against the registry by its receiver, so a participant can be held to what it sent

Data Flow: The Pune Thali#

DiagramThe Pune thali, from cached search to rider pickup
%% title: The Pune thali, from cached search to rider pickup
sequenceDiagram
  autonumber
  participant B as Payments app · BAP
  participant S as Rehana's seller app · BPP
  participant L as Logistics app
  Note over B: search served from its catalogue cache
  B->>S: select thali, txn T1
  S-->>B: ACK
  S->>L: logistics search Moradabad to Pune
  L-->>S: ACK, then on_search with options
  S->>B: on_select quote · item + delivery
  B->>S: init billing, address, payment terms
  S->>L: logistics init
  S->>B: on_init with cancellation and settlement terms
  B->>B: buyer pays · order id O-77 created
  B->>S: confirm O-77
  S-->>B: ACK · order held, not yet sent to shop
  S->>L: logistics confirm
  S->>B: on_confirm O-77 Accepted
  B-->>S: ACK · now released to Rehana
  L->>S: on_status Order-picked-up
  S->>B: on_status Order-picked-up

MEANWHILE, IN PARALLEL:

  • Every arrow is signed by its sender and verified against a cached copy of the sender's registry key
  • One transaction_id runs through select, init and confirm; each request and callback pair has its own message_id, so either side can match callbacks and drop duplicates
  • Both sides log every message, and push masked copies to network observability
Quick check3 questions · +5 XP each

High-level design

  1. Q1/3 Which messages in a retail order pass through a gateway?

    Answer & explanation

    Answer: A

    ONDC's FAQ: only discovery-phase messages go through the gateway, and it doesn't relay responses.

  2. Q2/3 In the Pune flow, who decides the delivery charge in on_select?

    Answer & explanation

    Answer: C

    Logistics is another network transaction, run by the seller app inside the retail one.

  3. Q3/3 What does an ACK to /confirm mean?

    Answer & explanation

    Answer: B

    Beckn calls are asynchronous: the synchronous reply is only an acknowledgement.

Phase 4 · Stage 4 of 7 · 20 min budget · 24 min read

Phase 4: Deep Dives (20 minutes)#

Deep Dive 1: Every Message Is a Signed Contract (Week 9 Concepts: Security Architecture; Week 2: Webhook Delivery)#

You

"In a single company, a request from the cart service to the order service is trusted because both run inside the same network. Here the 'cart service' is a payments company and the 'order service' is a seller platform in another city. They've never exchanged a credential. The registry is how they meet, and the signature is how each message proves where it came from."

This builds on Week 9, Day 5 · Security Architecture and Week 2, Day 4 · Webhook Delivery.

Your turn · checkpoint 3not answered

A request from a stranger

Rehana's seller app receives a /select that claims to be from the Jaipur loyalty marketplace. It has never talked to that company before. How does it know the request came from that company and wasn't changed on the way? Where does it get the right public key, and what if it gets 1,000 such requests a second? The same request arrives a second time, 40 seconds later, byte for byte identical: is that an attack? And the marketplace rotates its key tonight: how do in-flight messages survive?

Think it through, or jot it down. Then compare.

One strong answer

  • Signature: the sender hashes the exact body with BLAKE-512 (BLAKE2b), builds a signing string from (created), (expires) and that digest, signs it with its Ed25519 private key, and sends it in an Authorization header with keyId="subscriber_id|unique_key_id|ed25519". The receiver recomputes the digest from the raw bytes it received and verifies the signature.
  • The key: looked up in the registry by subscriber id and key id, then cached. At 1,000 a second the cache answers almost all of them; the registry's recommended /lookup limit is ~127 a second.
  • A replay: the signature has an expiry, and the message's context.timestamp must be near the receiver's clock (ONDC's FAQ says within ±10 seconds). A 40-second-old copy of the same message_id is either a retry (handled idempotently) or stale and NACKed.
  • Rotation: the registry can hold more than one key per subscriber, each with its own unique_key_id. Publish the new key, start signing with it, and keep the old one valid until messages signed with it have expired.

What is published: ONDC's signing document says that because of "the commercial nature of the transactions, every request/callback pair is considered to be a 'contract' between two parties", so all requests and callbacks are signed and verified. The digest is BLAKE-512 over the body; the signing string is (created), (expires) and the digest; the signature is Ed25519, in an Authorization header whose keyId is {subscriber_id}|{unique_key_id}|{algorithm}. A gateway forwarding a search adds its own X-Gateway-Authorization header, and the seller app verifies both. With no valid key, the receiver NACKs as unauthorised. To join, a participant needs a domain name (its subscriber_id) with an SSL certificate the registry checks by OCSP, an Ed25519 signing key pair and an X25519 encryption key pair, a site-verification page carrying a signed request id, and an /on_subscribe endpoint that must decrypt the registry's encrypted challenge. The registry's recommended rate limits are 10 a minute for /subscribe and 7,600 for /lookup.

DiagramJoining the network, then every message after
%% title: Joining the network, then every message after
flowchart TD
  NP["New participant<br/>FQDN, SSL, two key pairs"]:::service
  SUB@{ icon: "sd:id", label: "Registry /subscribe<br/>schema, OCSP, site check" }
  CH@{ icon: "sd:lock", label: "Challenge to /on_subscribe<br/>X25519 shared key, AES" }
  LIVE["Listed: roles, domains,<br/>cities, public keys"]:::core
  SIGN["Each request: BLAKE-512 digest,<br/>Ed25519 signature, keyId"]:::hot
  RX["Receiver: key from cache<br/>or /lookup, then verify"]:::core
  BAD["NACK, unauthorised"]:::bad
  NP ==> SUB ==> CH ==> LIVE
  LIVE -.->|"keys published"| RX
  SIGN ==> RX
  RX -.->|"no key or bad signature"| BAD
  class SUB edge
  class CH edge
  • Sign the bytes you send, verify the bytes you got. Re-serialising JSON changes whitespace and key order; ONDC's auth-header guide says to hash the "raw payload".
  • Cache keys, not answers. A cache keyed by (subscriber_id, unique_key_id) with a TTL of minutes absorbs nearly every verification. On a failure, refresh that one key once before NACKing: it may have just rotated.
  • The registry is a dependency with a budget. If it's slow, cached keys keep the network running; only new participants wait, which is the right failure.
  • Signatures make logs evidence. A signed /on_select with a price is provably what the seller app said; that matters in disputes and reconciliation.
  • It's webhook delivery in both directions. Every callback is a webhook between companies: signed, retried with a cap, deduplicated by id.
pythonsigning_string · 40 lines
# network/auth.py

"""
Signing and verifying ONDC-style requests (illustrative, using PyNaCl and hashlib).
Digest: BLAKE2b-512 of the raw body. Signing string: (created), (expires), digest.
Keys are looked up in the registry by subscriber id and key id, then cached.
"""

import base64, hashlib, time
from nacl.signing import SigningKey, VerifyKey


def signing_string(body: bytes, created: int, expires: int) -> bytes:
    digest = base64.b64encode(hashlib.blake2b(body, digest_size=64).digest()).decode()
    return f"(created): {created}\n(expires): {expires}\ndigest: BLAKE-512={digest}".encode()


def sign(body: bytes, sk: SigningKey, subscriber_id: str, key_id: str, ttl_s: int = 300) -> str:
    created = int(time.time()); expires = created + ttl_s
    sig = base64.b64encode(sk.sign(signing_string(body, created, expires)).signature).decode()
    return (f'Signature keyId="{subscriber_id}|{key_id}|ed25519",algorithm="ed25519",'
            f'created="{created}",expires="{expires}",headers="(created) (expires) digest",'
            f'signature="{sig}"')


def verify(raw_body: bytes, header: dict, keys) -> bool:
    sub, key_id, alg = header["keyId"].split("|")
    if alg != header["algorithm"] or not (int(header["created"]) <= time.time() <= int(header["expires"])):
        return False
    msg = signing_string(raw_body, int(header["created"]), int(header["expires"]))
    for attempt in (0, 1):
        pub = keys.get(sub, key_id, refresh=(attempt == 1))     # cache first, registry on retry
        if pub is None:
            continue
        try:
            VerifyKey(base64.b64decode(pub)).verify(msg, base64.b64decode(header["signature"]))
            return True
        except Exception:
            continue                                            # maybe rotated: refresh once
    return False                                                # caller NACKs: unauthorised

Deep Dive 2: Discovery: a Broadcast You Pay Once (Week 7 Concepts: Indexing Pipeline; Week 2: Timeout Hell; Week 4: Cache Invalidation)#

You

"A marketplace searches its own index. A buyer app on an open network has no index of its own until it builds one from what every seller app tells it. The question is whether that index is built per query, by broadcasting each search, or ahead of time, by broadcasting a request for catalogues and caching the answers."

This builds on Week 7, Day 2 · Indexing Pipeline, Week 2, Day 1 · Timeout Hell and Week 4, Day 2 · Cache Invalidation.

Your turn · checkpoint 4not answered

The last Lakshmi–Ganesh set

At 7:44 Rehana has one brass Lakshmi–Ganesh set left. The Lucknow grocery app and the Jaipur marketplace both show it "in stock" from their caches. Both buyers tap "Add to cart" within a second. What does each buyer app send, and who decides who gets it? Earlier that evening, a live search on a third buyer app went to 150 seller apps through the gateway and 30 hadn't answered after 3 seconds: what should it show? And Rehana's seller app was down for 20 minutes at 6 pm and missed a full catalogue request: are her products now "gone" from that buyer app?

Think it through, or jot it down. Then compare.

One strong answer

  • The cart: both send /select directly to Rehana's seller app, which checks its live stock. One gets a quote; the other gets an /on_select saying the item isn't available. The cache was a hint; /select is where truth is checked. In our design the seller app doesn't reserve stock at select, so it rechecks at /init and /confirm.
  • Partial results: show what has arrived, ranked, and keep accepting late on_search callbacks until the request's ttl expires, inserting them if the buyer is still on the page. Never wait for all 150.
  • Missed refresh: no. A full refresh overwrites a provider's items only when that provider's response arrives; a provider that didn't answer keeps its cached items. ONDC's contract says providers must be disabled explicitly, through an incremental refresh, and "cannot be disabled if a response isn't received".

What is published: ONDC's retail API contract (1.2.0) says search "will be used to communicate the intent for a full or incremental catalog refresh" and is "always broadcast through the gateway"; ONDC's FAQ adds that the gateway filters seller apps by domain and city. The full catalogue "will be sent directly by the seller NPs to the buyer NP", by city (for grocery, optionally by sub-category), inline or as a time-limited download link; the contract notes that "most buyer apps cache catalogs". For incremental refresh, a buyer app can pull a time window or ask for pushes "at a frequency within a range of 1-30 mins"; changes carry timestamps at provider, location and item level; and a seller app "should push catalog updates… until ACK is received from all buyer NPs". The search intent also declares the buyer app's finder fee (the example is 3%). In December 2025 the Commerce Ministry told the Lok Sabha that seller-side apps make their full catalogues discoverable to all buyer-side apps, while buyer-side apps disclose "key parameters used for sorting or listing search results".

DiagramTwo ways to search an open network
%% title: Two ways to search an open network
flowchart TD
  Q@{ icon: "sd:search", label: "Buyer types<br/>brass puja thali" }
  MODE{"Catalogue cached<br/>for this city?"}
  IDX["Buyer app's own index<br/>answers in milliseconds"]:::core
  BC@{ icon: "sd:gateway", label: "Live search via gateway<br/>to every seller app" }
  WAIT["Collect on_search until ttl<br/>show partial results"]:::hot
  REF["Full refresh daily by city,<br/>incremental pushes 1 to 30 min"]:::service
  Q ==> MODE
  MODE ==>|"yes"| IDX
  MODE -->|"no, or new category"| BC ==> WAIT
  REF -.->|"keeps it fresh"| IDX
  class Q client
  • The buyer app owns an indexing pipeline. Catalogue refreshes are its ingestion: Week 7's pipeline, except the documents arrive as signed callbacks from a hundred companies.
  • Order by the seller's timestamps, not by arrival. A full refresh ingested from 8:00 to 9:00 can race with pushes from 8:30. Keep the latest event timestamp per item; ignore anything older.
  • Absence isn't deletion. Disabling a provider, a store or an item is an explicit message.
  • Stock in the cache is a hint. The seller app's /on_select is authoritative, and it pushes stock-outs in its next incremental refresh.
  • Live search is for gaps. A new city, an uncached category, a ride: broadcast, collect until ttl, show partial results. The slow seller app loses its slot, not the buyer's patience.
  • The seller app runs pub–sub too. With 25 subscribed buyer apps, it aggregates changes once and fans them out 25 times, with ACKs and retries.
pythonapply_full_refresh · 32 lines
# buyer_app/catalog_ingest.py

"""
Ingesting full and incremental catalogue refreshes (illustrative). Each item update
carries the seller's event timestamp; the cache keeps the latest per item, so a slow
full refresh can't overwrite newer incremental changes. Providers are disabled only
by an explicit message, never because a response didn't arrive.
"""


def apply_full_refresh(cache, bpp_id: str, provider: dict, as_of: str) -> None:
    pid = (bpp_id, provider["id"])
    seen = set()
    for item in provider["items"]:
        key = pid + (item["id"],)
        seen.add(key)
        if cache.item_ts(key) is None or cache.item_ts(key) <= as_of:
            cache.put_item(key, item, ts=as_of)
    for key in cache.items_of(pid):                  # items this provider no longer lists
        if key not in seen and cache.item_ts(key) <= as_of:
            cache.disable_item(key, ts=as_of)        # only for providers that DID respond


def apply_incremental(cache, bpp_id: str, provider: dict) -> None:
    pid = (bpp_id, provider["id"])
    if provider.get("disabled"):                     # explicit: the only way a provider goes away
        cache.disable_provider(pid, ts=provider["time"]["timestamp"])
        return
    for item in provider.get("items", []):
        key, ts = pid + (item["id"],), item["time"]["timestamp"]
        if cache.item_ts(key) is None or ts > cache.item_ts(key):
            cache.put_item(key, item, ts=ts)         # newer wins; older pushes are ignored
Quick check3 questions · +5 XP each

Signing and discovery

  1. Q1/3 A receiver parses the JSON body, re-serialises it, and then verifies the digest. What goes wrong?

    Answer & explanation

    Answer: B

    Sign the bytes you send; verify the bytes you got.

  2. Q2/3 Two buyer apps both show the last item in stock and both send /select. Who decides?

    Answer & explanation

    Answer: C

    Cached stock is a hint; the seller app is the source of truth for its own inventory.

  3. Q3/3 A live search has 120 of 150 replies after 3 seconds. What do you show?

    Answer & explanation

    Answer: B

    Partial results are the normal case in a broadcast; the slowest seller app shouldn't set the buyer's latency.

Deep Dive 3: One Order, Two Databases (Week 2 Concepts: Idempotency; Week 5: Saga Pattern; Week 3: Transactional Outbox)#

You

"This is the heart of it. The buyer app has an order row; the seller app has an order row; there is no transaction that spans both. They agree only if every message about the order is idempotent, every failure has a rule both sides follow, and the order isn't released to the shop until both sides have heard each other. It's a saga between two companies, with the protocol as the coordinator."

This builds on Week 2, Day 2 · Idempotency in Practice, Week 5, Day 2 · Saga Pattern and Week 3, Day 2 · Transactional Outbox.

Your turn · checkpoint 5not answered

The spinner in Pune

The Pune buyer has paid. Her payments app sends /confirm for order O-77. Three things can go wrong: Rehana's seller app never ACKs; it ACKs but /on_confirm never arrives; or /on_confirm arrives but the payments app's ACK of it is lost, so the seller app retries /on_confirm. For each, what does each side do so that they end with the same answer? When may Rehana's tablet show the order? And the buyer taps "Place order" a second time: what id does the second /confirm carry?

Think it through, or jot it down. Then compare.

One strong answer

  • The id: the buyer app creates a network-unique order id before the first /confirm; every retry, and the second tap, carries it. /confirm is idempotent.
  • No ACK: the buyer app retries a capped number of times, then cancels with reason code 999, "order confirmation failure", and refunds.
  • ACK, no /on_confirm: the seller app holds the order and retries /on_confirm; with no ACK in its window, it cancels with 998 and pushes the cancellation. The buyer app keeps retrying /confirm with the same id and, with no /on_confirm in its own window, cancels with 999 and refunds. Status asks meanwhile get "processing" (31003).
  • Lost ACK of /on_confirm: the seller app resends the same callback; the buyer app recognises the order and ACKs again.
  • Rehana sees it only after the buyer app ACKs /on_confirm.

What is published: ONDC's retail contract spells out "Rules for order confirmation". /confirm and /on_confirm "should be idempotent", and the buyer app "will create the order id which will be unique across the network". On a valid /confirm, the seller app ACKs and "creates order (preferably with internal state to indicate order is being processed), but shouldn't forward to merchant". A validation failure is NACKed with 31002 and the buyer app cancels with reason code 999; after a retriable error (31001) or silence, the buyer app retries, with "a cap for no of attempts over specific duration", then cancels with 999. Once the buyer app ACKs /on_confirm, "SNP forwards order to merchant"; if the buyer app NACKs it (23002) or stays silent past the seller app's retries, the seller app cancels with 998, and meanwhile answers other calls with error 31003, "order processing in progress". Stale messages, older than one already processed for the same transaction_id and message_id, can be NACKed (20002 or 30022). ONDC's FAQ adds that when a cart spans several seller apps and one /confirm fails, "all associated orders in that transaction_id must be cancelled".

DiagramAn order on both sides, or on neither
%% title: An order on both sides, or on neither
stateDiagram-v2
  [*] --> CONFIRM_SENT: buyer app creates order id, sends confirm
  CONFIRM_SENT --> HELD_AT_SELLER: seller ACKs, order held
  CONFIRM_SENT --> CANCELLED_999: NACK 31002 or no answer after retries
  HELD_AT_SELLER --> ACCEPTED: on_confirm ACKed, released to shop
  HELD_AT_SELLER --> CANCELLED_998: on_confirm NACKed or unanswered
  ACCEPTED --> IN_PROGRESS: packed, picked up
  IN_PROGRESS --> COMPLETED: delivered
  ACCEPTED --> CANCELLED: cancel or force cancel
  IN_PROGRESS --> CANCELLED: RTO initiated
  COMPLETED --> [*]
  CANCELLED_999 --> [*]
  CANCELLED_998 --> [*]
  CANCELLED --> [*]
  class COMPLETED ok
  class CANCELLED_999 bad
  class CANCELLED_998 bad
  class HELD_AT_SELLER queue
  • The buyer app owns the id; the seller app owns the state. One side names the order so retries collide; the other is the truth about fulfilment.
  • Hold, then release. The ACK to /confirm means "recorded", not "packing". Release to the shop only after the buyer app acknowledges /on_confirm.
  • Every failure has one terminal answer. No ACK → 999 by the buyer app; unacknowledged /on_confirm → 998 by the seller app. Both reach "cancelled" without talking: a saga whose compensations are written in the protocol.
  • An outbox per edge. The order row and its pending /on_confirm commit in one local transaction; a sender retries until ACKed. A crash between "saved" and "sent" can't lose a callback.
  • Terminal means frozen. ONDC's contract freezes an order at "Completed" or "Cancelled", except returns within the window, each a new fulfillment.
  • Cancellation has a fallback. If the seller app doesn't answer /cancel in time, the buyer app sends a "force" cancel; if that's ignored too, an IGM issue's resolution produces the /on_cancel. Fees follow terms published in /on_init.
pythonon_confirm_request · 39 lines
# seller_app/confirm.py

"""
Handling /confirm at the seller app (illustrative). The order is created once per
network order id, held until the buyer app ACKs our /on_confirm, and released to the
shop only then. All outgoing callbacks go through an outbox in the same transaction.
"""

RETRY_WINDOW_S = 120          # our cap; ONDC leaves the cap to each participant


def on_confirm_request(db, msg) -> dict:
    ctx, order = msg["context"], msg["message"]["order"]
    key = (ctx["bap_id"], order["id"])                          # network-unique order id
    existing = db.orders.get(key)
    if existing:                                                # a retry: same order, same answer
        return {"ack": "ACK"}
    if db.seen_newer(ctx["transaction_id"], ctx["message_id"], ctx["timestamp"]):
        return {"nack": "30022"}                                # stale request
    problem = validate(db, order)                               # quote, stock, terms match on_init
    if problem:
        return {"nack": "31002", "message": problem}            # buyer app cancels with 999
    with db.tx():
        db.orders.insert(key, order, state="HELD", txn=ctx["transaction_id"])
        db.stock.reserve(order["items"], ref=key)
        db.outbox.add(to=ctx["bap_uri"], action="on_confirm",
                      body=build_on_confirm(order, state="Accepted"), ref=key)
    return {"ack": "ACK"}


def on_outbox_result(db, ref, acked: bool, age_s: float) -> None:
    if acked:
        db.orders.update(ref, state="Accepted")
        release_to_merchant(ref)                                # only now does Rehana see it
    elif age_s > RETRY_WINDOW_S:
        with db.tx():
            db.orders.update(ref, state="Cancelled", reason="998")
            db.stock.release(ref=ref)
            db.outbox.add(action="on_cancel", body=cancel_body(ref, "998"), ref=ref)

Deep Dive 4: Logistics Is Just Another Participant (Week 5 Concepts: Saga Orchestration; Week 3: Dead Letters)#

You

"Rehana doesn't own a van. Her seller app buys delivery on the same network, from whichever logistics provider quotes best for Moradabad to Pune. So one retail order is really two linked transactions, and the seller app sits in the middle of both: seller to the buyer app, buyer to the logistics app. Every state the rider creates has to travel back through it."

This builds on Week 5, Day 3 · Saga Orchestration and Week 3, Day 4 · Dead Letters and Poison Pills.

Your turn · checkpoint 6not answered

The rider who didn't come

At 7:42, while quoting the Pune thali, Rehana's seller app got four logistics quotes and picked one at ₹68. At 8:30 that provider cancels: no rider free. The retail order is already confirmed and the buyer has paid ₹68 for delivery. What can the seller app do, and what does the buyer see? Two days later the courier can't find the Pune address and starts returning the parcel: which messages flow, between whom, and what happens to the retail order and the refund? And who pays if the replacement courier charges ₹90?

Think it through, or jot it down. Then compare.

One strong answer

  • Re-procure first: search for another provider and confirm with it; the buyer sees a later time in the next /on_status. Only if nobody can take it does the seller app cancel and cascade a retail cancellation.
  • Return to origin (RTO): the provider sends /on_cancel with an RTO reason code; the seller app accepts a valid RTO and sends a cascaded retail /on_cancel with a new RTO fulfillment; RTO runs to "RTO-Delivered" or "RTO-Disposed", and the refund follows the order's terms.
  • The ₹22: in our design, the seller app bears it. The retail contract says the order value "cannot increase through /on_update", so the buyer can't be charged more; and ONDC's FAQ, for the non-cascaded flow, says a seller app whose delivery estimate was lower than the provider's charge procures logistics and bears the cost.

What is published: ONDC's FAQ describes the "cascaded" flow: "once the retail buyer builds a cart (in /select call), the seller app starts searching for logistics service providers… Logistics buyer which is also the retail seller then goes ahead with one of the LSP and cascades back the delivery charges in /on_select of the retail contract." On retail /init it initialises the logistics order, "from then on, all subsequent calls are cascaded". If the provider delays pickup or cancels, the logistics buyer can cancel and cascade it back, or "search for another LSP"; in a non-cascaded flow the seller app bears any delivery cost above its estimate. The retail contract defines RTO: a logistics /on_cancel with an RTO reason code "will initiate RTO" and a cascaded retail /on_cancel with a new RTO fulfillment. ONDC's "Harmonizing statuses" note adds pickup and delivery confirmation codes (an AWB number or OTP). At network scale, ONDC reported 50,000+ delivery executives in 250+ cities, and a first-in, first-out model matching the first-ready order with the first-arriving rider that reached 25% of logistics orders and cut average pickup times from 15 minutes to under 6 (FY 2025–26).

DiagramOne retail order, two linked transactions, one orchestrator in the middle
%% title: One retail order, two linked transactions, one orchestrator in the middle
sequenceDiagram
  autonumber
  participant B as Buyer app
  participant S as Seller app · orchestrator
  participant L1 as Logistics app 1
  participant L2 as Logistics app 2
  B->>S: confirm O-77
  S->>L1: logistics confirm, linked to O-77
  S->>B: on_confirm Accepted
  L1->>S: on_cancel · no rider
  alt another provider available
    S->>L2: logistics search, init, confirm
    S->>B: on_status · new pickup time
    L2->>S: on_status Order-picked-up
    S->>B: on_status Order-picked-up
  else nobody available
    S->>B: on_cancel O-77 · refund per terms
  end
  Note over S,L2: RTO later? logistics on_cancel with RTO code · cascaded retail on_cancel with an RTO fulfillment
  • The seller app is the saga orchestrator. It holds both transactions and decides compensations: re-procure, cancel, or RTO. The buyer app never talks to the logistics app.
  • Map states with a table. "Agent-assigned", "Order-picked-up", "Out-for-delivery", "Order-delivered" map to retail fulfilment states; a versioned table beats ad hoc translation.
  • Forward every change, once. Each logistics on_status becomes one retail on_status through the outbox; the contract says batching "should not be done".
  • Dead-letter, don't loop. If the buyer app NACKs a cascaded status for bad schema, resending the same bytes won't help: park it, alert, fix, replay.
  • Codes are the handover, and price is fixed at confirm. Pickup and delivery codes are evidence for disputes; any delivery-cost difference is between the seller app and the provider.
pythonon_logistics_event · 32 lines
# seller_app/logistics_cascade.py

"""
Cascading logistics events into the retail order (illustrative). The seller app maps
each logistics state to the retail fulfillment state, re-procures on a pre-pickup
cancellation, and cascades RTO. Every outgoing callback goes through the outbox.
"""

STATE_MAP = {
    "Agent-assigned": "Agent-assigned", "Order-picked-up": "Order-picked-up",
    "Out-for-delivery": "Out-for-delivery", "Order-delivered": "Order-delivered",
}


def on_logistics_event(db, lo, event) -> None:
    order = db.orders.by_logistics_order(lo.id)
    if event.kind == "status":
        retail_state = STATE_MAP.get(event.state)
        if retail_state and db.fulfilment.advance(order.id, retail_state, at=event.ts):
            db.outbox.add(action="on_status", body=status_body(order, retail_state), ref=order.id)
    elif event.kind == "cancel" and event.is_rto:
        with db.tx():                                           # a valid RTO must be accepted
            db.orders.update(order.id, state="Cancelled", fulfilment="RTO-Initiated")
            db.outbox.add(action="on_cancel", body=rto_body(order, event), ref=order.id)
    elif event.kind == "cancel" and not db.fulfilment.picked_up(order.id):
        alt = reprocure(order, exclude=lo.provider)             # search, init, confirm with another LSP
        if alt:
            db.outbox.add(action="on_status", body=eta_body(order, alt.eta), ref=order.id)
        else:
            with db.tx():
                db.orders.update(order.id, state="Cancelled", reason=event.reason)
                db.outbox.add(action="on_cancel", body=cancel_body(order, event.reason), ref=order.id)
Quick check3 questions · +5 XP each

Orders and logistics

  1. Q1/3 The buyer app gets an ACK to /confirm but never receives /on_confirm. What does the seller app do with the order meanwhile?

    Answer & explanation

    Answer: B

    ONDC's rule: create the order on ACK, but don't forward it to the merchant until /on_confirm is acknowledged.

  2. Q2/3 A cart spans two seller apps; one /confirm fails. What does ONDC's FAQ say?

    Answer & explanation

    Answer: B

    All-or-nothing across seller apps, by compensation.

  3. Q3/3 The logistics provider cancels before pickup. What's the seller app's first move?

    Answer & explanation

    Answer: B

    Re-procure if possible; cascade a cancellation only when no provider can take it.

Deep Dive 5: Settlement and Reconciliation Between Companies (Week 3 Concepts: Audit Logs; Week 5: Conflict Resolution; Week 2: Distributed Cron)#

You

"The buyer in Pune paid the payments app. Rehana is owed the thali's price minus two commissions, the payments app keeps its finder fee, and the courier is paid by the seller app in its own logistics transaction. Four parties, four bank accounts, and two companies' books that each think they know what's owed. If either side can move money on its own say-so, the other has to trust it. So neither does: a settlement agency moves money only when both sides' instructions match."

This builds on Week 3, Day 5 · Audit Log System, Week 5, Day 4 · Conflict Resolution and Week 2, Day 5 · Distributed Cron.

Your turn · checkpoint 7not answered

₹1,050, three days later

Take ONDC's own example. A retail order of ₹1,050 is collected by the buyer app. The buyer app's commission (its finder fee) is ₹50; the seller app's commission is ₹200. What does the buyer app's settlement instruction say, and what does the seller app's? When are they sent, and what does the settlement agency do with them? Then: the buyer app's instruction says ₹1,000 is owed to the seller app, but the seller app's says ₹1,010 because it forgot a ₹10 discount. What happens? And the buyer app sends nothing at all that day: does the seller app still get paid?

Think it through, or jot it down. Then compare.

One strong answer

  • Buyer app (collector): inter-participant ₹1,000 (owed to the seller app), collector ₹50 (its finder fee), self ₹50 (moved to its own operative account).
  • Seller app (receiver): inter-participant ₹1,000, collector ₹50, self ₹200 (its commission), provider ₹800 (to Rehana's bank account).
  • When: on the date the order's terms give (for example, delivery plus the settlement window), both send /settle before the cycle's cut-off.
  • The agency matches them order by order, checks the collector's balance, instructs the banks, and answers both with /on_settle: SETTLED or NOT_SETTLED.
  • ₹1,000 vs ₹1,010: NOT_SETTLED. The seller app sends /recon with its breakup; the buyer app answers /on_recon. Agreed orders settle next cycle; the rest become IGM issues (underpaid, overpaid, not paid).
  • Buyer app silent: under RSF 2.0 the seller app's claim is settled. ONDC's FAQ strongly suggests a daily /settle, even an empty one, so money isn't debited on the seller app's word alone.

What is published: ONDC's Reconciliation and Settlement Framework (RSF 2.0, redesigned in 2024) names a collector (usually the buyer app in retail), a receiver (the seller app) and a settlement agency (ONDC's FAQ names NBBL, an NPCI subsidiary). Participants open an account for ONDC settlement and integrate six APIs: /settle, /on_settle, /report, /on_report, /recon and /on_recon. Both sides send /settle for orders due under the order's terms (settlement_basis, such as delivery, and settlement_window, such as P1D); the cycle runs daily. The NOCS procedural guidelines list the agency's checks (duplicates, participants, settlement accounts, "2-way reconciliation", balance), then a Multilateral Net Settlement Batch uploaded to RBI's RTGS platform at a cut-off, pair-wise payouts to sellers by NEFT, RTGS or IMPS, and /on_settle with a settlement reference per order. RSF 2.0 is "claim based rather than depending solely on Buyer Apps", unmatched orders are rejected, and the agency nets interbank movement while participants submit gross. ONDC's FAQ says retail usually settles after a terminal state, "for example return window plus 7 days", that the time allowed for an on_recon is typically "around 5-7 days", and that unresolved differences go to IGM; refunds after money has moved go through an IGM issue too.

DiagramMoney moves only when both sides say the same thing
%% title: Money moves only when both sides say the same thing
flowchart TD
  C["Buyer app · collector<br/>/settle: inter-NP 1,000, self 50"]:::core
  R["Seller app · receiver<br/>/settle: inter-NP 1,000, self 200, provider 800"]:::core
  SA@{ icon: "sd:bank", label: "Settlement agency<br/>dedupe, validate, match, balance" }
  M{"Both sides match<br/>for this order?"}
  NET@{ icon: "sd:file", label: "Net batch to RTGS<br/>pair-wise payouts" }
  NS["NOT_SETTLED<br/>recon and on_recon"]:::hot
  IGM@{ icon: "sd:chat", label: "IGM issue<br/>underpaid, overpaid, not paid" }
  SOR@{ icon: "sd:wallet", label: "Rehana's bank<br/>₹800" }
  C ==> SA
  R ==> SA
  SA ==> M
  M ==>|"yes"| NET ==> SOR
  M -->|"no"| NS
  NS -->|"agreed: next cycle"| SA
  NS -.->|"still disputed"| IGM
  class SA store
  class NET store
  class IGM service
  class SOR external
  • Two ledgers, one matcher. Each participant keeps an append-only ledger of what each order owes. The agency never decides who's right; it moves money only when they agree.
  • Derive amounts from the signed trail. Quote, finder fee, cancellation terms and every /on_update are signed messages both sides hold. Computed from the same trail, settlements agree by construction; mismatches mean bugs, missed updates or tax.
  • Settle after the terminal state. Waiting for "Completed" plus the return window makes most refunds a reduction, not a clawback.
  • Daily is a cron with a deadline. One idempotent run per cycle before the cut-off: select orders due today, send /settle, persist /on_settle, ask /report if it doesn't arrive.
  • Reconcile three ways. Ledger against /on_settle, /on_settle references against the bank statement, and own view against the counterparty's through /recon.
  • Gross in, net out. The agency nets every participant's gross orders into one batch, so a hundred participants don't need a hundred bilateral transfers.
pythonsettle_cycle · 30 lines
# settlement_agency/match.py

"""
Matching collector and receiver instructions for one settlement cycle (illustrative).
Only orders where both sides' amounts agree (or where the collector stayed silent,
as RSF 2.0 provides) are settled, and only if the collector's account can fund them.
"""

from collections import defaultdict


def settle_cycle(claims, submissions, balances):
    """claims: receiver /settle lines; submissions: collector /settle lines; keyed by order id."""
    by_order = {s.order_id: s for s in submissions}
    silent = {c.collector for c in claims} - {s.collector for s in submissions}
    matched, rejected = [], []
    for c in claims:
        s = by_order.get(c.order_id)
        if s is None and c.collector in silent:
            matched.append(c)                                   # claim-based: collector sent nothing
        elif s is None or (s.inter_np, s.collector_amt) != (c.inter_np, c.collector_amt):
            rejected.append((c.order_id, "NOT_SETTLED"))        # → /recon between the two NPs
        else:
            matched.append(c)
    need = defaultdict(int)
    for c in matched:
        need[c.collector] += c.inter_np
    funded = [c for c in matched if balances[c.collector] >= need[c.collector]]
    short = [(c.order_id, "NOT_SETTLED") for c in matched if c not in funded]
    return net_batch(funded), payouts(funded), rejected + short  # → RTGS batch, NEFT/IMPS, /on_settle

Deep Dive 6: Complaints and Health Across Company Lines (Week 10 Concepts: Observability, SLOs; Week 8: Event Ingestion)#

You

"When something goes wrong in a single company, the complaint goes to one support team that can see everything. Here the buyer complains to the app she used, which doesn't hold the order details the shop holds, can't see the rider, and has no authority over either. And when the network as a whole gets slower, nobody can see why, because nobody sees everything. Both problems need a protocol of their own."

This builds on Week 10, Day 2 · Observability, Week 10, Day 1 · SLIs, SLOs and SLAs and Week 8, Day 1 · Event Ingestion.

Your turn · checkpoint 8not answered

A dented diya in Kolkata

The Kolkata family's diyas arrive with one dented. They complain in their bank's app. Who must answer them, by when, and who actually decides whether they get a refund: the bank, Rehana's seller app, Rehana, or the courier? If the courier dented it, how does the complaint reach the courier? If the bank's app does nothing for three days, where can the family go? Separately: ONDC notices that confirmation rates on one seller app have dropped from 98% to 70% in an hour. How could it know that, without seeing anyone's orders?

Think it through, or jot it down. Then compare.

One strong answer

  • First response: the buyer app, within 48 hours, with a ticket number; resolution within 30 days, the limits ONDC cites from the Consumer Protection (E-commerce) Rules, 2020.
  • Who decides: typically the seller or seller app; the buyer app coordinates. Over IGM, the bank's app raises an issue to the seller app, which can cascade it to the logistics provider, whose grievance officer can propose a refund.
  • Escalation: after a missed 48 hours, a missed promise or 30 days, to ONDC's grievance portal (or the National Consumer Helpline), then online dispute resolution or a consumer court.
  • Health without data: participants push masked protocol logs; ONDC computes per-participant metrics such as attempt-to-confirm rate and callback times, and flags the drop to that participant alone.

What is published: ONDC's complaints page says the buyer app "must provide you with this response within 48 hours", with a ticket number, and the complaint "has to be resolved within 30 days", both "under the E-commerce Rules 2020"; for product or delivery issues, typically, "the seller or Seller App will be responsible for resolving your complaint, while the Buyer App only facilitates". Unresolved complaints go to ONDC's grievance portal, then to "empanelled Online Dispute Resolution service providers" or consumer courts. IGM defines four APIs (issue, on_issue, issue_status, on_issue_status), issue types (issue, grievance, dispute), grievance officers (the interfacing participant's, the counterparty's and the cascaded counterparty's), and expected_response_time and expected_resolution_time fields; ONDC's FAQ adds that in a cascaded complaint "multiple eligible actors (like seller or lsp) can submit various resolution options". ONDC's September 2023 observability white paper has participants push logs "while masking personal identifiable information and business sensitive information", computes metrics like "attempt to confirm rate, fill rate, cancellation rate" and asynchronous response times, sends monthly scorecards, and promises that "at no point, a network participant would have access to sensitive insights pertaining to another network participant". Before going live, participants get their test logs approved, generated with ONDC's Pramaan tool or checked by its log validation utility.

DiagramA complaint crosses three companies, one append-only issue
%% title: A complaint crosses three companies, one append-only issue
sequenceDiagram
  autonumber
  participant U as Kolkata buyer
  participant B as Bank's app · BAP
  participant S as Seller app
  participant L as Logistics app
  U->>B: one diya dented, photo
  B-->>U: ticket, resolve by Thursday
  B->>S: issue · item damaged, refs O-91
  S-->>B: ACK
  S->>L: issue cascaded · damage in transit?
  L->>S: on_issue · resolution proposed, refund ₹120
  S->>B: on_issue · refund ₹120 proposed
  B->>U: accept refund?
  U->>B: yes
  B->>S: issue · resolution accepted, close
  Note over B,S: refund flows through settlement · every action appended, signed, never edited
  • One issue, append-only actions. Both sides hold copies; changes are appended actions with ids and references. Nobody edits history, so copies converge.
  • Timers are data. Response and resolution deadlines are fields every participant's scheduler watches; a miss is an escalation event.
  • Route by role. Each grievance officer talks only to its neighbours, as in the order flow.
  • Observability is a protocol too. One masked event per protocol message (action, ids, timestamps, outcome) goes to a network collector, which computes the same metrics for everyone and shows each participant only its own.
  • Measure the edges. Callback latency, NACKs by code, confirm success, cancellations by reason. In an async network a slow edge is invisible until orders silently die.
pythonedge_metrics · 26 lines
# network_obs/edge_metrics.py

"""
Per-participant edge metrics from masked protocol logs (illustrative). Each event has
the sender, receiver, action, transaction and message ids and timestamps; no buyer
data. Callback latency is measured from the request's ACK to its on_ callback.
"""

from statistics import quantiles


def edge_metrics(events, window):
    req, lat, confirms, accepted = {}, [], 0, 0
    for e in events.in_window(window):
        if not e.action.startswith("on_"):
            req[(e.transaction_id, e.message_id)] = e.ts       # request seen
            confirms += e.action == "confirm"
        else:
            t0 = req.pop((e.transaction_id, e.message_id), None)
            if t0 is not None:
                lat.append(e.ts - t0)                           # request → callback
            accepted += e.action == "on_confirm" and e.order_state == "Accepted"
    p95 = quantiles(lat, n=20)[-1] if len(lat) >= 20 else None
    return {"confirm_success": accepted / confirms if confirms else None,
            "callback_p95_s": p95,
            "unanswered": len(req)}                             # requests with no callback yet
Quick check4 questions · +5 XP each

Money and trust

  1. Q1/4 In ONDC's example, a ₹1,050 order has a ₹50 buyer-app fee and a ₹200 seller-app commission. What does the seller app's /settle send to the seller on record?

    Answer & explanation

    Answer: C

    Inter-participant ₹1,000 from collector to receiver; of that, ₹200 is the seller app's and ₹800 goes to the provider.

  2. Q2/4 The collector says ₹1,000 is owed; the receiver says ₹1,010. What happens?

    Answer & explanation

    Answer: C

    The agency never decides who's right; it moves money only on a match.

  3. Q3/4 A buyer app hasn't answered a complaint in 48 hours. Where can the buyer go?

    Answer & explanation

    Answer: B

    48 hours to respond and 30 days to resolve, as ONDC's complaints page cites from the E-commerce Rules, 2020.

  4. Q4/4 How can ONDC see that one seller app's confirmations fell to 70% without reading its orders?

    Answer & explanation

    Answer: C

    Network observability computes edge metrics from masked events and shares each participant's own numbers only with it.

Phase 5 · Stage 5 of 7 · 5 min budget · 2 min read

Phase 5: Scaling and Edge Cases (5 minutes)#

Interviewer

"Walk me through Rehana's Dhanteras from the network's side, then the cases that bite."

Rehana's Dhanteras#

Dhanteras on an open network (illustrative)
  • Morning · Full catalogue refreshes
    • Buyer apps request catalogues through the gateway; Rehana's seller app answers each directly and then pushes changes every few minutes
  • 7:41 pm · Four orders in four minutes
    • Select, init and confirm go directly to her seller app; each delivery is bought inside the retail select
  • 7:44 pm · One idol, two carts
    • One select gets a quote, the other "not available"; the stock-out goes out in the next push
  • 7:46 pm · A silent confirm
    • The Pune app retries with the same order id; the order is created once and released after its on_confirm is acknowledged
  • 8:10 pm · Pickup
    • The rider reads the pickup code; statuses cascade from the logistics app through the seller app to each buyer app
  • Days later · Settlement
    • After the return window, both sides send /settle; the agency matches, nets and pays Rehana's bank; a dented diya is refunded through IGM

Edge Cases#

Edge cases
Edge case 1: A seller app goes down on festival night

Problem: A large seller app stops answering for 40 minutes at the peak.

Solution: Its sellers stay in cached catalogues (absence isn't deletion), but buyer apps trip a per-counterparty circuit breaker after repeated /select timeouts and stop offering its items until it answers. In-flight confirms end as 999 cancellations and refunds. No other seller app is affected.

Edge case 2: A participant's key is compromised

Problem: A buyer app's signing key leaks.

Solution: Publish a new key with a new unique_key_id and revoke the old one; receivers need short key-cache TTLs and refresh-on-failure, so messages signed with the old key are NACKed quickly.

Edge case 3: Clock skew

Problem: A seller app's clock drifts by a minute; its callbacks are rejected as stale.

Solution: NTP everywhere (ONDC's FAQ asks for timestamps within ±10 seconds) and a skew metric per counterparty that alerts before rejections start.

Edge case 4: The collector can't fund settlement

Problem: A buyer app's settlement account can't cover the day's matched orders.

Solution: The balance check fails and those orders are NOT_SETTLED until it's funded. ONDC's website lists a voluntary Payment Assurance Program, a refundable security deposit by buyer apps; as of October 2026 its page says it is "not live yet".

Quick check2 questions · +5 XP each

Scaling and edge cases

  1. Q1/2 A seller app times out on every select for 10 minutes. What should buyer apps do?

    Answer & explanation

    Answer: B

    The failure stays local to that seller app's transactions; the catalogue comes back the moment it does.

  2. Q2/2 A buyer app's signing key leaks. What has to happen at receivers?

    Answer & explanation

    Answer: B

    Revocation only works as fast as the slowest cache honours it.

Phase 6 · Stage 6 of 7 · 1 min

Phase 6: Monitoring and Operations#

You

"Three views: a participant watching its own edges, a participant watching its money, and the network watching everyone's health without seeing anyone's data."

Monitoring Dashboard#

Rehana's seller app, all its shops — Dhanteras, 7:50 pm IST (illustrative)
Edges
Metric Now
Requests received (searches, orders, ACKs of our callbacks) / ACKed 240/s / 99.6%
NACKs by code (top: 30022 stale, 31002 validation) 0.3% / 0.1%
/on_select callback p95 (incl. logistics search) 2.4 s
/on_confirm callback p95 0.9 s
Outbox backlog / oldest unACKed callback 312 / 41 s
Key-cache hit rate / registry lookups 99.97% / 0.6/s
Orders
  • Confirms received: 4,210/h; released to shops: 4,152; cancelled 999 / 998: 31 / 9
  • Logistics re-procured after provider cancel: 44; RTOs initiated today: 17
Catalogue
  • Subscribed buyer apps: 23; incremental pushes pending ACK: 2 (one buyer app NACKing, schema)

SLOs for an Open Network#

SLOs for edges, orders, money and complaints
SLO 1: Callbacks arrive
  • Target: 99% of callbacks within their message's ttl; p95 /on_confirm < 2 s
SLO 2: Orders agree
  • Target: zero orders released to a shop without an acknowledged /on_confirm; zero duplicate orders per network order id; order state mismatches with counterparties (found by /status sampling) < 0.01%
SLO 3: Money matches
  • Target: > 99.5% of due orders SETTLED in their first cycle; every NOT_SETTLED order in recon within 1 day; every /on_settle reference matched to a bank statement line
SLO 4: Complaints are answered
  • Target: 100% of issues acknowledged within 48 hours; 100% resolved or escalated within 30 days
Quick check2 questions · +5 XP each

Reading the dashboard

  1. Q1/2 The outbox backlog climbs from 300 to 30,000 and the oldest unACKed callback is 9 minutes old, while requests in are normal. Where do you look?

    Answer & explanation

    Answer: B

    In an async network, a stuck callback queue is a counterparty problem first. Isolate queues per destination.

  2. Q2/2 Key-cache hit rate drops to 60% and registry lookups jump to 400 a second. Likeliest cause, and the risk?

    Answer & explanation

    Answer: B

    Rate-limit-aware key caching is part of availability, not an optimisation.

Final challenge · Stage 7 of 7 · 2 min

Final Challenge: Dhanteras, 8:40 pm, a Seller App Goes Quiet#

The interviewer leans back. "One more."

Interviewer

"Festival night. At 8:40 pm a large grocery seller app (fictional, like everything in this scenario) deploys a change. It still ACKs every /confirm, but its /on_confirm sender has a bug: callbacks are written to its outbox and never sent. It also stops sending incremental catalogue updates. Buyers have paid. Tell me what happens at the buyer apps, at the seller app, to the shops, and to settlement next week, and what you'd change."

The incident's brief
  • The seller app serves ~3,000 shops (assumed); at the peak it receives ~60 confirms a minute (assumed)
  • From 8:40 pm, it ACKs /confirm but sends no /on_confirm; incremental catalogue pushes stop
  • Buyer apps retry /confirm with the same order id and a capped retry window (assumed 2 minutes)
  • The bug is found at 9:25 pm and the sender restarts with ~2,700 callbacks in its outbox
Your turn · checkpoint 9not answered

What happens, and what do you change?

Decide what each buyer app does from 8:42, what the seller app's held orders become, what happens when the 2,700 callbacks are finally sent, what shops and buyers see, what settlement does next week, and what you'd add so this is caught at 8:45, not 9:25.

Think it through, or jot it down. Then compare.

One strong answer

  • Buyer apps from 8:42: no /on_confirm within the retry window, so each order is cancelled with 999 and refunded. Repeated failures trip each buyer app's breaker for that seller app, so they stop taking new orders for it.
  • Held orders: never released to shops, because release waits for an ACK of /on_confirm. That rule just saved ~2,700 phantom orders.
  • At 9:25: replaying 2,700 stale /on_confirms gets NACKs and then 998 cancellations. Better: check each order's age against the buyer apps' retry windows and cancel anything past it instead of sending it.
  • Catalogue: caches are 45 minutes stale, so more selects fail; when pushes resume, send all changes since 8:40.
  • Settlement: cancelled orders never reach "Completed", so nobody sends /settle for them; nothing to reconcile if both sides recorded the same codes.
  • Catch it at 8:45: alert on "confirms ACKed without an /on_confirm sent" and on outbox age per destination; let network observability flag the falling attempt-to-confirm rate within minutes; and canary deploys that watch both numbers.
Quick check3 questions · +5 XP each

Final challenge · Quest check

  1. Q1/3 The seller app ACKs /confirm but never sends /on_confirm. Why don't shops pack orders the buyer apps have cancelled?

    Answer & explanation

    Answer: B

    Hold, then release: the rule that makes a silent failure a refund, not a phantom delivery.

  2. Q2/3 2,700 delayed /on_confirm callbacks are sent at 9:25. What should have happened instead?

    Answer & explanation

    Answer: B

    A replay after a long outage should be reconciled against what the other side has certainly done by now.

  3. Q3/3 What alert would have caught this in minutes?

    Answer & explanation

    Answer: B

    Watch the edges: the system looked healthy from inside because every request was ACKed.

Wrap-up · 9 min

Interview Conclusion#

Transcript
Interviewer

"Why not just have ONDC run a central order database that everyone writes to? It'd make consistency easy."

You

"It'd make consistency easy and the network pointless. A central order store makes the operator a platform: every participant depends on its availability, its schema, its speed and its policies, and it sees every order. The point of an open network is that a payments app and a seller platform can transact without a third party in the middle of each order. So consistency has to come from the protocol: one side names the order, both sides make every message idempotent, failures have deterministic outcomes both sides apply on their own, and money moves only when both sides' books match at a neutral agency. That's harder to build, but it's what lets anyone join."

Interviewer

"The single most important decision?"

You

"Holding the order at the seller app until the buyer app acknowledges the confirmation. It's one rule, and it means that whatever fails between two companies, the shop never fulfils an order the buyer's app doesn't have, and the buyer is never charged for one the shop doesn't."

Summary: Concepts Applied from 10-Week Course#

Concepts from the 10-week course in an open commerce network
  • Week 2: Failure-first design
    • Timeout hell — partial search results until the message's ttl; capped confirm retries
    • Idempotency — network-unique order ids; idempotent request and callback pairs; settlement ids
    • Webhook delivery — every callback is a signed, retried, deduplicated webhook between companies
    • Distributed cron — the daily settlement job before the cut-off
  • Week 3: Messaging and async processing
    • Transactional outbox — order rows and their callbacks committed together
    • Dead letters — cascaded messages that keep failing are parked, fixed and replayed
    • Audit logs — append-only ledgers and issue actions; signed messages as evidence
  • Week 4: Caching
    • Cache invalidation — catalogue caches refreshed by timestamped full and incremental updates; stock as a hint
  • Week 5: Consistency and coordination
    • Sagas — confirm and its compensations (999, 998) across two companies; logistics re-procurement and RTO orchestrated by the seller app
    • Conflict resolution — two-sided settlement matching, then recon, then IGM
  • Week 7: Search
    • Indexing pipeline — buyer apps build their own index from catalogue refreshes
  • Week 8: Analytics pipelines
    • Event ingestion — masked protocol logs into network observability
  • Week 9: Security
    • Registry, Ed25519 signatures over BLAKE-512 digests, key rotation and revocation
  • Week 10: Production readiness
    • Edge metrics, participant scorecards, SLOs for callbacks, orders, money and complaints

Why ONDC Matters#

Why this is a different kind of hard problem
Consistency without a shared database
  • Most systems get consistency from a database. An open network gets it from rules every participant follows on its own: who names the order, what a retry means, what silence means, when an order is released, when money moves. The protocol is the transaction manager
Trust is engineered, not assumed
  • Strangers transact because every message is signed against a public registry, every rupee moves only when two ledgers match at a neutral agency, and every complaint has a deadline and an escalation path. Each of those is a system someone had to design
The load is in the broadcasts
  • ONDC's order rate is modest (218 million transactions in FY 2025–26, about 7 a second on average, across retail, mobility, transit and finance). The expensive parts are discovery, which is a broadcast, and the cost of every participant re-implementing the same protocol correctly
Openness has a price, and a payoff
  • An open network has more failure modes than a platform: more companies, more edges, more versions of the spec in production. In return, a brass seller in Moradabad can be found from a payments app, a bank and a grocery app at once, and a metro operator can sell tickets through dozens of apps with one integration

"Name the order on one side, keep it on both, release it only when both have heard, and move money only when both books agree."

Self-Assessment Checklist#

After studying this case study, you should be able to:

Protocol and trust:

Protocol and trust0/3 ready
  • Explain the asynchronous request, ACK and on_ callback pattern, and why the synchronous reply is only an acknowledgement
  • Describe how a participant joins a registry, and how requests are signed (BLAKE-512 digest, Ed25519) and verified with cached keys
  • Plan key rotation and revocation across hundreds of independent receivers

Discovery and orders:

Discovery and orders0/3 ready
  • Explain why catalogue caching with full and incremental refresh beats live broadcast search, and how to order refreshes by timestamp
  • Walk through confirm and on_confirm failures and the deterministic outcomes (999, 998) that keep two companies' orders consistent
  • Orchestrate a cascaded logistics transaction, including re-procurement and RTO

Money and operations:

Money and operations0/3 ready
  • Build collector and receiver settlement instructions for an order and explain two-sided matching, recon and escalation
  • Route a complaint across buyer app, seller app and logistics provider with deadlines
  • Define edge metrics and SLOs for an asynchronous multi-party network without sharing participants' data

Sources#

ONDC (ondc.org and ONDC publications):

ONDC protocol and developer documentation (github.com/ONDC-Official):

Beckn protocol:

Government of India:

Reporting:

Design note: the services inside each participant, the code, thresholds, retry windows, dashboards and every number labelled as an assumption or estimate are an illustrative design, not any participant's or ONDC's internal systems. Seller apps per city, searches a second, catalogue sizes, messages per order, the festival peak and the final challenge's figures are assumptions. Rehana Ansari, Ansari Brass Works and her orders are fictional, and the buyer apps in her story are described, not real. ONDC's figures come from publications between 2022 and 2026; ONDC's transaction counts include mobility, transit tickets and financial services as well as retail. Consumer-protection and settlement rules are summarised for engineering context and should be checked against current ONDC policy and law.

Further Reading#

Related systems to study:

  • Read

    UPI (Bonus Problem 1 in this series): the open payments network ONDC is often compared to; one atomic transfer through a central switch, where ONDC has no switch in the order path

  • Read

    Razorpay (Bonus Problem 18): payments, settlement and reconciliation from a gateway's side

  • Read

    Blinkit (Bonus Problem 12) and Swiggy (Bonus Problem 17): one platform owning catalogue, orders and dispatch, the opposite of an unbundled network

  • Read

    Flipkart Big Billion Days (Bonus Problem 27): a single marketplace's festival sale, for contrast with a festival on an open network

Foundational concepts (this course):

  • Read

    Week 2, Day 1 — Timeout Hell

  • Read

    Week 2, Day 2 — Idempotency in Practice

  • Read

    Week 2, Day 4 — Webhook Delivery

  • Read

    Week 2, Day 5 — Distributed Cron

  • Read

    Week 3, Day 2 — Transactional Outbox

  • Read

    Week 3, Day 4 — Dead Letters and Poison Pills

  • Read

    Week 3, Day 5 — Audit Log System

  • Read

    Week 4, Day 2 — Cache Invalidation

  • Read

    Week 5, Day 2 — Saga Pattern

  • Read

    Week 5, Day 3 — Saga Orchestration

  • Read

    Week 5, Day 4 — Conflict Resolution

  • Read

    Week 7, Day 2 — Indexing Pipeline

  • Read

    Week 8, Day 1 — Event Ingestion

  • Read

    Week 9, Day 5 — Security Architecture

  • Read

    Week 10, Day 1 — SLIs, SLOs and SLAs

  • Read

    Week 10, Day 2 — Observability

"Name the order on one side, keep it on both, release it only when both have heard, and move money only when both books agree."

this lesson in shorts · 4

play them all →

One idea from this lesson in about a minute each, made for your phone.

End of Side quest 32 · ONDC
Clear the questFinish ONDC.Stages clear as you read them. Marking complete unlocks after ~8 more min of reading.
  • 0/7stages cleared
design sheet · 7 diagrams · pdfRevise the whole design in ten minutesThe numbers, architecture, sequence, class and state diagrams, decisions and failure modes of this quest on a few printable pages.open the sheet →

New lessons by email

New lessons, side quests and case files as they ship, at most once a week. No spam, unsubscribe anytime.

Discussion · …

Public: everyone reading this lesson sees comments. Be kind and keep it on the material.

Sign in to ask a question or answer one. Reading stays open to everyone.
loading the discussion…