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.
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:
{"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
accessTokencallback, 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.
Watching connection health
You can observe each heartbeat from your code with heartbeatCallback:
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.