What it is
The charge point sends an empty request; the CSMS answers with the current time.
// Charge point -> CSMS
[2, "19223201", "Heartbeat", {}]
// CSMS -> charge point
[3, "19223201", { "currentTime": "2026-08-11T09:00:00.000Z" }]That is the entire message. Its value is in the two things it produces as side effects: the CSMS learns the charger is still there, and the charger learns what time it is.
Where the interval comes from
The interval is set in two places and the precedence matters:
| Source | When it applies |
|---|---|
interval in the BootNotification response | Set at boot; the charger adopts it immediately |
The HeartbeatInterval configuration key | Changed at runtime via ChangeConfiguration |
A charger that has just booted uses whatever the BootNotification response gave it. That makes BootNotification the right place to apply fleet policy — a charger you are troubleshooting can be given 60 seconds while the rest of the estate stays on 900.
The interval is a cost decision
Most chargers are on cellular SIMs with metered data. Heartbeat is small, but it is constant, and it multiplies by fleet size.
| Interval | Messages per charger per day | Detection latency |
|---|---|---|
| 60 s | 1,440 | About a minute |
| 300 s | 288 | About five minutes |
| 900 s | 96 | About fifteen minutes |
| 3600 s | 24 | Up to an hour |
Across 2,000 chargers, moving from 60 s to 900 s removes roughly 2.7 million messages a day. That is real money on metered connectivity and real load on the CSMS.
Heartbeat is not WebSocket ping
Both look like liveness checks and they are not interchangeable.
| WebSocket ping/pong | OCPP Heartbeat | |
|---|---|---|
| Layer | Transport | Application |
| Proves | The socket is open | The OCPP stack is running and responsive |
| Handled by | The WebSocket library | The charger's OCPP firmware |
| Typical period | Seconds | Minutes |
A charger can hold an open socket while its OCPP application has hung. Ping keeps answering because the library answers it; Heartbeat stops because the application is dead. That gap is exactly the failure mode you care about — a charger that looks connected and will not start a transaction.
Run both. Ping detects a dropped socket in seconds; Heartbeat detects a wedged application in one interval.
The clock role
currentTime is the charger's authoritative clock source. Most charge points have no reliable RTC and no NTP client, so this response is how they learn the time.
Everything downstream depends on it:
- Transaction timestamps on StartTransaction and StopTransaction.
- Meter sample timestamps on MeterValues, which drive time-of-use billing.
- Charging profile windows in smart charging, where an off-peak schedule applied at the wrong hour is worse than no schedule.
- Offline transaction records, which are timestamped locally and replayed later.
A charger whose clock has drifted produces plausible-looking data that reconciles to the wrong tariff period. This is a genuinely nasty class of bug because nothing errors — the numbers are just wrong. Serve currentTime from a synchronised source and never from an application server's local clock.
Detecting a dead charger
Missing one heartbeat is not an outage. Cellular links drop packets. A practical policy:
- Mark suspect after 2–3 missed intervals, not one.
- Do not alert on suspect — alert on sustained absence, or you will train the team to ignore the alert.
- Reconcile against StatusNotification. A charger that stopped heartbeating but is still reporting status is not offline; something is wrong with your heartbeat handling instead.
- Propagate the state outward. A charger you consider offline should surface as
UNKNOWNin OCPI Locations, not as available. Showing an unreachable charger as free is how drivers get stranded.
Production lessons
- Tier the interval by outage cost. Uniform intervals either waste data or detect too slowly.
- Serve
currentTimefrom a synchronised clock. Every downstream timestamp inherits this value. - Run WebSocket ping alongside Heartbeat. They catch different failures.
- Never alert on a single miss. Use a multi-interval threshold.
- Track clock skew per charger. Comparing the charger's reported timestamps against your own receipt time surfaces drifting units before they corrupt billing.
- Use BootNotification to apply per-charger intervals rather than pushing configuration changes across a fleet.
Frequently asked questions
The charge point sends an empty request and the CSMS replies with currentTime. It proves the charger's OCPP application is alive and gives the charger an authoritative clock, which most charge points lack because they have no reliable RTC or NTP client.
No. WebSocket ping is handled by the transport library and only proves the socket is open. Heartbeat is handled by the charger's OCPP application, so it proves the application itself is responsive. A charger can answer pings while its OCPP stack has hung, so run both.
It depends on what an undetected outage costs. A highway DC fast charger justifies a tight interval; a residential AC unit does not. Across 2,000 chargers, moving from 60 to 900 seconds removes roughly 2.7 million messages a day, which is significant on metered cellular data.
Because every transaction timestamp, meter sample timestamp and charging profile window derives from it. A charger with a drifted clock produces plausible data that reconciles to the wrong tariff period, and nothing errors — the numbers are simply wrong.