Skip to main content
Instrumenting a CSMS means adding three calls to code you already have: one when a charge point connects, one per frame, one when the socket closes.
Everything shared across protocols — installing the SDK, getting an API key, configuration, monitoring, shutdown — is in Getting started. This page is the OCPP-specific part.

The model

EVPanda records an OCPP connection: everything that happened on one WebSocket, from handshake to close, under one connection ID. The session handle owns the connection ID, so per-frame calls don’t carry one. A reconnect calls connection() again and gets a fresh ID — which is how the dashboard tells one long session from twenty short ones.

Direction is from the charge point’s perspective

Record it where the actual socket read or write happens. A helper that both sides call is where directions get flipped, and flipped directions make every request look like a response.

Integration steps

1

Start one client at boot

Share it across your process — it is safe for concurrent use. Each client runs its own worker and buffer, so one per socket means thousands of workers and no batching.
The client needs a charger network API key. A roaming network’s key is rejected.
2

Identify the charge point at the handshake

The SDK doesn’t identify chargers for you — your CSMS already does, before it decides whether to accept the connection. Whatever you use for that is what you pass to EVPanda.
If you’re multi-tenant, add the tenant pair here — it is the one place you know it for certain. See Identity.
3

Open a session when the socket opens

connection() records the connect, mints the connection ID, and returns a handle that carries both.
Use one session per socket. Sharing one across connections merges unrelated traffic under one ID; minting a new one per frame makes every frame its own session.
4

Capture every inbound frame

Capture before you handle, so a frame that breaks your handler is still recorded — that is exactly the frame you want to see in EVPanda.
5

Capture every outbound frame

A CSMS writes from more than one place — replies from the read loop, and calls your operators or schedulers initiate. Put the write and the capture behind one helper so nothing can reach the socket uncaptured.
Capture after a successful write. A frame that failed to send never reached the charge point, and recording it as sent makes the validation engine hunt for a response that was never going to come.
6

Close the session when the socket closes

Attach this where you clean up your own connection state — somewhere that runs on an abrupt drop, not just a clean shutdown.
A charge point that loses power never closes cleanly. If disconnect() only runs on your graceful path, the dashboard shows sessions that never end, and flapping chargers look healthy.

A complete example

Uses gorilla/websocket, but nothing here is specific to it — swap in nhooyr.io/websocket and the capture calls stay where they are.
csms.go

Per-SDK notes

The client and session are safe to use from multiple goroutines, but most WebSocket libraries allow only one concurrent writer. Keep the capture call inside the same helper that holds the write lock, so capture and write can never disagree about what was sent.
On earlier published versions connection() does not exist. Use the flat primitives below; everything else on this page is unchanged.
message() accepts either, and encodes a str as UTF-8, so you can pass whatever your WebSocket library hands you.
Capture is safe from async code: delivery runs on a background daemon thread, so the calls never block the event loop and never await.
There is no OCPP redaction — an altered frame is not evidence of what your charger did. If a payload carries data you don’t want stored, an idTag for example, mask it before you hand the frame to the SDK.

Multi-tenant CSMS

Add the tenant pair to the identity at the handshake. It is optional but all-or-nothing — set both fields or neither.
Send a tenant pair on OCPP even if you have one operator. The ingestion API currently requires it on OCPP messages, and rejects those that omit it after a successful delivery — so nothing in the SDK reports the loss. A constant value such as your own operator name is fine. See Identity.

The flat primitives

connection() is built on three lower-level calls. Use them when a session handle doesn’t fit — replaying an archive, capturing from a component that only sees frames, or on a Node version without the handle.
The message call requires both the frame and the direction, and drops the message if either is missing. Connect and disconnect ignore both.
If you mint connection IDs yourself, the ID must be stable for the whole socket and fresh on every reconnect. Reusing the charger ID as the connection ID collapses a charger’s entire history into one endless session.

Common mistakes

Tune the network

Two per-network settings shape what OCPP traffic gets reported as an issue. Both are under the network’s Settings tab.

Next

What gets captured

Event shapes, size limits, and what the SDK deliberately leaves out.

Getting started

Configuration, monitoring, shutdown, and troubleshooting.