–Every OCPP tutorial explains how the charger talks to the backend. Almost none explain how the driver gets in.
If you’ve read our guides on building an OCPP 1.6 central system with Flask or architecting a full EV charging network, you know the protocol side well: BootNotification, StatusNotification, Heartbeat, the WebSocket handshake. That’s the charger-to-backend half of the system.
The other half — how an actual driver, standing at a charger with a phone, starts a session — gets skipped almost everywhere. Most teams default to "we’ll build a native app," which quietly becomes the most expensive line item in the project: two codebases, app store review cycles, forced updates whenever a fee structure changes, and a download step most drivers won’t bother with for a charger they’ll use once.
There’s a simpler pattern, and it’s the one we run in production: a QR code at the charger opens a mobile web page, no install, no account required, and the driver is charging within four taps. This post walks through how that works under OCPP, what your backend actually needs to build, and includes a live setup flow you can generate a real QR code from right now.
Table of Contents
- Where Driver Authorization Fits in OCPP
- Two Ways to Authorize a Session: RFID vs App-Generated ID Tags
- The QR-Code Flow, End to End
- What Your Backend Needs to Build
- Try It Live
- Compliance Notes
- FAQ
Where Driver Authorization Fits in OCPP
OCPP 1.6 doesn’t care how a driver gets authorized — it only cares that an idTag shows up attached to an Authorize.req or a StartTransaction.req. The spec is agnostic about the source of that tag. It could be:
- A physical RFID card, read by the charger’s own reader
- A tag typed into the charger’s local keypad
- A tag your backend hands to the charger remotely via
RemoteStartTransaction.req
That third option is the one that makes app-free charging possible. Your backend doesn’t wait for the charger to read a card — it pushes a start command with a generated idTag the instant the driver taps "Start" in the web page. The charger receives it exactly the way it would receive an RFID swipe.
CS→CP: RemoteStartTransaction.req(connectorId, idTag)
CP→CS: RemoteStartTransaction.conf(status=Accepted)
CP→CS: StatusNotification.req(status=Preparing)
CP→CS: StartTransaction.req(connectorId, idTag, meterStart, timestamp)
CS→CP: StartTransaction.conf(transactionId, idTagInfo.status=Accepted)
This is the same message sequence your dashboard uses when an operator clicks "Remote Start" on a charge point — the driver app is just a second caller of the same backend function, scoped to one charger and one session.
Two Ways to Authorize a Session: RFID vs App-Generated ID Tags
Both models can run side by side on the same CSMS, and most production deployments do:
| RFID Card | App-Generated ID Tag | |
|---|---|---|
| Setup per user | Physical card, mailed or handed out | None — generated on first scan |
| Where authorization happens | At the charger’s own reader | Backend, then pushed via RemoteStart |
| Best for | Fleet depots, staff, repeat users with an existing card program | Public chargers, hotel guests, one-off visitors |
| Revocation | Deactivate the card’s id_tag record |
Session-scoped, nothing to revoke |
| Backend requirement | User record with matching id_tag |
URL-to-charger mapping + RemoteStart call |
The RFID path needs a User record whose id_tag field exactly matches the string the card transmits — unregistered tags are rejected outright. The app-generated path skips user records entirely for casual drivers: the web page itself creates a disposable tag scoped to that charging session and registers it automatically.
For most public and semi-public deployments — hotel parking, retail, apartment visitor charging — app-generated tags are the better default, because you’re not asking a one-time visitor to go through a card-issuance process to charge for twenty minutes.
The QR-Code Flow, End to End
flowchart TD
A["Driver scans QR code at the charger"] --> B["Mobile web page opens no install"]
B --> C["Select connector if charger has more than one"]
C --> D["Enter charging budget or set custom amount"]
D --> E["App generates ID tag and calls RemoteStartTransaction"]
E --> F["Charger authorizes and starts the session"]
F --> G["Live session view shows energy cost and duration"]
G --> H["Driver taps Stop"]
H --> I["Receipt is generated and shown"]
Each QR code encodes a single URL: the charger’s ID and optionally a connector number, e.g. https://your-domain.com/ev?cp=CP_014&connector=2. That’s the entire "app install" step — a driver’s camera app resolves it and opens a browser tab. No App Store listing, no Play Store review queue, no forced update push when you change your pricing model next quarter.
From there the flow is intentionally short: pick a connector if the charger has more than one, set a budget (a fixed amount like ฿50/฿100 or a custom figure), tap start, watch the live session, tap stop, get a receipt. Four taps is the target — every additional screen is a driver who abandons before charging starts.
What Your Backend Needs to Build
Three pieces, on top of an OCPP server you likely already have:
1. A stateless URL scheme. GET /ev?cp={charge_point_id}&connector={n} — no session token needed until the driver actually starts charging. This is what the QR code encodes and what makes it printable and static; you never have to reissue a QR code because the URL never expires or rotates.
2. A RemoteStartTransaction endpoint scoped to that charger. When the driver taps "Start," your backend generates a disposable idTag (a UUID is fine), registers it against a short-lived session record, and calls RemoteStartTransaction on the specific charge point from the URL. This reuses the exact remote-start logic your operator dashboard already calls — you’re not building new OCPP handling, just a second, narrower caller.
3. A polling or WebSocket feed for the live session view. MeterValues.req messages arrive from the charger every 10–60 seconds during an active transaction. Surface Energy.Active.Import.Register as running kWh and multiply by your rate to show live cost — this is what makes the "Live session" screen feel real-time rather than a static "charging…" spinner.
None of this requires a new architecture if you already have an OCPP 1.6 backend. It’s a thin driver-facing layer over commands your CSMS already supports.
Try It Live
Rather than describe this in the abstract, we built the actual flow: enter a charge point ID, get a working QR code and driver app URL, generated live from a real (demo) CSMS.
Open the setup and QR generator →
It walks through connecting a charge point, requesting WebSocket credentials, registering ID tags, and — the part most relevant here — generating a driver app URL and QR code for any charge point ID, with a live view of which demo chargers are currently connected. If you want to see the CSMS side that powers this, the full dashboard and API walkthrough covers the backend in depth.
Compliance Notes
A no-install web app still collects data — session duration, energy delivered, and (if you take payment) transaction records. A few things worth building in from the start rather than retrofitting:
- Session data minimization. App-generated ID tags should be scoped to a single session and not linked to a persistent driver identity unless the driver explicitly creates an account. This limits what you’re responsible for if a device is lost or a session is disputed.
- Receipt retention. Most jurisdictions expect energy-purchase receipts to be retrievable for a minimum retention period even without an account — consider emailing or SMS-ing the receipt at session end rather than only displaying it in-browser.
- Rate transparency. Show the per-kWh or per-minute rate before the driver commits to a budget, not just the running total afterward.
FAQ
Do I need a native app at all if I use this pattern?
Not for casual or public charging — the web-based flow covers the full session lifecycle: start, live monitoring, stop, receipt. A native app becomes worth building once you need offline queuing, push notifications for "charging complete," or a persistent loyalty/wallet system tied to a driver account.
What happens if the driver closes the browser tab mid-session?
The charging session itself is unaffected — it’s running on the charger and tracked by your backend via the transaction ID, independent of the browser tab. Reopening the same QR-encoded URL (or any link with the same cp and connector parameters) reconnects to the live session in progress.
Can this coexist with a native app for registered fleet drivers?
Yes. Run RFID or a native app for registered users with standing accounts, and the QR/web flow for public or visitor charging on the same chargers and the same backend. The two paths differ only in how the idTag is generated and authorized — the OCPP message flow underneath is identical.
Does this work with OCPP 2.0.1 as well as 1.6?
Yes, with one difference: 2.0.1 uses RequestStartTransactionRequest instead of RemoteStartTransaction.req, and ID tokens carry an explicit type field (Central, ISO14443, etc.) rather than a bare string. The QR-and-web-page pattern itself is version-agnostic — only the backend call changes.
Building the driver-facing side of an OCPP deployment, or want to see how this fits into a full CSMS? We run this exact stack in production.
Try the live demo — generate a real QR code in under a minute.
hello@simplico.net — we typically respond within one business day.
Simplico Co., Ltd. · Bangkok, Thailand · Serving ASEAN and Japan
Latest Posts
- On-Premise LLM Deployment: Hardware, Models, and TCO for Enterprise (2026) July 1, 2026
- Your Quality Records Are a Fire Hazard June 27, 2026
- Why Your Factory Floor Is the Softest Target in Your Network June 27, 2026
- Enterprise Local LLM Readiness Assessment June 26, 2026
- Why Enterprises in Southeast Asia and Japan Are Moving LLMs Inside the Firewall June 26, 2026
- How to Choose a Technology Partner in Southeast Asia: A Practical Evaluation Guide for Enterprise Teams June 24, 2026
