Real-Time Communication: WebSocket, SSE and Webhook — An Engineering Decision Framework
Real-time communication is not just WebSocket. This article compares WebSocket, SSE (Server-Sent Events), and Webhook across three dimensions: push direction, connection model, and reconnection strategy — with implementation patterns and selection guidance for backend developers and architects.
The Bottom Line: Choose by Push Direction
Three mainstream real-time communication solutions exist: WebSocket, SSE (Server-Sent Events), and Webhook. The selection criterion is not “which is newer” — it is “which direction your data flows.”
| Solution | Direction | Connection | Best For |
|---|---|---|---|
| WebSocket | Bidirectional | Long-lived | Chat, collaboration, gaming, live dashboards |
| SSE | Server → Client | Long-lived | Notifications, data push, log streams |
| Webhook | Server → Client | Callback | Payment callbacks, CI/CD notifications, event subscriptions |
1. WebSocket: Bidirectional, Highest Complexity
Best For
- Chat / instant messaging
- Collaborative editing
- Real-time gaming
- Live dashboards with server push
Implementation Notes
Connection management: WebSocket is a long-lived connection requiring a connection pool. Map connection IDs to user IDs on each client connection.
const connections = new Map<string, WebSocket>();
wss.on('connection', (ws, req) => {
const userId = parseUserId(req);
connections.set(userId, ws);
ws.on('close', () => connections.delete(userId));
});
Heartbeat: Network issues may disconnect a WebSocket without firing the close event. Send periodic pings to check connection health.
setInterval(() => {
connections.forEach((ws, id) => {
if (ws.readyState === WebSocket.OPEN) {
ws.ping();
} else {
connections.delete(id);
}
});
}, 30000);
Horizontal scaling: Use Redis Pub/Sub or a message queue to broadcast across WebSocket servers:
redis.publish('chat:channel', JSON.stringify(message));
redis.subscribe('chat:channel', (message) => {
const { userId, content } = JSON.parse(message);
const ws = connections.get(userId);
if (ws) ws.send(content);
});
2. SSE: Server Push, Simple and Effective
Best For
- Notifications (new message alerts, system notifications)
- Data push (stock prices, log streams, progress updates)
- Live data updates (dashboards, monitoring panels)
Implementation
SSE is much simpler than WebSocket — set the correct Content-Type and keep the connection open:
// Server
app.get('/events', (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
setInterval(() => {
res.write(`data: ${JSON.stringify({ time: Date.now() })}\n\n`);
}, 5000);
});
// Client
const eventSource = new EventSource('/events');
eventSource.onmessage = (e) => {
console.log('Push received:', JSON.parse(e.data));
};
SSE’s biggest advantage: browsers natively support automatic reconnection. No need to implement reconnection logic yourself.
SSE Limitations
- Unidirectional — client cannot send data over the same connection
- Browser connection limit: 6 SSE connections per domain (HTTP/1.1), no limit with HTTP/2
- No binary data support (can be worked around with Base64 encoding)
3. Webhook: Event Notification, Back to HTTP
Best For
- Payment callbacks (notification after Alipay/WeChat Pay completes)
- CI/CD notifications (trigger build on code push)
- Event subscriptions (third-party service notifies you of events)
Implementation
Webhook is not a long-lived connection — it is an HTTP POST callback to the client’s URL. Less “real-time” but simpler and more reliable.
Retry mechanism: Without retries, notifications are lost if the client is unreachable:
async function sendWebhook(url: string, payload: any) {
const maxRetries = 3;
for (let i = 0; i < maxRetries; i++) {
try {
await axios.post(url, payload, { timeout: 5000 });
return;
} catch (err) {
await sleep(Math.pow(2, i) * 1000); // 1s, 2s, 4s
}
}
await logFailedEvent(url, payload);
}
Signature verification: Webhook callbacks can be forged. Use HMAC signatures:
const signature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
function verifySignature(payload: any, signature: string): boolean {
const expected = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return expected === signature;
}
4. Decision Tree
Does your scenario need real-time communication? ├── No → REST API polling (simple, sufficient) └── Yes → Push direction? ├── Bidirectional → WebSocket ├── Server → Client → Connection persistence? │ ├── Long-lived → SSE │ └── Short-lived (callback) → Webhook └── Mixed → WebSocket + Webhook
Summary
| Dimension | WebSocket | SSE | Webhook |
|---|---|---|---|
| Direction | Bidirectional | Unidirectional (server→client) | Unidirectional (server→client) |
| Connection | Long-lived | Long-lived | Short-lived (HTTP callback) |
| Browser support | Native | Native (EventSource) | N/A |
| Auto-reconnect | Manual | Native | Manual |
| Horizontal scaling | Message queue needed | Message queue needed | Natively supported |
| Complexity | High | Low | Low |
| Best for | Chat, collaboration, gaming | Notifications, push, logs | Callbacks, events, CI/CD |
Real-time communication selection is not about “which is more advanced” — it is about “which matches your data flow direction.” In most scenarios, SSE and Webhook combined are sufficient without the complexity of WebSocket.
Need real-time communication design or backend development? Contact us — tell us your communication scenario and concurrency scale, feasibility within 24 hours.
Related reading
- REST vs GraphQL vs gRPC — upper-layer API protocol selection reference
- API Security in Practice — security configuration for real-time communication endpoints
FAQ
What is the core difference between WebSocket and SSE?
WebSocket is bidirectional — both client and server can send messages at any time. SSE is unidirectional — the server pushes messages to the client, but the client cannot send messages over the same connection. If you only need server-side push (notifications, data updates), SSE is simpler, supports automatic reconnection, and works with HTTP/2. You only need WebSocket if you require bidirectional interaction (chat, collaborative editing, gaming).
What is the difference between Webhook and polling?
Polling means the client periodically asks the server "is there new data?" — consuming resources even when nothing changes. Webhook means the server proactively notifies the client when new data arrives — more efficient, but requires the client to expose a callback URL. The downside: if the client is unreachable (offline, network failure), the notification is lost. Webhooks should be paired with retry logic and event logs.
How should reconnection strategy be designed?
Use exponential backoff: after disconnection, retry after 1 second, then 2s, 4s, 8s, up to a maximum interval (e.g., 30s). Include the last received message ID on each reconnect attempt so the server can resume from that point, avoiding duplicate consumption. Both WebSocket and SSE support automatic reconnection, but you need to implement the backoff logic yourself.
How do you manage WebSocket connections at scale?
A single 4C8G server can handle approximately 50,000-100,000 WebSocket connections. Beyond that, you need horizontal scaling: use Redis Pub/Sub or a message queue (RabbitMQ, Kafka) to broadcast messages across multiple WebSocket servers, ensuring a user receives messages regardless of which server they are connected to.
This article comes from AI Enable Harness front-line delivery practice. Need a similar system or optimization service?
Subscribe to Updates
Get notified when new articles are published. No spam, occasional updates only.
Subscribe →