Skip to content
NFH Fabric Support home
NFH Fabric Support home

Migration Guides

Guides for moving a Beckn Network deployment between various topologies without breaking network interoperability.

Protocol upgrades happen while the network keeps running — consumer-side (BAP) and provider-side (BPP) applications can't all be rewritten and redeployed at once. These guides cover how to move a deployment from one network topology to another with a phased, low-risk approach.

This guide covers migration using the request mapper plugin built into the ONIX adapter.


Business Context

Why — the migration problem

Beckn Protocol v2.x introduces a significantly improved data model designed to handle complex, multi-party commerce more precisely and consistently across sectors such as agriculture, financial services, and utilities. It standardises concepts that were loosely defined in v1 — for example, what constitutes an "item", how pricing is structured, how a transaction is tracked end-to-end, and how metadata is attached to catalog entries.

However, the real-world networks and applications built on Beckn v1.x — including buyer-side (BAP) and seller-side (BPP) platforms — represent significant investment. Requiring every participant to simultaneously rewrite their systems to speak v2 is both risky and operationally challenging. A failed or delayed migration by any single participant could break the entire network's interoperability.

The core business problem is: how do we upgrade the network protocol without forcing every participant to upgrade at the same time?

What — the mapper as a migration bridge

The mapper is the answer to that problem. It is a configuration-driven translation layer built into the ONIX adapter that converts payloads between v1 and v2 transparently — in both directions.

From a business perspective, this means:

  • BPP operators (catalog providers, scheme administrators, procurement platforms) continue publishing and responding in v1 format. They do not need to change their backend systems.

  • BAP operators (buyer apps, beneficiary portals, market linkage platforms) continue sending and receiving v1 format. No application rebuild is required.

  • The Beckn network operates on v2, gaining the benefits of the improved schema. Once BAP and BPP applications are ready, they can upgrade to v2 independently and in parallel — at which point the mapper can be bypassed and eventually retired, completing the migration with no forced coordination between participants.

  • The adapter sits in between, translating every message as it passes through — invisibly to both the application and the network participant on the other side.

This decouples the protocol upgrade from the application upgrade, enabling a phased, low-risk migration.

How — the approach

The adapter intercepts every Beckn message at the boundary between the application and the network. Depending on direction:

  • Outbound (application → network): the application sends a v1-formatted request. The adapter converts it to v2 before forwarding it to the Beckn network.

  • Inbound (network → application): the network sends a v2-formatted message. The adapter converts it back to v1 before delivering it to the application.

This conversion is driven entirely by configuration — a YAML file of JSONata transformation expressions, one per Beckn action (search, publish, confirm, status, etc.) and per role (buyer-side or seller-side). No adapter code changes are needed when mapping rules evolve; only the configuration file is updated and the adapter is restarted.

The result is that both the application layer and the network layer believe they are talking to a compatible peer, while the adapter silently handles all schema translation between them.

Technical Overview

The ONIX adapter bridges Beckn Protocol v1.x and v2.x networks using a file-based transformation layer called the request mapper. Rather than hard-coding payload shapes in the adapter binary, all transformation logic lives in a single YAML file containing JSONata expressions. The adapter loads this file at startup and applies the right expression automatically based on the action and the participant role (BAP or BPP).

This guide explains how the mapper is wired into ONIX, what it transforms, and how transformations are structured for each Beckn action.

How the Mapper Plugs Into ONIX

Configuration

The mapper is declared as a plugin named payloadTransformer in the adapter config (configs/bpp-config.yaml / bap-config.yaml):

payloadTransformer: id: reqmapper config: role: bpp # or "bap" mappingsFile: /app/config/mappings.yaml

role tells the plugin which side of the mapping to apply. mappingsFile is the path to the YAML file mounted into the container.

The transformPayload step

Each module (Receiver and Caller) defines an ordered list of pipeline steps. The transformPayload step is where payload conversion happens:

BAP Receiver (/bap/receiver/) — receives v2 callbacks from the network, converts to v1 for the BAP app:

validateSign → addRoute → validateSchema → transformPayload

BAP Caller (/bap/caller/) — receives v1 requests from the BAP app, converts to v2 for the network:

transformPayload → addRoute → sign → validateSchema

BPP Receiver (/bpp/receiver/) — receives v2 from the network, converts to v1 for the BPP app:

validateSign → addRoute → validateSchema → transformPayload

BPP Caller (/bpp/caller/) — receives v1 from the BPP app, converts to v2 for the network:

transformPayload → addRoute → sign → checkPolicy → validateSchema

The step position determines which payload version the surrounding steps see. On Receiver modules, schema validation runs before transformPayload (validating the incoming v2 payload); on Caller modules, it runs after (validating the outbound v2 payload).

Mapping Architecture: bapMappings vs bppMappings

