Real-Time WebSocket Notifications for Your Website
Imagine: a user places an order but doesn't receive a status update until they refresh the page. Or a moderator doesn't see a new comment until a reload. This isn't just an inconvenience — it's a loss of conversion and trust. We are a team of engineers with 5+ years of experience, developing instant notification systems using WebSocket that deliver events in milliseconds, without unnecessary server requests. Across 80+ projects, we've built a reliable architecture that withstands peak loads.
Why WebSocket Instead of Polling?
With polling, the client queries the server every N seconds. With 10,000 users requesting status every 5 seconds, the server receives 120,000 requests per minute — a huge load with up to 5 seconds of delay. WebSocket establishes a persistent connection: data arrives instantly, and traffic is reduced tenfold. We use WebSocket not only for chats but also for order status alerts, marketplace notifications, ticket systems, and any scenario requiring reactivity.
What Problems We Solve
Excessive Polling and Server Load
Switching to WebSocket cuts HTTP requests from hundreds of thousands to a few hundred per hour. On one project, after replacing polling with WebSocket, backend load dropped by 80%, and infrastructure costs halved — saving roughly $2,400 per month (based on current exchange rates).
Delivering Notifications to Offline Users
If a user closes a tab but still wants to receive notifications, we store them in the database and send them upon the next connection. Our solution uses Redis Pub/Sub with persistence. Additionally, we send push notifications via FCM/APNS. Offline delivery works with a delay of no more than 100 ms after reconnection.
Scaling to Thousands of Connections
A single application can handle tens of thousands of WebSocket connections. We scale using Redis or Kafka so connections work across multiple instances. Load testing confirms stability up to 50,000 concurrent connections.
How We Ensure Delivery to Offline Users
In a typical architecture (see code below), we store a mapping of userId → set<socketId>. When a user connects, we check for undelivered notifications in the database and deliver them. If the user is offline, the notification is saved with a pending status.
// notification-ws.service.ts
class NotificationWebSocketService {
private userSockets = new Map<string, Set<string>>();
async onConnect(socket: Socket, userId: string) {
if (!this.userSockets.has(userId)) {
this.userSockets.set(userId, new Set());
}
this.userSockets.get(userId)!.add(socket.id);
socket.join(`user:${userId}`);
const pending = await this.notificationRepo.findUndelivered(userId);
if (pending.length > 0) {
socket.emit('notifications:batch', pending);
await this.notificationRepo.markDelivered(pending.map(n => n.id));
}
}
async sendToUser(userId: string, notification: Notification): Promise<void> {
const isOnline = this.userSockets.has(userId) &&
this.userSockets.get(userId)!.size > 0;
if (isOnline) {
io.to(`user:${userId}`).emit('notification:new', notification);
await this.notificationRepo.markDelivered([notification.id]);
} else {
await this.notificationRepo.save({ ...notification, status: 'pending' });
await this.pushService.send(userId, notification);
}
}
}
Why the React Hook useNotifications Simplifies Integration?
On the frontend, we've prepared a React hook that subscribes to Socket.IO events. It automatically handles adding new notifications, counting unread ones, and marking as read. All a developer needs to do is call useNotifications() and render the list.
// hooks/useNotifications.ts
function useNotifications() {
const [notifications, setNotifications] = useState<Notification[]>([]);
const [unreadCount, setUnreadCount] = useState(0);
const socket = useSocket();
useEffect(() => {
if (!socket) return;
socket.on('notification:new', (notification: Notification) => {
setNotifications(prev => [notification, ...prev]);
setUnreadCount(prev => prev + 1);
showToast(notification);
});
socket.on('notifications:batch', (batch: Notification[]) => {
setNotifications(prev => [...batch, ...prev]);
setUnreadCount(prev => prev + batch.filter(n => !n.readAt).length);
});
return () => {
socket.off('notification:new');
socket.off('notifications:batch');
};
}, [socket]);
const markAsRead = async (id: string) => {
await fetch(`/api/notifications/${id}/read`, { method: 'POST' });
setNotifications(prev =>
prev.map(n => n.id === id ? { ...n, readAt: new Date() } : n)
);
setUnreadCount(prev => Math.max(0, prev - 1));
};
const markAllAsRead = async () => {
await fetch('/api/notifications/read-all', { method: 'POST' });
setNotifications(prev => prev.map(n => ({ ...n, readAt: n.readAt || new Date() })));
setUnreadCount(0);
};
return { notifications, unreadCount, markAsRead, markAllAsRead };
}
Notification Types and Their Visualization
We define typical events for a typical application:
| Type | Description | Icon |
|---|---|---|
| order:status_changed | Order status changed | 📦 |
| message:received | New chat message | 💬 |
| mention:comment | Mention in comment | @ |
| task:assigned | New task assigned | ✅ |
| payment:processed | Payment processed | 💳 |
| system:alert | System warning | ⚠️ |
For each type, you can configure the display duration of the Toast and the click action.
Comparison: Polling vs WebSocket
| Parameter | Polling | WebSocket |
|---|---|---|
| Latency | 1–5 sec | < 100 ms |
| Server Load | High | Low |
| Traffic | ~120,000 requests/min | ~1,000 messages/min |
| Scaling | Difficult with many clients | Easy with Redis/Kafka |
What's Included in the Work
- Architectural documentation: interaction scheme, stack selection (Socket.IO, Redis, PostgreSQL).
- Backend service implementation: WebSocket handler, authorization integration, disconnect handling.
- Frontend module: React hook, bell component, Toast notifications.
- Push notification integration (FCM/APNS) — optional.
- Load testing: verification up to 50,000 concurrent connections.
- Deployment instructions and team training.
We guarantee stable operation under peak loads — 5+ years of experience in real-time solutions and 80+ completed projects speak for themselves. Contact us to discuss your project — we'll help you choose the optimal architecture and implement it turnkey. Get a consultation now.
Our Process
- Analysis — study the current architecture, identify notification scenarios, and assess load.
- Design — select protocol (WebSocket, SSE), message broker (Redis, Kafka), storage scheme.
- Implementation — build backend + frontend, cover with tests.
- Integration — connect to existing project (Laravel, Nest.js, Django, etc.).
- Deployment and monitoring — set up CI/CD, logging, alerts.
Estimated Timelines
Basic implementation (WebSocket + offline storage + React hook) — 7–10 days. With push notifications and load testing — 2–3 weeks. Cost is calculated individually — write to us, we'll estimate your project.







