Heartbeat is the simplest OCPP message and one of the most operationally important. The charger sends a heartbeat at a regular interval; the CSMS responds with the current time. That’s it. No payload to speak of, no complex semantics.
But the absence or presence of heartbeats determines whether a charger is considered online or offline in your operations dashboard. Misconfigured heartbeats are a frequent cause of “charger is offline but seems fine” support tickets, and the connection-recovery behavior around heartbeats is where production OCPP implementations earn their reputation.
This article covers what Heartbeat does, how the connection lifecycle works, and the patterns that make real-world OCPP deployments survive the inevitable network problems. If you are new to the protocol, what is OCPP sets the context for how a charger and CSMS talk to each other.
What Heartbeat actually carries
The Heartbeat request (OCPP 1.6 and 2.0.1):
{}
That’s it. No fields. Just “I’m alive.”
The response:
{ "currentTime": "2026-06-26T08:05:00.000Z" }
The CSMS reports its current time in UTC. The charger uses this to keep its internal clock synchronized.
In OCPP 2.0.1 the message is named slightly differently in some contexts but functions the same way. The semantics carry across versions. For how the two generations differ more broadly, see the OCPP version comparison (coming soon).
Why time sync via Heartbeat matters
Every other OCPP message has timestamps — MeterValues, StatusNotification, TransactionEvent, etc. If the charger’s clock drifts, those timestamps drift. A session that started at “2026-06-26T19:00:00” according to the charger might really have started at 19:13 wall-clock time. This is a problem for:
- Billing. Tariffs with time-of-day pricing depend on accurate session timestamps.
- Roaming reconciliation. Your CDR’s session_start has to match the partner’s expectation.
- Operational analysis. “How long did this charger take to start a session?” requires accurate timestamps.
- Regulatory compliance. Some jurisdictions require timestamps on signed CDRs to be accurate within seconds.
The Heartbeat-driven clock sync gives the charger a periodic correction. Drift between heartbeats is usually small (milliseconds to single-digit seconds depending on the local clock’s quality). Without Heartbeat or another time-sync mechanism (NTP), drift can grow unbounded.
Some modern chargers also use NTP. Heartbeat-based sync is a backup and a CSMS-controlled override.
The connection lifecycle
A typical lifecycle:
- Charger powers on. Establishes WebSocket connection to CSMS URL.
- Sends BootNotification. CSMS responds with Accepted (or Pending, or Rejected). See the BootNotification message (coming soon) for how that handshake sets the heartbeat interval.
- If Accepted, charger begins normal operation including Heartbeats at the specified interval.
- Heartbeats flow. Every N seconds, charger sends Heartbeat, CSMS responds with currentTime.
- Other messages interleaved. StatusNotification when connector state changes. TransactionEvent during sessions. Responses to CSMS commands.
- At some point, connection drops. Network glitch, CSMS restart, charger reboot, cellular blip, whatever.
- Reconnection logic kicks in. Charger detects the disconnect (usually via failed message send), tries to reconnect.
- New WebSocket establishes. BootNotification fires again. Cycle repeats.
stateDiagram-v2
[*] --> Connecting
Connecting --> Booting: WebSocket open
Booting --> Online: BootNotification Accepted
Online --> Online: Heartbeat every N sec
Online --> Disconnected: Connection drops
Disconnected --> Connecting: Backoff retry
The “connection drops” step is more common than first-timers expect. Production OCPP deployments see thousands of disconnects per day across a fleet of chargers. The protocol is designed around this; the goal is fast detection and graceful recovery, not preventing disconnects in the first place.
What “online” actually means
The CSMS judges a charger to be online based on multiple signals:
- WebSocket connection state. Is the underlying TCP/WS still connected?
- Heartbeat freshness. Has the charger sent a Heartbeat within the expected window?
- Recent message activity. Even without Heartbeat, recent meaningful messages (StatusNotification, TransactionEvent) indicate liveness.
Different CSMSes weight these differently. A robust definition: “received any OCPP message within the heartbeat interval × 2.” Stricter: “received a Heartbeat in the last interval.” Looser: “WebSocket is still connected even if no recent messages.”
The right answer depends on operational priorities. Tighter detection means faster awareness of dead chargers but more flapping (chargers oscillating between online and offline due to short network glitches). Looser detection means smoother operation but slow detection of truly dead chargers.
A common pattern: mark offline after 2-3 missed heartbeats, mark online again as soon as ANY OCPP message arrives. Show pending-flap warnings if a charger has been offline-online-offline multiple times in an hour.
The disconnect detection problem
Here’s something that surprises first-time OCPP implementers: when a charger’s WebSocket dies, the charger often doesn’t know.
A WebSocket can die “silently” — the TCP connection is in a half-open state where one side thinks it’s connected but messages don’t actually go through. From the OS perspective the socket is open. From the application perspective everything looks fine. Until the next write attempt, when it fails — sometimes hundreds of seconds later.
The CSMS sees this clearly (no messages arriving) but the charger doesn’t realize until it tries to send something. This is why Heartbeat exists at the application level instead of relying purely on TCP — the regular outbound Heartbeat from the charger forces a write that exposes the dead connection.
For this reason, many implementations also use WebSocket-level pings (the WebSocket protocol’s own keepalive) on top of OCPP Heartbeats. The pings catch some dead connections faster than the next Heartbeat would.
Choosing a Heartbeat interval
OCPP doesn’t mandate a specific interval. The CSMS chooses and tells the charger in the BootNotification response. Considerations:
Shorter intervals (under 60s):
- Faster disconnect detection.
- More bandwidth and CSMS load.
- Tighter clock sync.
- Worth it if every minute of downtime matters.
Medium intervals (60-300s):
- The sweet spot for most production deployments.
- Reasonable detection lag (1-5 minutes worst case).
- Manageable load.
Long intervals (300-1800s):
- Lower load and bandwidth.
- Slower detection — a charger could be offline for half an hour before you notice.
- OK for fleets with high reliability and where one or two missed sessions don’t matter.
Very long (over 1800s):
- Useful for Pending or Rejected registration states (the charger isn’t actively serving anyway, no need for fast detection).
- Not appropriate for normal operation.
A common pattern: Accepted chargers heartbeat every 300 seconds (5 min). Pending chargers every 600-900 seconds. Rejected chargers every 1800+ seconds (registration retries).
Reconnection patterns
When the WebSocket dies, the charger should reconnect. The patterns that work in production:
Exponential backoff. First retry after 5 seconds. Then 10s. Then 30s. Then 60s. Up to a cap (say 300s). This avoids hammering the CSMS during outages where many chargers are reconnecting simultaneously.
Jitter. Add random jitter (±20%) to backoff intervals. Without jitter, all chargers reconnect at exactly the same moment after an outage, creating a thundering herd against the CSMS.
Persistent retry. Never give up. A charger that hits the max backoff continues to retry indefinitely. The WebSocket WILL come back eventually (when the network is restored, when the CSMS is back online, when whatever was wrong is fixed).
Bootnotify on reconnect. Every new WebSocket starts with BootNotification, even if the previous WebSocket worked fine 30 seconds ago. The CSMS uses BootNotification to confirm the charger’s identity, possibly re-provision, and re-establish the heartbeat interval.
Resume in-flight transactions. If a session was in progress when the connection died, the charger should report its current state (via post-boot TransactionEvent or StatusNotification) so the CSMS knows what happened. Some chargers buffer messages locally during disconnect and replay them on reconnect.
Common failure modes around Heartbeat
A list of things that go wrong.
Charger never sends Heartbeat after Accepted. Firmware bug, or the charger interpreted the BootNotification response unusually. Symptom: charger looks online for a few minutes, then offline.
Heartbeats arrive irregularly. Network jitter, CPU contention on the charger, firmware bug. Symptom: occasional false-offline alerts.
Charger heartbeats but no other messages. Indicates the StatusNotification or other messages are failing. Symptom: charger looks online but never accepts sessions.
Clock drift between heartbeats. Charger’s local clock is unreliable. Symptom: timestamps on session events don’t match expected times.
Heartbeat interval drift on the CSMS side. CSMS time changes (NTP correction, server reboot). Charger gets a different currentTime than expected. Symptom: charger sessions show timestamps with a sudden jump.
Heartbeats over a flaky cellular connection. Cellular blips, hand-offs between cell towers, signal degradation. Symptom: charger flaps between online and offline frequently. Operational fix: better cellular planning (external antenna, signal booster, different carrier).
Operational best practices
For CPOs operating OCPP deployments:
- Choose a sensible interval. 300 seconds is a fine default for most fleets.
- Monitor heartbeat freshness, not just the WebSocket. Both signals matter.
- Track per-charger flap rate. Frequent flaps indicate a connectivity problem worth investigating.
- Alert on prolonged offline. A charger that has missed 5+ heartbeats deserves investigation, especially if it was previously stable.
- Time-sync your CSMS rigorously. NTP, monitor drift, alert if drift exceeds 1 second.
- Test cellular connectivity. If chargers are cellular, the carrier signal at each site matters as much as the firmware.
- Plan for outage recovery. When the CSMS comes back from a maintenance window, expect thousands of BootNotifications in seconds. Capacity-plan for it.
What this means for CSMS designers
If you’re building a CSMS, a few principles:
- Don’t make Heartbeat handling expensive. It’s the highest-volume incoming message. Process it lightly. Don’t write to your main database on every Heartbeat unless you need to.
- Aggregate Heartbeat for analytics. Per-minute or per-five-minute summaries rather than per-event records.
- Idempotent BootNotification. A charger may reconnect frequently. Don’t treat each BootNotification as a fresh provisioning event; treat it as “is this charger known? Resume.”
- Per-charger configurable interval. Some chargers benefit from shorter intervals, some from longer. Give operators control.
The honest summary
Heartbeat is the boring part of OCPP that does crucial work. It keeps connections detectable, keeps clocks synchronized, and gives you a steady signal that “this charger is alive and reachable.” Get the interval right for your operational needs, design clean reconnection logic, and monitor heartbeat freshness as carefully as you monitor sessions. Most “the charger is offline” support tickets trace back to the connection-and-Heartbeat layer; getting it right pays back in less operational pain.