Each Beckn action in the mappings file has two entries:

mappings: <action>: bapMappings: | (JSONata expression...) bppMappings: | (JSONata expression...)

Entry

Direction

Applied when

bapMappings

v1 → v2 (outbound) or v2 → v1 (inbound)

adapter running as BAP

bppMappings

v2 → v1 (inbound) or v1 → v2 (outbound)

adapter running as BPP

The role field in the plugin config (bap or bpp) determines which entry is used at runtime. This keeps a single mappings file shared between both roles.

Transformation Levels

Transformations happen at two levels in every action:

1. Context level (universal)

Every message — regardless of action — carries a context block. The field names changed between v1 and v2:

v1 field

v2 field

Notes

domain

networkId

Identifies the Beckn network/domain

bap_id

bapId

Consumer platform identifier

bap_uri

bapUri

Consumer platform endpoint

bpp_id

bppId

Provider platform identifier

bpp_uri

bppUri

Provider platform endpoint

transaction_id

transactionId

Correlation ID for a full transaction

message_id

messageId

Unique ID per request

version: "1.0.0"

version: "2.0.0"

Protocol version literal

The mapper always rewrites version and all snake_case → camelCase context fields.

2. Message/payload level (per-action)

Each action also transforms the message body. These are the substantive schema changes between v1 and v2.

Per-Action Transformations

discover (v1 search → v2 discover)

bapMappings (BAP outbound: v1 → v2)

The v1 message.intent is deconstructed into two v2 constructs:

  • filters — JSONPath expressions derived from intent fields:

    • Language preference → JSONPath on descriptor.tags

    • Fulfillment type → JSONPath on fulfillments[].type

    • Address/city/state → JSONPath on provider location

    • Price range → JSONPath on offers[].considerations[].considerationAttributes

    • Government scheme eligibility criteria → JSONPath on entitlementIds

  • spatial — geo-search using the S_DWITHIN operator, built from GPS coordinates in the intent:

{ "op": "S_DWITHIN", "path": "$.provider.availableAt", "value": [lon, lat], "distance": 50000 }

Coordinates follow GeoJSON convention ([longitude, latitude]); default radius is 50 km.

bppMappings (BPP inbound: v2 → v1)

Reverses the above: v2 filter/spatial objects are not applicable on the BPP side; the BPP receives a standard v1 search intent.

on_discover (v2 catalog response → v1 on_search)

This is the most complex mapping because v2 restructured the catalog schema entirely.

bapMappings (BAP inbound: v2 → v1)

The v2 message.catalogs[] structure is mapped back to the v1 message.catalog.providers[] shape:

v2 structure

v1 structure

catalogs[].resources[]

providers[].items[]

catalogs[].offers[]

Merged into items via resourceIds linkage

resourceAttributes.entitlementIds[]

items[].tags[] (structured tag groups)

catalogs[].provider.availableAt[]

providers[].locations[]

catalogs[].offers[].considerations[]

items[].price

The entitlementIdstags conversion handles multiple named groups:

entitlementIds[].code

v1 tags[].descriptor.code

targetBeneficiaryType

target-beneficiary-type

fundingModel

funding-model

benefitFor + benefitValue

benefit

requiredDoc

required-docs

economicEligibility

economic-eligibility

demographicEligibility

demographic-eligibility

categoryIds

category_ids (item field)

locationIds

location_ids (item field)

Unknown codes

Passed through as generic display tags

catalog/publish (v1 catalog publish → v2)

bppMappings (BPP inbound: v1 → v2)

A BPP application sends a v1-style catalog publish. The mapper converts it to the v2 catalog structure:

v1 structure

v2 structure

catalog.providers[]

catalogs[] (one catalog per provider)

providers[].items[]

catalogs[].resources[] + catalogs[].offers[]

items[].tags[]

resources[].resourceAttributes.entitlementIds[]

items[].price

offers[].considerations[].considerationAttributes

providers[].locations[]

catalogs[].provider.availableAt[]

providers[].fulfillments[]

catalogs[].resources[].resourceAttributes fields

Each v1 item becomes both a resource (the thing being offered) and an offer (the commercial terms). Offer resourceIds reference the corresponding resource id, establishing the linkage.

The tagsentitlementIds conversion is the inverse of the on_discover conversion above.

confirm (v1 order → v2 contract)

This action renamed the top-level transactional object.

bapMappings (BAP outbound: v1 → v2)

v1 structure

v2 structure

message.order

message.contract

order.provider

contract.participants[0]

order.items[]

contract.commitments[] (each item → a commitment with resource + offer)

order.fulfillments[]

contract.performance[].performanceAttributes

order.payment

contract.commitments[].offer.considerations[].considerationAttributes

items[].tags[]

commitments[].resources[].resourceAttributes.entitlementIds[]

bppMappings (BPP inbound: v2 → v1)

