Two states, and only one of them is interesting
An order carries a status, and each fulfilment carries its own state. They answer different questions.
| Level | Answers | Typical values |
|---|---|---|
Order status | Is this order live, done or dead? | ACTIVE, COMPLETE, CANCELLED |
Fulfilment state | What is physically happening? | RESERVED, CHARGING, COMPLETED |
The order status stays ACTIVE for the entire charge. It tells a driver nothing. Everything the UI needs — has charging started, is energy flowing, how much so far — lives in the fulfilment state and its associated tags. Building a progress screen on order status produces an interface that never changes.
The request and response
POST https://cpo.electreefi.in/beckn/status
{
"context": {
"domain": "ev-charging:uei", "action": "status", "version": "1.1.0",
"bap_id": "emsp.example.com", "bap_uri": "https://emsp.example.com/beckn",
"bpp_id": "cpo.electreefi.in", "bpp_uri": "https://cpo.electreefi.in/beckn",
"transaction_id": "txn-4471", "message_id": "msg-9031",
"timestamp": "2026-08-11T09:35:00Z"
},
"message": { "order_id": "ord-EFI-2026-0811-4471" }
}The callback carries the live picture:
POST https://emsp.example.com/beckn/on_status
{
"context": { "...": "same transaction_id", "action": "on_status",
"message_id": "msg-9031" },
"message": {
"order": {
"id": "ord-EFI-2026-0811-4471",
"status": "ACTIVE",
"fulfillments": [{
"id": "FUL-1",
"state": { "descriptor": { "code": "CHARGING" } },
"tags": [{
"descriptor": { "code": "progress" },
"list": [
{ "descriptor": { "code": "energy_delivered" }, "value": "18.400" },
{ "descriptor": { "code": "power" }, "value": "48.2" },
{ "descriptor": { "code": "started_at" },
"value": "2026-08-11T09:12:00Z" }
]
}]
}],
"quote": {
"price": { "currency": "INR", "value": "401.67" },
"breakup": [{ "title": "Energy (18.4 kWh @ 18.50)",
"price": { "currency": "INR", "value": "340.40" } }]
},
"payments": [{ "id": "PAY-1", "status": "NOT-PAID",
"type": "POST-FULFILLMENT" }]
}
}
}quote is an estimate, exactly as in an OCPI Session. It moves as energy accumulates and it is not the invoice. Label it, or field the support ticket that begins “the app said 401 and you charged me 535”.Unsolicited on_status is the better pattern
Beckn permits a BPP to send on_status without a preceding status request. For EV charging this is not a nicety — it is the difference between a good driver experience and a bad one.
| Approach | Cost | Latency |
|---|---|---|
| BAP polls every 30s | N requests per active session, most returning nothing new | Up to 30s stale |
| BPP pushes on state change | One message per actual event | Near real time |
Push on the transitions that matter — charging started, charging stopped, fault raised, session complete — and optionally on a slow heartbeat during a long charge. A BAP still needs polling as a fallback, because pushes get lost, but it should be a safety net rather than the primary mechanism.
This is the same push-plus-reconcile pattern that OCPI uses for Locations and Sessions, and for the same reason: a lost push is silent.
The states that matter for EV charging
| Fulfilment state | Means | Driver-facing |
|---|---|---|
RESERVED | Order confirmed, connector held, not yet plugged in | Reserved — countdown to expiry |
STARTED | Authorised at the charger, session opening | Starting… |
CHARGING | Energy flowing | Live progress |
COMPLETED | Charging finished normally | Summary, final cost pending |
CANCELLED | Order cancelled | Cancelled |
Two gaps worth designing around. First, a session can sit in CHARGING while the vehicle has stopped drawing power — a full battery does not change the state. Compare energy_delivered across successive updates to know whether anything is actually happening. Second, COMPLETED does not mean billed; the final amount follows once the CPO has finalised metering.
Production lessons
- Drive the UI from fulfilment state, never order status.
- Implement unsolicited push if you are a BPP. Polling scales badly on an open network and delivers a worse experience.
- Keep polling as a fallback if you are a BAP, but back it off once pushes are arriving reliably.
- Guard against out-of-order callbacks. Use the context
timestampand discard anything older than the state you hold — the same rule as OCPI'slast_updated. - Age out stalled orders. A charger that drops offline mid-session leaves an order stuck in
CHARGINGforever unless you time it out. - Never bill from a status quote. It is an estimate until the transaction closes.
Frequently asked questions
Order status answers whether the order is live, complete or cancelled and stays ACTIVE for an entire charge. Fulfilment state answers what is physically happening — RESERVED, CHARGING, COMPLETED — and is what a driver-facing UI should be built on.
Yes. Unsolicited on_status is permitted and is the better pattern for EV charging: the BPP pushes on real state changes instead of the BAP polling every 30 seconds. A BAP should still poll as a fallback because pushes can be lost.
No. It is a running estimate that moves as energy accumulates, in the same way an OCPI Session carries an estimated cost. The authoritative figure arrives once the provider has finalised metering.
Compare energy_delivered across successive on_status updates. A session stays in the CHARGING state even when a full battery has stopped drawing power, so the state alone does not tell you energy is flowing.