PHP2Shell alert: critical PHP flaws CVE-2026-109618 / 109619 — check your servers and get patched fast
HashTech Systems
EV Charging
Part 12 of 12

Troubleshooting OCPP 1.6: CALLERRORs, Timeouts and Interop Bugs

A field guide to debugging OCPP 1.6-J: CALLERROR codes, timeouts and retries, message ordering, reconnect storms and common charger interop bugs.

HashTech Engineering · October 26, 2026 · 6 min read

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

errorCodeMeaning in the 1.6-J specTypical cause
NotImplementedRequested action is not known by the receiverCharger firmware lacks a profile, or the CSMS does not handle a vendor action
NotSupportedAction is recognised but not supportedFeature disabled or partially implemented
InternalErrorInternal error; receiver could not process the actionException in your handler, database down
ProtocolErrorPayload for the action is incompleteMissing required field
SecurityErrorSecurity issue prevented processingRare in practice
FormationViolationPayload is syntactically incorrect or does not conform to the PDU structureInvalid JSON, wrong structure
PropertyConstraintViolationA field contains an invalid valueEnum value not in the spec, string too long
OccurenceConstraintViolationA field violates occurrence constraintsRequired field missing, array where one is not allowed
TypeConstraintViolationA field violates data type constraints"value": 60 instead of "value": "60"
GenericErrorAny other errorCatch-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.

json
[
  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 StartTransaction because 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:

  • StatusNotification Charging before StartTransaction, or Finishing before StopTransaction.
  • Queued transaction messages from hours ago arriving immediately after BootNotification, interleaved with fresh status messages.
  • A StopTransaction arriving on a new connection while your service is still processing the StartTransaction from 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

SymptomLikely causeMitigation
Energy off by 1000xkWh values sent without unitPer-model unit override; sanity checks against meterStart
Duplicate sessionsStartTransaction resent after a timeoutIdempotent start handling
Sessions never closeStopTransaction lost or rejected with CALLERRORAlways respond; reconcile stale sessions
Timestamps in 1970 or 2000Clock not set before first transactionFlag and correct using receive time; check time sync
Remote start never startsDriver did not plug in before ConnectionTimeOut, or model requires cable firstApp guidance; pending-start timeout
Smart charging ignoredWrong chargingRateUnit, profile lost on reboot, TxProfile without active transactionGetCompositeSchedule; re-apply after boot
Configuration change has no effectRebootRequired not handled, or vendor accepts but ignoresRead back and verify; per-model notes
Same card, different idTagByte order or formatting differs by modelNormalise per model
Connector shows available but charger is deadConnector 0 Faulted ignoredCombine connector 0 and connector status
transactionId errors on some chargersIDs beyond 32-bit rangeKeep IDs within signed 32-bit integers

A troubleshooting checklist

  1. Is the charger connected right now, and when did it last send anything?
  2. What did the last BootNotification say (firmware, status), and was it Accepted?
  3. What are the current statuses of connector 0 and the affected connector?
  4. Read the raw frames around the reported time. Look for CALLERRORs and timeouts in both directions.
  5. Compare the configuration with the expected baseline for that model.
  6. Use TriggerMessage to get fresh status and meter values, and GetCompositeSchedule for power complaints.
  7. 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.

Let's Talk

Ready to build something that lasts?

Whether it's a charging network, an AI rollout, or a core enterprise system — let's talk about what you're building.