Same multi-tenant seller BPP, same aggregating BAP as the in-house build — but now no store owns a delivery fleet. Your BAP wears a second hat: a Logistics BAP that books a carrier (JNE) over the network. Each store's Trade contract earns its own Logistics contract, joined by linkedContractId, and your marketplace escrow pays both the store and the carrier.
Your BAP still aggregates the multi-tenant seller BPP. What's new: once an order is confirmed on the Trade side, the BAP turns around and acts as a Logistics BAP, booking a carrier BPP (JNE) to collect from the store and deliver to Andi. The store hands the parcel to JNE; the parcel never rides a store van.
linkedContractId. You build the multi-tenant seller BPP and the aggregating BAP — exactly as in the in-house guide. JNE is an external Logistics BPP you discover and book over the network; you don't build it. ION stays in the background, providing the catalogue and policy registries everyone consults.
LOG-PARCEL · NEXT_DAY. Operates under a Framework Agreement with your marketplace. Picks up at the store, delivers to Andi.
Unchanged from the in-house build: in Beckn 2.0 each catalogue
binds to exactly one provider — beckn.yaml ties
the Catalog schema to a single
provider field, not an array. Your multi-tenant
BPP publishes one catalogue per tenant; the BAP keys
its local index on provider.id across all of them.
# beckn 2.0: one catalog, one provider — publish this once per tenant catalog: descriptor: { name: Toko Elektronik on Your Marketplace } provider: id: store-a-toko-elektronik descriptor: { name: Toko Elektronik } locations: [ { id: JKT-001, gps: -6.20,106.84 } ] items: [ { id: item-phone-x, price: 5,000,000, location_ids: [JKT-001] } ] fulfillments: [ { id: ful-A-3pl, type: DELIVERY, provider: jne } ]
One field differs from the in-house build: the fulfillment's
provider is now the carrier
(jne), not the store. That mismatch — fulfillment
provider ≠ item provider — is the signal that carriage is
delegated. The store advertises which carrier it ships
with; the actual freight quote arrives later, from JNE.
No store owns a van. When an order is confirmed on the Trade side,
your BAP — now wearing its Logistics-BAP hat — books JNE over the
network. That booking is a second Beckn exchange and earns
its own Logistics contract, joined to the store's Trade contract by
linkedContractId. One order, two linked contracts, per
store.
linkedContractId pointing back at the goods contract. They settle independently — the link is a reference, not a merge. Repeat once per store in the cart. /select → /confirm leg on Andi's behalf — the store never talks to the carrier.awbNumber./init validates against it, not against Andi./on_status events land on your BAP; you relay them to Andi as one timeline, and mirror the milestone onto the Trade contract so the store stays informed.
The Trade × Logistics walk-through
traces a single store's Trade-and-Logistics handshake end to end,
including the linkedContractId exchange. The
difference here is multiplicity: your BAP runs that handshake once
per store in the cart, while the seller BPP fronts every store at
once. Compare with the in-house
build, where there is no carrier and no second contract.
Same escrow as the in-house build — payment.collected_by =
BAP — but now the BAP settles two kinds of payee. Andi still
pays once. The BAP holds the funds, then pays each store
for goods at Trade reconcile and pays JNE for freight at
Logistics reconcile under the FWA, all net of your platform fee.
ION carries references; the money moves off-network.
/on_reconcile per contract — Trade and Logistics alike — carrying statements and NTPN, never the funds. BAP. The buyer app collects once and never lets go of the funds until each contract reconciles — neither the store nor JNE touches Andi's money./on_reconcile. Freight → JNE, settled on each Logistics /on_reconcile under the FWA. Both flow from the same escrow.PRE-FULFILLMENT. Andi authorises at Trade /confirm, before the BAP even books the carrier. The escrow funds the freight too./reconcile runs per contract: once per Trade contract (store payout + NTPN), once per Logistics contract (carrier settlement under FWA).
Same three shapes Beckn allows as the in-house build. Set
collected_by: BPP and each store collects goods
directly with its own QRIS while you still pay JNE — see the
Trade × Logistics walk-through. Or
promote payment to its own sector with a dedicated
Payment BPP that
authorises and settles on-network. In all of them the BAP only
orchestrates — the funds stay off-network.
Walk one store's order through both sectors. Five lanes: Andi,
your BAP, your seller BPP, JNE the Logistics BPP, and ION. The
Trade leg books the goods; the Logistics leg books the carriage;
linkedContractId joins them; your escrow settles both.
The seller BPP is unchanged from the in-house build. The buyer
already picked the provider, so by /select message.order.provider.id is set; the BPP only
forwards. One ONIX route, one backend, an internal
switch on provider_id.
# seller BPP · config/local-routing.yaml — one backend, forward everything routes: - when: { } # every verb, every provider target: http://bpp-backend:9000/beckn # switches on provider.id
What's new lives on the BAP side. After Trade
/on_confirm, the BAP must pick a carrier. That's
not provider selection — it's a routing decision over your
carrier roster: the set of Logistics BPPs you hold an
FWA with. Match the store's pickup pincode and the item's
service level to a carrier, then run the Logistics leg against
it.
# BAP · carrier roster — pick a Logistics BPP for the shipment carriers: - when: { service: NEXT_DAY, from: JKT* } bpp: jne # FWA: marketplace × JNE 2026Q2 - when: { service: SAME_DAY, from: JKT* } bpp: gosend - fallback: jne
So the seller BPP routes inbound by provider; the BAP routes outbound by carrier. Neither one ever chooses the store — Andi did that — but the BAP does choose who carries the parcel, because Andi never sees JNE.
Most per-tenant axes carry over from the in-house build. Delegating carriage adds a handful more — all of them living on the BAP, which now runs a second sector and a second class of settlement.
/init validates against it./on_select. The BAP surfaces it to Andi and funds it from the same escrow.tradeContractId ↔ logisticsContractId mapping. linkedContractId lives on-network; your BAP needs the reverse index to forward status and reconcile.awbNumber and rider details come from the Logistics /on_confirm and /on_status. Relay them to Andi; mirror milestones onto the Trade contract.provider.id and logisticsContractId. The Logistics sector is a second set of dashboards — carrier SLA, pickup latency, lane cost.The seller BPP's per-tenant list — signing keys, inventory, settlement bank, onboarding — is identical to the in-house build; only the fulfilment axis moved off the store and onto the carrier. The asymmetry is still the point: the BAP carries the breadth (every BPP, every carrier), the seller BPP carries the routing fidelity across tenants.