Skip to content

NetOfNetBeckn Network-of-Networks PoC

AMUL + MH VISTAAR fan-out across real Bharat Vistaar Dev and a mocked MH network. Stage (b.1) live.

At a glance

NetOfNet is a Beckn "Network of Networks" demo. A single client (AMUL App or MH VISTAAR) submits one search; the Amul BAP fans it out to two Beckn networks in parallel and aggregates the on_search callbacks side-by-side. (A BAP — Beckn Application Platform — is the standard Beckn role that bridges synchronous client apps to the async/callback world of a Beckn network.)

Two stages are live:

StageWhat runsStatus
stage-a-v1Both legs are local mocks (registries, gateways, BPPs) — full Beckn message flow with stand-in catalogs.Tag retained for reproducibility.
stage-b1-v1MOA leg = real Bharat Vistaar Dev sandbox (DA, GoI). MH leg is still mocked.Current.

The whole thing runs on one Hetzner host (sdcrs-demo, 91.99.29.19), fronted by host nginx with per-subdomain Let's Encrypt certs. All bound to 127.0.0.1; nginx is the only 0.0.0.0 surface.

Try it

Browser

Open ui.netofnet.theflywheel.in, pick a persona, type any query, hit search. Stage (b.1) BPPs vary in how they treat the query string — Vistaar matches on message.intent.item.descriptor.name, the MH mock returns its full catalog regardless.

curl — end-to-end fan-out

bash
curl -sS -X POST -H "Content-Type: application/json" \
  -d '{"persona":"amul","query":"KCC","user_id":"farmer-grape-47"}' \
  https://ui.netofnet.theflywheel.in/api/search | jq .

Expected (abridged):

json
{
  "elapsed_ms": 2605,
  "moa": {
    "context": {
      "domain": "schemes:vistaar",
      "bpp_id": "bpp-network-playground-sandbox-vistaar.da.gov.in"
    },
    "message": { "catalog": { "providers": [{
      "descriptor": { "name": "SchemeFinder" },
      "items": [{
        "id": "PMKISAN-101",
        "descriptor": { "name": "Kisan Credit Card", "short_desc": "..." }
      }]
    }] } }
  },
  "mh": {
    "context": { "domain": "retail", "bpp_id": "bpp-mh.netofnet.theflywheel.in" },
    "message": { "catalog": { "bpp/providers": [
      { "id": "mh-weather", "items": [{"id":"mh-w-basic","descriptor":{"name":"Weather Advisory (MH)"}}] },
      { "id": "mh-market",  "items": [{"id":"mh-m-prices","descriptor":{"name":"Mandi Prices (MH)"}}] }
    ] } }
  },
  "traces": {
    "moa": { "transaction_id": "...", "elapsed_ms": 2599, "label": "MOA — Vistaar Dev (Schemes)" },
    "mh":  { "transaction_id": "...", "elapsed_ms": 266,  "label": "MH (mock)" }
  }
}

Per-service probes

bash
# Public callback URL (Vistaar Dev posts on_search here)
curl -sS https://bap-amul.netofnet.theflywheel.in/health
curl -sS https://bap-amul.netofnet.theflywheel.in/api/legs | jq .

# Real Vistaar Dev sandbox (no auth, accepts unsigned envelopes for now)
curl -sS -X POST -H "Content-Type: application/json" \
  -d '{
    "context": {"domain":"schemes:vistaar","action":"search","version":"1.1.0",
                 "bap_id":"amul-dev","bap_uri":"https://bap-amul.netofnet.theflywheel.in",
                 "transaction_id":"manual-1","message_id":"msg-1",
                 "timestamp":"2026-04-27T07:00:00.000Z","ttl":"PT10M"},
    "message": {"intent":{"category":{"descriptor":{"code":"schemes-agri"}},
                          "item":{"descriptor":{"name":"KCC"}}}}}' \
  http://43.205.18.120/search

# MH mock chain (host-bound)
curl -sS https://bap-mh.netofnet.theflywheel.in/api/legs   # not exposed; use bap-amul instead
curl -sS https://customdata-mh.netofnet.theflywheel.in/health

What we're talking to

The MOA leg's http://43.205.18.120/search is a BPP, not a Beckn gateway. The on_search callback comes from bpp-network-playground-sandbox-vistaar.da.gov.in (DA, GoI). So our MOA leg is BAP → BPP direct — no gateway tier in this demo.

That's a legitimate Beckn shape: a BAP that knows the BPP (from registry lookup or out-of-band) can call /search on it directly, ACK is synchronous, on_search arrives async on the BAP's own callback URL. Gateways are only needed when a BAP is broadcasting across many BPPs in a domain it doesn't yet know.

Topology

MH leg — mock

MOA leg — real

Amul · BAP

POST /api/search

BAP→BPP
POST /search

POST /on_search
async, ~2-6s later

MOA-style mock chain
POST /bap/caller/search/

on_search

Browser
ui.netofnet.theflywheel.in

amul-bap
Beckn BAP
per-leg envelope builder
implements POST /on_search
correlates by transaction_id