Reverses the above — contractorder, commitmentsitems, performancefulfillments.

status (v1 status → v2 status)

bapMappings (BAP outbound: v1 → v2)

Minimal transformation: only the context fields change. The message body maps order_id to contract.id:

v1: message.order_id v2: message.contract.id

bppMappings (BPP inbound: v2 → v1)

Reverses: contract.idorder_id.

on_confirm and on_status (v2 contract response → v1 order response)

Both follow the same pattern as confirm.bapMappings in reverse:

bppMappings (BPP outbound: v1 → v2)

The BPP app returns a v1 order with status. The mapper builds:

  • contract.id from order.id

  • contract.status.code from order.state (mapped through a status-code lookup)

  • contract.participants[] from order.provider

  • contract.commitments[] from order.items[] (including entitlementIds from tags)

  • contract.performance[] from order.fulfillments[]

bapMappings (BAP inbound: v2 → v1)

Converts the v2 contract response back to v1 order format for the BAP application, reconstructing the full tags structure from entitlementIds.

Summary of Schema Changes (v1 → v2)

Category

v1

v2

Context field names

snake_case (bap_id, bpp_uri)

camelCase (bapId, bppUri)

Protocol version

"1.0.0"

"2.0.0"

Domain identifier

context.domain

context.networkId

Catalog items

providers[].items[]

catalogs[].resources[] + catalogs[].offers[]

Item metadata

items[].tags[] (grouped, hierarchical)

resourceAttributes.entitlementIds[] (flat, coded)

Order object

message.order

message.contract

Order parties

order.provider + order.fulfillments[].agent

contract.participants[]

Order items

order.items[]

contract.commitments[]

Fulfillment status

order.fulfillments[].state

contract.performance[].status

Price

items[].price.value (string)

considerationAttributes.value (number)

Geo search

Intent-based (flat address)

filters (JSONPath) + spatial (S_DWITHIN)

Adapter Configuration

The mapper file path is configured separately in the BAP and BPP adapter configs under payloadTransformer.config.mappingsFile. Both roles (BAP and BPP) share the same mappings file — the role field in the plugin config (bap or bpp) determines which entry (bapMappings or bppMappings) is applied at runtime.

Adapter config

Role

Modules configured

bap-config.yaml

bap

bapTxnReceiver (port 8081, /bap/receiver/) and bapTxnCaller (/bap/caller/)

bpp-config.yaml

bpp

bppTxnReceiver (port 8082, /bpp/receiver/) and bppTxnCaller (/bpp/caller/)

When deploying, the mappings file is mounted into the container and its path is referenced by mappingsFile. No adapter code changes are required when updating the mappings — only a container restart is needed.

Challenges and Maintenance Considerations

Mapper must be updated for every new field

The JSONata expressions in the mappings file explicitly enumerate every field that needs to be carried across. If a new field is added to a v1 payload (by the BPP or BAP application) and is not handled in the corresponding mapping expression, that field will be silently dropped during transformation. The adapter will not throw an error — it simply produces an output that omits the unrecognised field.

This means:

  • Any new v1 field added to order, items, tags, fulfillments, or context must have a corresponding mapping added to confirm.bapMappings / confirm.bppMappings (and likewise for other actions).

  • Any new v2 field added by the network side must be handled in the reverse direction.

  • The same applies to new entitlementIds codes: if a BPP starts sending a new code in v2, the on_discover.bapMappings (and on_confirm/on_status equivalents) must add a rule to convert it back to the correct v1 tags group. Without this, the tag is either dropped or falls through to the generic "unknown" passthrough handler (where one exists).

No schema-awareness at transform time

The mapper operates on raw JSON; it has no knowledge of the Beckn schema. If the input payload has an unexpected structure (e.g., items is an object instead of an array), the JSONata expression may produce an incorrect output silently. Schema validation (the validateSchema step) is the safety net — but it runs separately from the transform, and only validates the version it receives.

Bidirectional parity must be maintained

bapMappings and bppMappings for a given action are mirror images of each other. When one is updated, the other typically needs a matching update. Allowing them to diverge can cause round-trip data loss (e.g., a field survives the outbound transform but is missing from the inbound response).

Adding or Modifying a Mapping

  1. Open the mappings file (path configured in the adapter config under payloadTransformer.config.mappingsFile).

  2. Find the relevant action block (e.g., on_discover:).

  3. Edit the bapMappings or bppMappings JSONata expression — and update the mirror direction if the change affects round-trip data.

  4. Redeploy the adapter — no code changes needed, only a container restart.

  5. Verify with the relevant test collection covering the affected action.

JSONata expressions have access to the full input payload via $ and support functions like $map, $filter, $merge, $append, $reduce, $string, $number, $boolean, $replace, startswith, and sprintf-style string formatting via the adapter's runtime.