Your operations dashboard says a station is offline. On-site, the hardware looks fine — screen on, network light green, nothing obviously broken. But it never accepts a session. Nine times out of ten, the story ends at a single message: the charge point sent a BootNotification and the CSMS answered Rejected (or left it looping on Pending), so the station never earned the right to operate.
This article is the hands-on companion to the BootNotification message reference (coming soon). That piece explains every field and status; this one is a debugging session. We reproduce a rejection live, read the actual OCPP-J frames, and work through each root cause. If you want to follow along instead of just reading, open Rey, the free browser-based virtual charge point — it opens a real OCPP WebSocket — 1.6, 2.0.1, or 2.1 — to your own CSMS (or a built-in demo one) and shows you every frame in both directions. No hardware, no install.
If OCPP itself is new to you, what is OCPP sets the scene first.
First, know which layer failed
The single most common debugging mistake is treating “the station won’t boot” as one problem. It’s two problems that look identical from the dashboard:
- The WebSocket never opened. BootNotification was never sent. The failure is in the transport: wrong CSMS URL, DNS failure, a TLS handshake the charger rejected, or a
401during the WebSocket upgrade because the Basic-auth credential was wrong or missing. - The WebSocket opened, the boot was sent, and the CSMS answered
Rejected. The transport is fine. The registration decision is the problem.
These have completely different fixes, and you cannot tell them apart from an “offline” badge. You have to see the frames. This is exactly where a virtual charge point earns its keep: Rey shows the connection state and the raw request/response, so within a few seconds you know whether you’re debugging the socket or the payload.
If you’d rather learn the shape of a clean, successful sequence first — every frame in a healthy boot-authorize-transaction flow — the canned OCPP simulator replays it passively. Learn the flow there, then come back and drive the live version.
Reproducing a rejection in the browser
Let’s manufacture the failure on purpose so we know its fingerprint.
In Rey, switch from Explore to Connect my CSMS, enter your backend’s wss:// URL, and — here’s the trick — enter a station id your CSMS has never heard of, something like TEST-does-not-exist-01. Send the boot.
With OCPP 2.0.1 selected, the request frame Rey sends is a BootNotification:
[2, "a3f9-1", "BootNotification", {
"chargingStation": {
"model": "Rey-VCP",
"vendorName": "LearnEVCharging",
"serialNumber": "TEST-does-not-exist-01"
},
"reason": "PowerUp"
}]
And the response frame comes back:
[3, "a3f9-1", {
"currentTime": "2026-07-12T09:14:22.000Z",
"interval": 1800,
"status": "Rejected"
}]
That is the whole diagnosis, right there in the second frame. status is Rejected. The interval of 1800 is not a heartbeat cadence — when the status is Rejected or Pending, that same field means “wait this many seconds before you retry the boot.” The station is now condemned to send this exact message every 30 minutes, forever, until something on the CSMS side changes.
Notice what didn’t fail: the message id a3f9-1 was echoed back in a CALLRESULT (message type 3). If the CSMS had hated the payload structure, you’d instead see a CALLERROR (message type 4) — a different class of bug covered in common OCPP errors explained (coming soon). A clean CALLRESULT carrying Rejected tells you the CSMS understood you perfectly and simply declined.
Working through the causes of Rejected
Once you’ve confirmed you’re looking at a genuine status: Rejected (and not a transport failure or a CALLERROR), the causes narrow to a short list.
The identity doesn’t match a record
This is the number-one cause on real networks. The CSMS keys registration off the station identity — most importantly the station id in the connection URL path (wss://csms.example.com/ocpp/STATION-ID), and often the vendorName, model, or serialNumber in the boot payload. If any of these don’t match what was pre-provisioned, you get Rejected.
The classic field scenario: a technician swaps a failed unit and installs a replacement whose station id was provisioned for a different site, or whose id has a typo (CP-0042 vs CP-042). This happens to a North American CPO running EVgo or ChargePoint hardware exactly as often as it happens to a European one running IONITY or Fastned sites — it’s a provisioning-database problem, not a hardware or geography problem.
How to confirm in Rey: send the boot once with the wrong id (you’ll see Rejected), then change only the station id to a known-good one and re-send. If it flips to Accepted, the identity was your culprit.
The station is pointed at the wrong CSMS
The unit is a perfectly valid station — it’s just registered in a different backend. It connects, boots, and gets Rejected because this CSMS has no record of it. Common after a migration between platforms, or when staging and production URLs get crossed. The fix is on the charger’s configuration side, not the CSMS.
The station is administratively disabled
Some CSMS platforms let an operator “block” or “decommission” a station without deleting it. A blocked station connects and boots but is deliberately answered with Rejected. If a station that worked yesterday suddenly rejects, check for an admin action before you chase anything technical.
It’s actually an auth failure one layer down
Here’s the trap. If your CSMS enforces OCPP security profiles (coming soon) — Basic auth on the WebSocket, or mutual TLS with client certificates — a bad credential is rejected during the WebSocket upgrade, before any BootNotification is sent. You’ll see the connection fail with a 401 or a TLS error, not a Rejected boot status. If Rey never reaches the “connected” state and never shows an outgoing boot frame, stop debugging the payload — your problem is the credential or the certificate. This is the distinction that saves hours: a security failure and a registration failure look the same on the dashboard and are nowhere near each other in the stack.
When it’s Pending, not Rejected
Pending is its own animal and people misread it constantly. It does not mean failure — it means “I know who you are, I’m just not ready for you yet.” Typical triggers:
- The station was just provisioned and the CSMS is still pushing its initial configuration.
- The CSMS is in a maintenance state for that station.
- An operator has to manually approve the newly-registered station and hasn’t.
The charger keeps retrying at the interval, and on some later retry the CSMS is expected to flip it to Accepted. You can watch this live: in Rey, boot against a CSMS that provisions on first contact and you may see Pending on the first frame and Accepted on the retry. The failure signature is a station that sits on Pending indefinitely — that’s a stuck provisioning pipeline or an approval nobody clicked, and it needs a human, not a longer retry interval.
Reproducing it against a local open-source CSMS
If you don’t have a backend to point at — or you want a controlled sandbox where you own both ends — stand up an open-source CSMS locally and drive it with Rey.
- SteVe is the long-running open-source OCPP server, maintained by the steve-community project and in use since 2013. It’s a Java server-side CSMS and implements OCPP 1.6J (plus older 1.2/1.5). Because it’s 1.6 only, drive it with Rey in 1.6 mode — it’s the canonical way to reproduce a 1.6 rejection: register a station, then boot with a different id and watch it reject.
- CitrineOS, authored by S44 and hosted as a Linux Foundation Energy project under the Apache 2.0 license, is a modular open-source CSMS that is OCPP 2.0.1 certified (and has since added 1.6). It’s the natural 2.0.1 counterpart to point Rey at: provision a station in Citrine, then use Rey to send both a matching and a mismatched
BootNotificationand compare theAcceptedandRejectedframes side by side.
On the station side, the firmware that real chargers actually run is worth knowing too: EVerest (an LF Energy stack originated by Pionix, whose libocpp covers 1.6, 2.0.1 and 2.1) and MicroOcpp (an MIT-licensed OCPP 1.6 / 2.0.1 client library for microcontrollers) are what produce these boot frames in the field. You don’t need them to debug — Rey stands in for the charger — but they’re the counterparts to the SteVe/CitrineOS server side.
The debugging checklist
When a station won’t come online, in order:
- Is the WebSocket open? If Rey (or your charger’s logs) never reaches “connected,” it’s transport — URL, DNS, TLS, or a
401/403at the upgrade. Not a boot problem. - Did a boot frame go out and a
CALLRESULTcome back? If you got aCALLERRORinstead, the payload is malformed — check the schema. - What is
status?Accepted→ you’re done.Pending→ wait and watch the retry; if it never flips, chase provisioning/approval.Rejected→ continue. - Does the station id in the URL path exactly match a provisioned record? Typos and swapped units live here.
- Is this station pointed at the right CSMS? Rule out a wrong-backend registration.
- Was it administratively disabled? Check for a recent operator action.
Run that top to bottom and you’ll classify almost any “charger is offline” ticket in a couple of minutes — most of them without leaving the browser. Reproduce the exact failure your fleet is hitting in Rey, confirm the fix flips it to Accepted, and only then send someone to site. The best BootNotification debugging is the kind that never needs a truck roll.
For what happens after a healthy boot, test an OCPP charge point without hardware (coming soon) walks the rest of the session, and common OCPP errors explained (coming soon) covers the CALLERROR failures that sit alongside registration.