Most OCPP problems are not in the happy path. They show up when a charger's firmware reads the spec differently from your CSMS, when a cellular link drops mid-transaction, or when a fleet reconnects all at once after a deployment. This final part of the series collects the error handling rules and the failure patterns that come up repeatedly when integrating 1.6-J chargers. It assumes familiarity with the framing from part 1.
Log raw frames first
Before anything else: log every frame in both directions, exactly as sent and received, with the charger identity, direction and a high-resolution timestamp. Parsed logs hide the bugs you are looking for, such as a number sent as a string, an extra field, or a missing subprotocol. Keep raw logs for long enough to cover billing disputes, and make them searchable by charger and by unique message ID.
CALLERROR codes and when to use them
| errorCode | Meaning in the 1.6-J spec | Typical cause |
|---|---|---|
NotImplemented | Requested action is not known by the receiver | Charger firmware lacks a profile, or the CSMS does not handle a vendor action |
NotSupported | Action is recognised but not supported | Feature disabled or partially implemented |
InternalError | Internal error; receiver could not process the action | Exception in your handler, database down |
ProtocolError | Payload for the action is incomplete | Missing required field |
SecurityError | Security issue prevented processing | Rare in practice |
FormationViolation | Payload is syntactically incorrect or does not conform to the PDU structure | Invalid JSON, wrong structure |
PropertyConstraintViolation | A field contains an invalid value | Enum value not in the spec, string too long |
OccurenceConstraintViolation | A field violates occurrence constraints | Required field missing, array where one is not allowed |
TypeConstraintViolation | A field violates data type constraints | "value": 60 instead of "value": "60" |
GenericError | Any other error | Catch-all |
Note the spellings: FormationViolation and OccurenceConstraintViolation are how the codes appear in OCPP 1.6 (2.0.1 changed them). Send exactly those spellings, and when parsing errors from chargers it costs nothing to accept the 2.0.1 spellings (FormatViolation, OccurrenceConstraintViolation) as well.
[
4,
"csms-cc-402",
"TypeConstraintViolation",
"value must be a string",
{"key": "value"}
]The errorDetails object is free-form, and often empty. When you are the receiver, put something helpful in errorDescription; the next engineer debugging this will thank you.
Strict or lenient validation?
A strict CSMS rejects anything that fails the JSON schema; a lenient one accepts what it can understand. In practice, be lenient on input from chargers where you can safely interpret the meaning (extra fields, timestamps without milliseconds, numbers as strings in places you can parse), strict where misinterpretation would cost money (meter values without a recognisable unit), and strict on everything you send. Above all, never answer a transaction message with a CALLERROR because of a business rule: as explained in part 5, the charger treats it as a delivery failure and retries.
Messages you cannot answer
If a frame is not valid JSON or has no readable unique ID, there is nothing to respond to. Log it and move on. If you receive a CALLRESULT or CALLERROR whose unique ID does not match an outstanding request (often a late answer after you timed out), log and discard it. An unknown MessageTypeId should also be logged and ignored.
Timeouts and retries
OCPP 1.6-J does not prescribe a response timeout. Choose one per direction, keep it configurable, and remember that cellular round-trips plus charger processing can take several seconds. Rules that save trouble:
- Retry idempotent queries (
GetConfiguration,TriggerMessage,GetCompositeSchedule) at most a few times. - Do not automatically retry commands with side effects (
Reset,UnlockConnector,RemoteStartTransaction). Report the timeout and let a person or a higher-level workflow decide. - When a charger retries a CALL to you, it may reuse the unique ID or generate a new one depending on firmware, so deduplicate by content (charger, action and the payload's natural keys), not by message ID.
- Keep your own handlers fast. A CSMS that takes 30 seconds to answer
StartTransactionbecause of a slow billing call creates retries and duplicates. Acknowledge quickly and do slow work asynchronously.
Message ordering
Within one direction, frames arrive in order on a single WebSocket. Across directions and connections, ordering is not guaranteed, and several orderings surprise people:
StatusNotificationChargingbeforeStartTransaction, orFinishingbeforeStopTransaction.- Queued transaction messages from hours ago arriving immediately after
BootNotification, interleaved with fresh status messages. - A
StopTransactionarriving on a new connection while your service is still processing theStartTransactionfrom the previous one.
Process messages for each charger sequentially (a per-charger queue or actor works well), order business state by the charger's timestamps, and reconcile periodically. Status and transaction state machines should be separate, as noted in part 3.
Connection problems
Duplicate connections
A charger can open a new WebSocket before your server notices the old one is dead. The usual rule is that the newest connection for an identity wins: close the old one, and route outgoing commands to the new one. Behind a load balancer this requires a shared registry of which node holds which charger, covered in CSMS architecture at scale.
Reconnect storms
When a CSMS node restarts, every charger on it reconnects at once, and each sends BootNotification, status for every connector, and queued transactions. OCPP 1.6 does not standardise reconnect back-off (2.0.1 does), so many chargers retry quickly and in lockstep. Protect yourself with connection rate limits, fast and cheap boot handling, and rolling deployments that drain connections gradually. A long interval in Pending or Rejected responses can also be used to spread load.
Connects, then drops
A charger that connects and disconnects in a loop without sending any OCPP message is almost always a handshake problem: missing Sec-WebSocket-Protocol echo, a TLS issue (unsupported cipher, missing intermediate certificate, expired root on the charger), or a proxy that does not support WebSocket upgrades or times out idle connections. Check the proxy's idle timeout against WebSocketPingInterval and HeartbeatInterval.
Common charger interop bugs
| Symptom | Likely cause | Mitigation |
|---|---|---|
| Energy off by 1000x | kWh values sent without unit | Per-model unit override; sanity checks against meterStart |
| Duplicate sessions | StartTransaction resent after a timeout | Idempotent start handling |
| Sessions never close | StopTransaction lost or rejected with CALLERROR | Always respond; reconcile stale sessions |
| Timestamps in 1970 or 2000 | Clock not set before first transaction | Flag and correct using receive time; check time sync |
| Remote start never starts | Driver did not plug in before ConnectionTimeOut, or model requires cable first | App guidance; pending-start timeout |
| Smart charging ignored | Wrong chargingRateUnit, profile lost on reboot, TxProfile without active transaction | GetCompositeSchedule; re-apply after boot |
| Configuration change has no effect | RebootRequired not handled, or vendor accepts but ignores | Read back and verify; per-model notes |
| Same card, different idTag | Byte order or formatting differs by model | Normalise per model |
| Connector shows available but charger is dead | Connector 0 Faulted ignored | Combine connector 0 and connector status |
transactionId errors on some chargers | IDs beyond 32-bit range | Keep IDs within signed 32-bit integers |
A troubleshooting checklist
- Is the charger connected right now, and when did it last send anything?
- What did the last
BootNotificationsay (firmware, status), and was itAccepted? - What are the current statuses of connector 0 and the affected connector?
- Read the raw frames around the reported time. Look for CALLERRORs and timeouts in both directions.
- Compare the configuration with the expected baseline for that model.
- Use
TriggerMessageto get fresh status and meter values, andGetCompositeSchedulefor power complaints. - Only then:
Reset(soft first), diagnostics upload, site visit.
Much of this can be found before a charger reaches the field. Running each new model and firmware version through a scripted set of scenarios against your CSMS (see testing OCPP with a charge point simulator) and keeping per-model notes saves far more time than debugging live sites. OCPP certification helps too, but certified hardware still needs testing against your own backend.
Key takeaways
- Log raw frames in both directions; it is the single most useful debugging tool.
- Use CALLERROR for protocol failures only, and never for transaction messages you simply dislike.
- Do not retry side-effecting commands automatically; deduplicate inbound retries by content.
- Process each charger's messages in order, and expect reconnect storms.
That completes the OCPP 1.6-J series. To go further, continue with the OCPP 2.0.1 tutorial or see migrating from OCPP 1.6 to 2.0.1. HashTech provides hardware onboarding and conformance testing as part of its CSMS work; you can reach the team through contact.

