OCPP 2.0.1 is the version of the Open Charge Point Protocol that new charging station firmware and new CSMS platforms are being built against. It is not an incremental update to 1.6: the transaction model, configuration, error reporting and security were all redesigned. This series walks through it the way you would implement it, with real OCPP-J frames and the mistakes we see when stations and backends meet for the first time.
If you already know OCPP 1.6, some of this will feel familiar (WebSocket, JSON arrays, BootNotification, Heartbeat). Much of it will not. If you want a higher-level comparison first, read OCPP 1.6 vs 2.0. For the 1.6 equivalent of this article, see the OCPP 1.6 introduction.
A note on versions: 2.0 vs 2.0.1
OCPP 2.0 was published in 2018 and quickly found to have issues; OCPP 2.0.1 followed in 2020 and replaced it. Nobody should implement plain 2.0. The Open Charge Alliance has since published further editions of 2.0.1 that fold in errata and clarifications, so when you implement, work from the latest 2.0.1 edition and its errata sheet rather than an old PDF someone emailed you. OCPP 2.1 (January 2025) extends 2.0.1 and is covered in part 11.
The specification is split into parts. In practice you will live in Part 2 (the specification itself: use cases, requirements, messages, data types and the device model appendix), Part 3 (the JSON schemas) and Part 4 (the OCPP-J implementation guide for JSON over WebSockets). Part 0 and Part 1 are introduction and architecture and are worth one careful read.
What actually changed from 1.6
- Transactions:
StartTransaction,StopTransactionand transaction-relatedMeterValuesare replaced by a singleTransactionEventmessage witheventTypeStarted/Updated/Ended. The station generates thetransactionId, not the CSMS. - Configuration: flat 1.6 configuration keys are replaced by a structured device model of components, variables and attributes, accessed with
GetVariables/SetVariablesand reported withGetBaseReport/NotifyReport. - Status and errors:
StatusNotificationonly carries one of five connector states. Fault details move toNotifyEvent, driven by monitors you can configure remotely. - Security: the security profiles, certificate management and security events that were a whitepaper add-on for 1.6 are part of the core spec.
- ISO 15118: Plug & Charge, contract certificate handling and EV charging needs are built in.
- Customer-facing features: display messages, tariff and running-cost information, and richer authorization (
IdTokenTypewith a type, group tokens, more status values). - Topology: an explicit three-level model of Charging Station, EVSE and Connector.
Transport: OCPP-J over WebSocket
The station opens a WebSocket to the CSMS. The URL is the CSMS endpoint with the station's identity appended as the last path segment, and the station offers the ocpp2.0.1 subprotocol. A CSMS that supports several versions picks one and echoes it in the response; if it returns no subprotocol, the station should close the connection.
GET /ocpp/CS-DEPOT-0042 HTTP/1.1
Host: csms.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Version: 13
Sec-WebSocket-Protocol: ocpp2.0.1, ocpp1.6
Authorization: Basic Q1MtREVQT1QtMDA0MjpzM2NyZXQtcGFzc3dvcmQtZm9yLWRlbW8=
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: ocpp2.0.1Every message is a JSON array. The first element is the message type, the second a unique message ID chosen by the sender, then either the action and payload (CALL), the payload (CALLRESULT), or error details (CALLERROR).
| MessageTypeId | Name | Shape |
|---|---|---|
| 2 | CALL | [2, messageId, action, payload] |
| 3 | CALLRESULT | [3, messageId, payload] |
| 4 | CALLERROR | [4, messageId, errorCode, errorDescription, errorDetails] |
[2, "b7c1e2a0-4d5f-4c1e-9a77-1f0b2f6c9d10", "Heartbeat", {}]
[3, "b7c1e2a0-4d5f-4c1e-9a77-1f0b2f6c9d10", {
"currentTime": "2026-10-26T09:15:02.000Z"
}]
[4, "b7c1e2a0-4d5f-4c1e-9a77-1f0b2f6c9d10", "FormatViolation", "Payload is not valid JSON", {}]The 2.0.1 error codes are FormatViolation, GenericError, InternalError, MessageTypeNotSupported, NotImplemented, NotSupported, OccurrenceConstraintViolation, PropertyConstraintViolation, ProtocolError, RpcFrameworkError, SecurityError and TypeConstraintViolation. Note the spelling: 1.6 had the typo FormationViolation; 2.0.1 fixed it to FormatViolation, and code shared between versions often gets this wrong.
Charging Station, EVSE, Connector
2.0.1 models hardware in three levels. The Charging Station is the physical unit with one OCPP connection. An EVSE is an independently operated and managed part of the station that can deliver energy to one EV at a time. A Connector is a physical socket or cable on an EVSE. An EVSE may have several connectors (for example a CCS cable and a CHAdeMO cable), but only one can be in use at a time.
- EVSE ids are numbered from 1 upwards, sequentially, per station.
- Connector ids are numbered from 1 upwards within each EVSE. Connector 1 on EVSE 2 is
{ "id": 2, "connectorId": 1 }. evseId0 is used to address the whole station in messages that take an EVSE id (for example a station-wide charging profile).
{
"evse": {
"id": 2,
"connectorId": 1
}
}This is the single biggest modelling change for a CSMS coming from 1.6, where a flat connectorId with 0 meaning "the whole station" did everything. Your database needs an EVSE table between station and connector, and it is the EVSE (not the connector) that holds a transaction, a charging profile or a reservation. EVSE ids are also what you map to OCPI EVSE objects, covered in OCPP to OCPI mapping.
Functional blocks and use cases
1.6 grouped messages into feature profiles. 2.0.1 instead organises the specification into lettered functional blocks, each containing numbered use cases. A use case such as E02 (Start Transaction, Cable Plugin First) has a sequence diagram and numbered requirements like E02.FR.01. When a vendor or test report cites a requirement, this is the ID they mean, and it is the fastest way to settle an interop argument.
| Block | Name | Typical messages |
|---|---|---|
| A | Security | SignCertificate, CertificateSigned, SecurityEventNotification |
| B | Provisioning | BootNotification, GetVariables, SetVariables, GetBaseReport, Reset |
| C | Authorization | Authorize, ClearCache |
| D | Local Authorization List Management | SendLocalList, GetLocalListVersion |
| E | Transactions | TransactionEvent, GetTransactionStatus |
| F | Remote Control | RequestStartTransaction, RequestStopTransaction, UnlockConnector, TriggerMessage |
| G | Availability | StatusNotification, ChangeAvailability, Heartbeat |
| H | Reservation | ReserveNow, CancelReservation, ReservationStatusUpdate |
| I | Tariff and Cost | CostUpdated, cost fields in TransactionEvent |
| J | Meter Values | MeterValues, meter values inside TransactionEvent |
| K | Smart Charging | SetChargingProfile, GetCompositeSchedule, NotifyEVChargingNeeds |
| L | Firmware Management | UpdateFirmware, FirmwareStatusNotification |
| M | ISO 15118 Certificate Management | Get15118EVCertificate, GetCertificateStatus, InstallCertificate |
| N | Diagnostics | GetLog, SetVariableMonitoring, NotifyEvent, CustomerInformation |
| O | Display Message | SetDisplayMessage, GetDisplayMessages, ClearDisplayMessage |
| P | Data Transfer | DataTransfer |
A station does not have to implement every block. Certification is done against defined profiles (a core set plus optional ones such as smart charging, ISO 15118 support or advanced security), so check which profiles a station was certified for rather than assuming. See OCPP certification explained.
Schemas are strict, and customData is your escape hatch
The 2.0.1 JSON schemas set additionalProperties to false. A station that adds a vendor field to BootNotificationRequest will, against a strictly validating CSMS, get a CALLERROR back. Every 2.0.1 data type instead allows an optional customData object with a mandatory vendorId, and that is where vendor extensions belong:
{
"reason": "PowerUp",
"chargingStation": {
"model": "DC-150",
"vendorName": "ExampleVendor"
},
"customData": {
"vendorId": "com.examplevendor",
"hardwareRevision": "C2"
}
}How this series is organised
- Introduction (this article)
- The device model: components, variables, reports and monitoring
- Boot and provisioning: BootNotification, network profiles, Heartbeat, StatusNotification
- TransactionEvent: the new transaction model
- Authorization: IdTokens, cache, local list, remote start
- Smart charging: profiles, external limits, EV charging needs
- Security: profiles, certificates, events, signed firmware
- ISO 15118 Plug & Charge
- Display messages and costs
- Migrating from 1.6 to 2.0.1
- What's new in OCPP 2.1
Key takeaways
- Use subprotocol
ocpp2.0.1, the station identity as the last URL segment, and the latest 2.0.1 edition plus errata. - Model Charging Station, EVSE and Connector explicitly; EVSE is where transactions live.
- Learn the functional block letters and use case IDs; they are the shared vocabulary for interop debugging.
- Expect strict schema validation and put vendor extensions in
customData.
HashTech builds and operates CSMS platforms that run OCPP 1.6-J and 2.0.1 side by side; more on that on our EV charging page.