Vistaar Dev · BPP
43.205.18.120/search
DA · GoI sandbox

mh-bap
beckn-onix Go adapter

mh-gateway
mock-gateway

mh-bpp
mock-bpp

mh-customdata

mh-registry

Public TLS termination + per-subdomain Let's Encrypt certs sit in front of the BAP (host nginx → 127.0.0.1:15010) — infra plumbing, not part of the Beckn protocol path. Skipped in the diagram so the protocol shape stays legible.

Stage (b.1) end-to-end sequence

mh-bppmh-gatewaymh-bapVistaar Dev · BPPamul-bap · BAPnetofnet-uiBrowsermh-bppmh-gatewaymh-bapVistaar Dev · BPPamul-bap · BAPnetofnet-uiBrowserbap_id=amul-devdomain=schemes:vistaarcategory.descriptor.code=schemes-agribap_uri=https://bap-amul.netofnet.theflywheel.inBAP exposes /on_search at this URL · Vistaar appends /on_search itself~2-6s async workbap_id=bap-mh.netofnet…domain=retailbap_uri=http://amul-bap:3000/on_searchpar[MOA leg — real, BAP→BPP direct][MH leg — mock chain]POST /api/searchpersona, query, user_id1POST /search2POST /search3200 ACK · message.ack=ACK4POST /on_searchcatalog with PMKISAN-101 etc.5200 ACK6POST /bap/caller/search/7200 ACK8forward9forward10POST /on_search11cache.deliver by transaction_id resolves both promises12response · moa, mh, traces13render — MOA shows real Vistaar items, MH shows mocks14

Stage (a) sequence — both legs mocked (retained for reference)

mh-bpp (mock)mh-gateway (mock)mh-customdatamh-bap (Onix + plugin)moa-bpp (mock)moa-gateway (mock)moa-bap (Onix)amul-bapnetofnet-uiBrowsermh-bpp (mock)mh-gateway (mock)mh-customdatamh-bap (Onix + plugin)moa-bpp (mock)moa-gateway (mock)moa-bap (Onix)amul-bapnetofnet-uiBrowserpar[MOA leg (mock)][MH leg (mock + enrichment)]POST /api/search1POST /search2POST /bap/caller/search/3200 ACK4signed search forwarded5search broadcast6POST /on_search7POST /bap/caller/search/8POST /enrich (Go plugin)9enriched intent10200 ACK11signed search forwarded12search broadcast13POST /on_search14{ moa, mh, traces }15

Per-leg config flow inside the Amul BAP

ACK

non-2xx or timeout

POST /search · query, user_id

loadConfigFromEnv

for each leg · moa, mh

buildSearchEnvelope
per-leg domain, version, city,
categoryCode, bap_id, bap_uri

cache.register transaction_id

POST envelope to gatewayUrl + gatewayPath

await pending callback
bounded by SEARCH_TIMEOUT_MS

null + error
bap_unreachable or timeout

on_search arrives at /on_search
cache.deliver by transaction_id

promise resolves with full body

fanout returns: moa, mh, errors?, traces

respond to UI

What's running

Containers across three docker-compose stacks on sdcrs-demo. Amul BAP + UI on amul and platform; the mocked MH chain on mh. The MOA mocks were retired in stage (b.1) — source preserved under networks/_archive/moa-mock/ for reproducing stage (a).

ServiceWhat it doesRepo path
amul-bapBeckn BAP — per-leg envelope builder, implements POST /on_search, correlates async callbacks by transaction_id, exposes /api/legs introspectionnetworks/amul/orchestrator/
mh-bapbeckn-onix Go adapter, Ed25519 signing, custom customdata-enricher Go pluginnetworks/mh/, networks/mh/plugins/customdata-enricher/
mh-customdataRule-based intent enrichment from yamlnetworks/mh/custom-data
mh-gatewayMinimal Beckn gateway: ACK + forward to BPPsnetworks/shared/mock-gateway
mh-bppMinimal Beckn BPP: ACK + async on_search from catalog.jsonnetworks/shared/mock-bpp
mh-registryfidedocker/registry (Java/Jetty); seeded at runtime via Ansibleunchanged image
netofnet-uiNext.js 14 — persona selector, search form, two-column resultsplatform/ui

Operations

bash
# Bootstrap (idempotent)
ansible-playbook ansible/playbooks/site.yml

# After a code change
git push && ansible-playbook ansible/playbooks/deploy.yml

# Read-only smoke (no mutations)
ansible-playbook ansible/playbooks/verify.yml

verify.yml from a cold cache reports ok=20 failed=0 rescued=0.

Deliberate non-goals at stage (b.1)

  • TLS between internal services, Vault-managed keys (stage c).
  • Real registrar approval flow + signed-envelope verification by the Vistaar BPP. The sandbox accepts unsigned envelopes; a production-style ceremony comes with stage (b.2) integration with a real MH-side network.
  • OTEL / Jaeger / Grafana — queued as the next additive increment (beckn-onix already ships collector configs).
  • Helm charts and CI/CD — stage (c).

Live at ui.netofnet.theflywheel.in · tag stage-b1-v1