BECKN CONCEPT · UPDATED 11 AUG 2026

context: the envelope every message shares

Every Beckn message, in both directions, carries the same envelope. Get the context object right and asynchronous callbacks correlate themselves. Get it wrong and you have a distributed system with no way to know which reply belongs to which question.

PART OF THE BECKN FOR EV CHARGING GUIDE · 10 ACTION & CONCEPT GUIDES

IN ONE PARAGRAPH

context carries routing, identity, correlation and expiry. Two fields do most of the work: transaction_id, which stays constant across an entire business transaction, and message_id, which is unique per request-callback pair. Almost every hard Beckn bug traces back to conflating them.

Why the envelope matters more here

Beckn has no synchronous responses that carry business data. A BAP sends search and receives 200 ACK; the actual catalogue arrives later as a separate HTTP request in the opposite direction, possibly from several providers, possibly seconds apart.

That inversion means the reply cannot be matched to the request by connection. It has to be matched by data, and context is that data. In a synchronous protocol the envelope is bookkeeping; here it is the mechanism.

The fields

FieldPurposeChanges
domainWhich network — e.g. ev-charging:ueiNever within a transaction
actionsearch, on_search, selectEvery message
versionCore spec version, e.g. 1.1.0Never
bap_id / bap_uriBuyer platform identity and callback addressNever
bpp_id / bpp_uriProvider identity and addressSet once a provider is chosen
transaction_idThe whole business transactionNever
message_idOne request and its callbackEvery request
timestampWhen sent, RFC3339Every message
ttlHow long this is valid, ISO 8601 durationPer message
locationCountry and city for discovery routingRarely

transaction_id versus message_id

This is the distinction that causes the most damage when it is missed.

transaction_id is the business transaction. One driver deciding to charge, from the first search through to the final settled quote. It survives every action in the chain. It is what you use to reconstruct what happened when a customer calls three weeks later.

message_id is one request and its callback. A new one for every action you send. It is what tells you that this on_select answers that select.

transaction_id: txn-4471                     ← constant throughout

  search   message_id: msg-9021  →  on_search   msg-9021
  select   message_id: msg-9022  →  on_select   msg-9022
  init     message_id: msg-9023  →  on_init     msg-9023
  confirm  message_id: msg-9024  →  on_confirm  msg-9024
  status   message_id: msg-9031  →  on_status   msg-9031
The classic bug: reusing message_id across actions. It appears to work in testing, where one action completes before the next begins. In production, where a slow on_select can land after init has been sent, callbacks get applied to the wrong step. The symptom is corrupted orders that nobody can reproduce.

The mirror-image bug is generating a fresh transaction_id per action, which destroys the ability to correlate anything and turns a five-step transaction into five unrelated events.

Search is the exception

One search produces many on_search callbacks — one per responding BPP. They all carry the same transaction_id and the same message_id, distinguished only by bpp_id.

So the correlation key for search results is the triple (transaction_id, message_id, bpp_id). Storing catalogue responses keyed on message_id alone means each provider's catalogue overwrites the last, and the driver sees results from whichever BPP happened to answer slowest.

TTL, and honouring it

ttl is an ISO 8601 duration — PT30S for thirty seconds, PT2M for two minutes. It states how long the message is meaningful.

  • As a sender, set it to something the receiver can actually meet. A one-second TTL on a search that fans out across a network guarantees failure.
  • As a receiver, stop processing once it expires. Answering a search whose TTL lapsed wastes work nobody will read.
  • On callbacks, discard anything arriving after its window. A quote that lands after the driver moved on must not be applied — showing a price they never saw is worse than showing nothing.

TTL is also your defence against unbounded state. Without it, a BAP accumulates open transactions waiting for callbacks that will never come.

Production lessons

  • Generate transaction_id once, at the first search, and thread it through everything.
  • Generate a fresh message_id per outbound request. Never reuse.
  • Key search results on (transaction_id, message_id, bpp_id).
  • Log the full context on every message, both directions. It is the entire audit trail, and partner disputes are unresolvable without it.
  • Validate bap_uri against the registry, do not trust the payload. An unvalidated callback URI is an open redirect — see signing and the registry.
  • Enforce TTL on both sides, and expire stale transactions rather than letting them accumulate.
  • Never mutate domain or version mid-transaction.

Frequently asked questions

What is the difference between transaction_id and message_id in Beckn?

transaction_id identifies the whole business transaction and stays constant from the first search through to completion. message_id identifies one request and its matching callback, and must be new for every request sent. Conflating them causes callbacks to be applied to the wrong step.

How do you correlate multiple on_search responses?

By the triple transaction_id, message_id and bpp_id. One search produces many on_search callbacks that share the same transaction_id and message_id, distinguished only by which provider sent them, so keying on message_id alone causes each catalogue to overwrite the last.

What does ttl mean in a Beckn context object?

An ISO 8601 duration stating how long the message remains meaningful — PT30S is thirty seconds. Receivers should stop processing expired requests, and senders should discard callbacks that arrive after the window, since applying a stale quote shows a driver a price they never saw.

Why does Beckn need a context object at all?

Because Beckn callbacks arrive as separate HTTP requests in the opposite direction, often from multiple providers seconds apart. The reply cannot be matched to the request by connection, so it has to be matched by data, and context carries that data.