0% прочитано

Why WebSockets Should Not Be the Only Source of Truth for Unread Notifications

WebSockets are excellent for immediate UI updates, but events can be missed. A tab may sleep, the network may disappear, or a laptop may wake after a pause. Real-time events should therefore be paired with HTTP refreshes that restore the actual state.

27 жовтня 2026 р. 9 хв читанняLaravel

When notifications arrive through WebSockets, the interface feels alive: a new event immediately increments the counter, adds an item to the list, or triggers the expected UI reaction.

This can lead to a dangerous assumption: if no WebSocket event arrived, then the state did not change.

In reality, a WebSocket is a channel for timely delivery, not a guarantee that the client observed every state change.

For unread notifications, the authoritative state should remain on the server and the client must be able to fetch it again.

WebSockets provide immediacy. HTTP refresh restores correctness when some events were missed.

A WebSocket Event Can Be Missed in a Healthy System

A missed event does not necessarily mean the server failed or the WebSocket implementation is broken.

The client may temporarily stop listening. The browser may suspend the tab. A laptop may sleep. The network may disappear for a few seconds. The connection may recover only after the event was already sent.

In all of these cases, the server state may be correct while the browser state is stale.

The Browser Counter Is Derived State

If the server stores notifications and their read state, then "5 unread" in the browser is not an independent source of data.

It is only a local representation of a result that can be recalculated or fetched from the server.

That is why the local counter may be updated immediately after an event, but it should periodically be reconciled with the server.

Local state can be recovered
Server state:    unread = 7 Browser state:    unread = 6 Reason:    one WebSocket event was missed Recovery:    GET /notifications/unread-count    -> unread = 7

Use WebSockets for Immediacy

There is no need to avoid WebSockets just because events can be missed.

Their strength is that the interface does not need to poll the server constantly. When a notification appears, the client can update the counter and list immediately.

The problem begins only when that channel becomes the only way to learn that state changed.

Immediate update after an event
echo.private(`notification.user.${userId}`)    .listen('.notification.created', () => {        unreadCount.value += 1;        refreshNotificationList();    });

Use HTTP for State Recovery, Not as a Backup WebSocket

The HTTP request has a different role. It does not try to replay every event.

It simply asks: "what is the correct state now?"

This is an important simplification. The client does not need to know which exact event was missed. It only needs to fetch the current unread count or current notification list again.

Restore the current state over HTTP
async function refreshUnreadCount() {    const response = await api.get(        '/notifications/unread-count'    );     unreadCount.value =        response.data.unread;}

When to Re-Synchronize State

There is no need to refresh state every second. It is more useful to choose moments when local and server state are genuinely likely to diverge.

  • when the tab becomes visible again;
  • after pageshow;
  • after network recovery;
  • after WebSocket reconnection;
  • after login or session restoration;
  • after actions that change read state.
Refresh at likely divergence points
document.addEventListener(    'visibilitychange',    () => {        if (            document.visibilityState === 'visible'        ) {            refreshUnreadCount();        }    }); window.addEventListener(    'pageshow',    refreshUnreadCount); 

Reconnection Should Trigger Reconciliation

A WebSocket reconnection means only that the channel is available again.

It does not prove that the client received every event that occurred during the gap.

After reconnection, it is therefore useful not only to continue listening for new events but also to fetch the current state over HTTP.

Recover State, Not Event History, When Current State Is Enough

For an unread counter, it often does not matter whether the client missed one, two, or five events.

What matters is the correct count now.

Re-reading server state can therefore be both more reliable and much simpler than trying to replay every missed event.

Immediacy and Correctness Work Together

Two mechanisms with different responsibilities
WebSocket event    -> immediate local update visibilitychange / pageshow / reconnect / online    -> HTTP refresh    -> reconcile with server    -> restore correct state

These two mechanisms are not duplicates.

WebSockets provide fast reaction to a new event. HTTP returns the client to the correct state when a gap appears.

The system therefore gets both immediacy and the ability to recover after temporary disconnection.

The Same Principle Applies Beyond Notifications

The same model is useful for other data updated through real-time events:

  • message counters;
  • task state;
  • order status;
  • long-running operation progress;
  • online presence;
  • other derived UI indicators.

If an event merely announces a change, the client should have a way to fetch the correct state again without depending on a complete event history.

What to Test

Minimal test coverage
1. A new event updates the counter immediately.2. A missed event does not leave the counter wrong forever.3. Returning to the tab triggers reconciliation.4. pageshow triggers reconciliation.5. Network recovery triggers reconciliation.6. WebSocket reconnection triggers reconciliation.7. The HTTP response replaces a stale local counter.8. Repeated refreshes do not duplicate notifications.

WebSockets Should Speed Up the Interface, Not Define Truth

If counter correctness depends on the browser never missing a single event, the system is fragile.

A more reliable model is different: WebSockets announce changes quickly, while authoritative server state can always be fetched again over HTTP.

Temporary disconnection then stops being irreversible information loss for the client.

This complements the problem described in the separate article about stale counters after returning to a browser tab, but the boundary here is broader: it is not only about returning to a tab, but about separating real-time delivery from authoritative state.

A WebSocket tells the client that something changed. Server state tells it what is correct now.