# Understanding Realtime heartbeats

> What the heartbeat messages in a Realtime WebSocket are, how often they're sent, what happens when one is missed, and how to watch connection health from your code.

Source: https://supahost.app/kb/realtime-heartbeats · Updated 2026-11-11

A heartbeat is a tiny message the Realtime client sends over its WebSocket every 25 seconds to say "I'm still here." The server replies. If either side stops hearing from the other, the connection is treated as dead: the server closes it, and the client reconnects and rejoins its channels.

## What a heartbeat looks like

Open DevTools → Network → WS, select the `realtime/v1/websocket` connection and watch the Messages panel. Every 25 seconds you'll see a pair like this:

```json
{"topic":"phoenix","event":"heartbeat","payload":{},"ref":"14"}
{"topic":"phoenix","event":"phx_reply","payload":{"status":"ok","response":{}},"ref":"14"}
```

Depending on the client version, the same fields may appear as a compact JSON array instead, such as `[null,"14","phoenix","heartbeat",{}]`. The `phoenix` topic is the connection itself, not one of your channels. The `ref` ties the reply to the request.

## Why heartbeats exist

- **To detect dead connections.** A Wi-Fi switch or a sleeping laptop can leave a socket that looks open but carries nothing. Without heartbeats, neither side would know.
- **To keep proxies from closing idle sockets.** Load balancers, corporate proxies and mobile networks often drop connections that have been silent for a minute or two. Regular traffic keeps them open.
- **To refresh the access token.** If you give the client an `accessToken` callback, it's called on connect and on every heartbeat, so the server always has a current token for private channels.

## What happens when one is missed

If the reply to a heartbeat hasn't arrived by the time the next one is due, the client closes the socket and reconnects with backoff. Its channels rejoin on their own. Messages sent while the connection was down aren't replayed, so refetch your data after a reconnect.

The most common reason for missed heartbeats is a throttled background tab. See [Silent disconnects when the app is in the background](/kb/realtime-background-disconnects).

## Watching connection health

You can observe each heartbeat from your code with `heartbeatCallback`:

```js
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY, {
  realtime: {
    heartbeatIntervalMs: 25000, // the default
    heartbeatCallback: (status) => {
      // 'sent', 'ok', 'error', 'timeout' or 'disconnected'
      if (status === 'timeout' || status === 'disconnected') {
        showBanner('Reconnecting…')
      }
      if (status === 'ok') hideBanner()
    },
  },
})
```

This is a cheap way to show users a "reconnecting" indicator in a team chat or a live board.

**Don't set the interval much higher.** A longer interval saves almost nothing and makes proxies more likely to cut the socket. A much shorter one adds traffic without detecting problems meaningfully sooner.

## On SupaHost

Heartbeats work exactly as in upstream Supabase. They cost nothing extra: SupaHost doesn't charge per Realtime message or connection. Their bytes do pass through your project's gateway and count toward monthly egress, but a heartbeat is under 100 bytes, so even thousands of idle connections add very little.

## Related

- [Silent disconnects when the app is in the background](/kb/realtime-background-disconnects)
- [Debugging Realtime with logs and DevTools](/kb/realtime-debugging)
- [Realtime on SupaHost](/docs/realtime/overview)
- [Egress on SupaHost explained](/kb/egress-explained)
