Building an OCPP backend looks simple at first.
A charger opens a WebSocket connection, sends a BootNotification, the backend responds, and you're connected.
Then you connect real chargers.
That's where things get interesting.
While building and testing Ureticy, our OCPP software platform, we've worked with real EV charging hardware using both OCPP 1.6J and OCPP 2.0.1.
The biggest lesson?
Supporting OCPP is not the same thing as supporting real OCPP chargers.
Here are some of the lessons we've learned from production.
1. BootNotification is only the beginning
A successful BootNotification feels great.
The charger connects.
The backend accepts it.
Everything looks good.
But that only proves the beginning of the communication works.
The real problems usually appear later:
- Authorization
- Connector status
- Transaction handling
- Meter values
- Remote start / stop
- Reconnection behavior
- EVSE / connector mapping
- Vendor-specific behavior
A successful boot should therefore be treated as the beginning of integration testing, not the end.
2. Real chargers don't always behave like simulators
Simulators are extremely useful during development.
But production hardware introduces another level of complexity.
We've seen chargers connect and boot successfully, then behave differently than expected during authorization, transaction handling or status updates.
This is particularly important when integrating hardware from multiple manufacturers.
That's why logging and observability become critical.
For every OCPP message, we want to know:
- Which charger sent it?
- Which OCPP version was used?
- What action was requested?
- What payload was received?
- What response was returned?
- How long did processing take?
Without this information, remotely debugging a charging station becomes painful very quickly.
3. OCPP 1.6J and OCPP 2.0.1 are fundamentally different
One mistake is treating OCPP 2.0.1 as simply a newer version of OCPP 1.6J.
The architecture changed significantly.
In OCPP 1.6J, transactions commonly revolve around:
StartTransaction → MeterValues → StopTransaction
With OCPP 2.0.1, transaction handling revolves around TransactionEvent:
Started → Updated → Ended
That difference affects how a backend should model charging sessions.
We've found it much cleaner to separate protocol-specific processing from the internal business logic.
Conceptually:
OCPP 1.6J / OCPP 2.0.1 → Protocol Layer → Normalized Events → Business Logic → API / Applications
This allows the rest of the platform to work with a consistent model without needing to understand every protocol-specific detail.
4. EVSE and connector mapping matters
This becomes especially important with OCPP 2.0.1.
You should not simply assume:
EVSE 1 = Connector 1
A charging station can expose multiple EVSEs and connectors.
A safer internal structure is:
Charging Station → EVSE → Connector
It sounds obvious when written down.
It's much less obvious when you're debugging a real charger at 2 AM because a transaction has been associated with the wrong connector. 😅
5. MeterValues need defensive handling
Energy data sounds simple.
Receive a value and store it.
Real chargers can report many different measurements:
- Energy
- Power
- Current
- Voltage
- State of Charge (SoC)
- Temperature
And not every charger provides every measurement.
A production backend should therefore never assume that every value will always exist.
Energy available? Display it.
Power available? Display it.
SoC available? Display it.
Not available? Continue normally.
Your system shouldn't fall apart because one charger doesn't provide SoC.
6. RemoteStart does not mean charging has started
This distinction is important.
A successful remote-start request does not necessarily mean energy is already flowing.
The actual sequence may look more like:
Remote command accepted → Authorization → Transaction created → EV ready → Energy starts flowing
Applications should distinguish between these states.
Otherwise, a customer might see "Charging" while the charger has merely accepted the remote command.
The same principle applies when stopping a charging session.
7. WebSocket reliability matters
EV chargers maintain long-lived connections.
Real networks, unfortunately, have other plans. :)
Routers restart.
Internet connections disappear.
Mobile networks become unstable.
Chargers sometimes reconnect without a perfectly clean disconnect.
A production OCPP backend needs to handle:
- Heartbeats
- Stale connections
- Reconnection
- Duplicate sessions
- Unexpected disconnects
- Transaction recovery
- Charger availability
A charger being online five seconds ago doesn't necessarily mean it's online now.
8. Observability is a product feature
This was one of our biggest lessons.
At first, logging feels like an engineering concern.
Once you start integrating real chargers, it becomes a product feature.
When something fails, developers need to answer one question immediately:
What exactly did the charger send?
This is also one of the reasons we built a small developer tool for working with OCPP messages.
OCPP Software by Ureticy — Chrome Extension
It supports OCPP 1.6J and OCPP 2.0.1 message analysis directly inside the browser.
And importantly:
🔒 OCPP payloads stay local.
Messages are parsed and analyzed inside the extension. They are not uploaded to Ureticy or any external server.
👉 OCPP Software by Ureticy — Chrome Web Store
9. Separate protocol logic from business logic
This is probably one of the architectural decisions we're happiest with.
Your OCPP layer should understand OCPP.
Your business layer should understand your product.
The architecture can look roughly like:
OCPP Message → Protocol Handler → Normalized Event → Business Rules → Database / API / Application
The business layer shouldn't need to care whether an event originated from StartTransaction in OCPP 1.6J or TransactionEvent in OCPP 2.0.1 when that distinction isn't relevant.
This separation also makes supporting additional protocol versions significantly easier.
10. Don't build only for the happy path
A perfect charging session might look like:
Connect → Boot → Authorize → Start → MeterValues → Stop → Disconnect
Production isn't always that clean.
You also need to ask:
What if authorization succeeds but the transaction never starts?
What if the charger disconnects during an active transaction?
What if MeterValues suddenly stop arriving?
What if the charger reconnects during the session?
What if a connector reports an unexpected status?
What if your backend restarts while chargers are connected?
The interesting part of building an OCPP backend isn't processing the perfect transaction.
It's surviving the imperfect ones.
Building Ureticy
These lessons are part of what led us to build Ureticy.
Ureticy is an OCPP software platform for EV charger manufacturers, CPOs, developers, fleets and companies building EV charging infrastructure.
We're developing the platform around real charging hardware and real OCPP communication, with support for OCPP 1.6J and OCPP 2.0.1.
⚡ OCPP Software:
👉 Ureticy — OCPP Software Platform
🇹🇷 Türkiye:
👉 Ureticy — OCPP Yazılımı
🧩 Developer Tool:
👉 OCPP Software by Ureticy — Chrome Extension
Final thought
If you're starting an OCPP backend today, my biggest recommendation would be:
Design for imperfect chargers and imperfect networks from day one.
The OCPP specification gives you the language.
Production hardware teaches you the dialects.
We're still learning those dialects ourselves.
If you're building chargers, a CSMS or anything around OCPP, I'd love to hear:
What's the strangest OCPP behavior you've encountered from real charging hardware?
Top comments (0)