The first time you look at OCPP logs, you see lines that start with square brackets and contain numbers and random-looking IDs. These lines are the message envelope of OCPP-J (OCPP over JSON). Once you understand the structure, you can read what is happening between the charger and the management software directly from the logs.

Three message types

In OCPP-J every message is a JSON array, and the first element is a number that identifies the message type (MessageTypeId):

  • 2 = CALL: A request sent from one side to the other.
  • 3 = CALLRESULT: A successful reply to the request.
  • 4 = CALLERROR: An error reply saying the request could not be processed.

This structure is the same in OCPP 1.6J, 2.0.1 and 2.1. What changes are the fields inside the messages. (OCPP 2.1 also defines a few new message types, but the basic trio remains.)

CALL: the request message

A CALL has four elements:

[2, "19223201", "BootNotification", {"chargePointVendor": "ExampleBrand", "chargePointModel": "AC22"}]

  1. 2 – message type (CALL).
  2. "19223201" – the MessageId, a unique ID generated by the sender.
  3. "BootNotification" – the name of the action being called.
  4. {...} – the action payload. Its fields are defined by the OCPP schema.

CALLRESULT: the successful reply

[3, "19223201", {"status": "Accepted", "currentTime": "2026-10-02T09:00:00Z", "interval": 300}]

  1. 3 – message type (CALLRESULT).
  2. The same MessageId – shows which request the reply belongs to.
  3. The reply payload.

A CALLRESULT has no action name; which request it answers is determined from the MessageId. That is why the MessageId must be unique for every open request.

CALLERROR: the error reply

[4, "19223201", "FormationViolation", "Payload is not valid", {}]

  1. 4 – message type (CALLERROR).
  2. The same MessageId.
  3. Error code – standard values such as NotImplemented, FormationViolation or InternalError.
  4. Error description – a short human-readable text.
  5. Error details – an object with extra information (may be empty).

We explain each error code in our article on OCPP CALLERROR error types.

Important rules

  • One request at a time: In OCPP-J a side should not send a new CALL over the same channel before receiving the reply to its previous request. This keeps order, but a slow-responding system can cause the queue to grow.
  • Timeout: If the reply does not arrive within a certain time, the request is considered failed. The length of the timeout depends on the implementation.
  • Consistent MessageId: IDs can be up to 36 characters long. UUIDs are common.
  • Business replies are not errors: When a card is rejected, for example, the CSMS does not send a CALLERROR; it returns "status": "Invalid" inside a CALLRESULT. CALLERROR is for cases where the message could not be processed technically.

Reading logs in practice

When investigating an issue, first find the CALL line, then look for the reply with the same MessageId. If the reply never arrives, it is a connection or timeout problem; if a CALLERROR arrives, it is a message format or unsupported action problem; if a CALLRESULT arrives but the status is "Rejected", it is a business rule problem. Üreticy's OCPP software stores all of this communication with the charger in searchable logs and shows it matched by MessageId.

When writing your own integration

If you are building a charger simulator or a test tool, validate every incoming message structurally first: the array length, a message type of 2, 3 or 4, and a MessageId that is a string. Then check the payload against the relevant OCPP JSON schema. Returning a CALLERROR with FormationViolation or TypeConstraintViolation for a payload that does not match the schema clearly shows the other side where the problem is. Use NotImplemented or NotSupported for an action name you do not recognise. This discipline lets you pinpoint issues within minutes when working with chargers from different brands.

Conclusion

The OCPP-J message envelope is simple: 2 for a request, 3 for a successful reply, 4 for an error. All communication is built on these three types and on matching MessageIds. Knowing this structure saves you a lot of time during integration and when resolving faults in the field.