Understanding and Monitoring Realtime Heartbeats
Last edited: 12/12/2025
What are heartbeat messages?
Heartbeat messages are periodic signals sent between the Realtime client and server to verify that the WebSocket connection is alive and functioning properly. The client sends a heartbeat message on the phoenix topic with the event type heartbeat at regular intervals (default: 25 seconds), and the server responds with a phx_reply message.
These messages serve as a keep-alive mechanism to detect connection issues that might not be immediately apparent, such as silent network failures or intermediary proxy timeouts.
Why heartbeats matter
Heartbeat messages are critical for maintaining reliable real-time connections:
- Connection health monitoring: They detect when a connection has silently failed without triggering WebSocket close events
- Automatic recovery: When heartbeats time out, the client automatically attempts to reconnect
- Network proxy compatibility: Many network proxies and load balancers close idle connections; heartbeats keep the connection active
- Early issue detection: Heartbeat timeouts alert you to connection problems before users experience message delivery failures
Without heartbeats, your application might appear connected while being unable to send or receive messages.
How to handle heartbeat messages
You can monitor heartbeat lifecycle events using the onHeartbeat method:
1import { useEffect } from 'react'2import { createClient } from '@supabase/supabase-js'34function MyComponent() {5 const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)67 useEffect(() => {8 supabase.realtime.onHeartbeat((status) => {9 console.log('Heartbeat status:', status)10 // status can be: 'sent', 'ok', 'error', 'timeout', or 'disconnected'11 })12 }, [supabase])1314 return <div>Your app content</div>15}Or configure the callback during client initialization:
1const supabase = createClient(SUPABASE_URL, SUPABASE_KEY, {2 realtime: {3 heartbeatCallback: (status) => {4 console.log('Heartbeat status:', status)5 },6 },7})Troubleshooting heartbeat issues
Frequent heartbeat timeouts
If you're seeing frequent timeout status in your heartbeat callback:
- Network instability: Check your network connection quality
- Firewall/proxy issues: Corporate firewalls or proxies might interfere with WebSocket connections
- Mobile app suspension: On mobile, the app can be suspended which can stop the heartbeat timers
Connection not reconnecting after timeout
The client automatically attempts reconnection with exponential backoff (1s, 2s, 5s, 10s). If you need to manually reconnect:
1import { useState, useEffect } from 'react'2import { createClient } from '@supabase/supabase-js'34function ConnectionStatus() {5 const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)6 const [isConnected, setIsConnected] = useState(true)78 useEffect(() => {9 supabase.realtime.onHeartbeat((status) => {10 if (status === 'ok') {11 setIsConnected(true)12 } else if (status === 'timeout' || status === 'disconnected') {13 setIsConnected(false)14 }15 })16 }, [supabase])1718 const handleReconnect = () => {19 supabase.realtime.connect()20 }2122 return (23 <div>24 <p>Status: {isConnected ? 'Connected' : 'Disconnected'}</p>25 {!isConnected && <button onClick={handleReconnect}>Reconnect</button>}26 </div>27 )28}Customizing heartbeat interval
You can adjust the heartbeat frequency when creating your Supabase client in React:
1import { createClient } from '@supabase/supabase-js'2import { useMemo } from 'react'34function App() {5 const supabase = useMemo(6 () =>7 createClient(SUPABASE_URL, SUPABASE_KEY, {8 realtime: {9 heartbeatIntervalMs: 15000, // Send heartbeat every 15 seconds (default: 25000)10 },11 }),12 []13 )1415 return <YourApp supabase={supabase} />16}Note: Increasing the interval reduces network traffic but delays detection of connection failures. Decreasing it improves detection speed but increases overhead.
Heartbeat errors in Web workers
For React applications with long-running connections, use Web Workers to prevent browser tab throttling:
1import { createClient } from '@supabase/supabase-js'2import { useMemo } from 'react'34function App() {5 const supabase = useMemo(6 () =>7 createClient(SUPABASE_URL, SUPABASE_KEY, {8 realtime: {9 worker: true,10 workerUrl: '/worker.js', // Optional: Place in public folder11 },12 }),13 []14 )1516 return <YourApp supabase={supabase} />17}If the worker fails to start, check:
- Browser Web Worker support is available
- Worker script is in the public folder and accessible
- CORS headers allow loading the worker script
Best practices
Show connection status to users
Display connection status with a simple indicator:
1import { useState, useEffect } from 'react'2import { createClient } from '@supabase/supabase-js'34function ConnectionIndicator() {5 const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)6 const [status, setStatus] = useState('healthy')78 useEffect(() => {9 supabase.realtime.onHeartbeat((heartbeatStatus) => {10 if (heartbeatStatus === 'ok') {11 setStatus('healthy')12 } else if (heartbeatStatus === 'timeout') {13 setStatus('poor')14 } else if (heartbeatStatus === 'disconnected') {15 setStatus('disconnected')16 }17 })18 }, [supabase])1920 return (21 <div>22 <span>Connection: {status}</span>23 </div>24 )25}Reconnect when React Native app comes to foreground
Ensure connection is active when user opens your app:
1import { useEffect } from 'react'2import { AppState } from 'react-native'3import { createClient } from '@supabase/supabase-js'45function App() {6 const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)78 useEffect(() => {9 const subscription = AppState.addEventListener('change', (nextAppState) => {10 if (nextAppState === 'active') {11 if (!supabase.realtime.isConnected()) {12 supabase.realtime.connect()13 }14 }15 })1617 return () => {18 subscription.remove()19 }20 }, [supabase])2122 return <YourApp />23}Monitor connection in your entire app
Set up monitoring when you create your Supabase client:
1import { createClient } from '@supabase/supabase-js'23const supabase = createClient(SUPABASE_URL, SUPABASE_KEY, {4 realtime: {5 heartbeatCallback: (status) => {6 if (status === 'ok') {7 console.log('Connection healthy')8 } else if (status === 'timeout') {9 console.warn('Connection slow')10 } else if (status === 'disconnected') {11 console.error('Disconnected')12 }13 },14 },15})1617export default supabase