Using WebSocket for instant messaging, a demo is easy to get working: the browser connects to the server and sends and receives messages bidirectionally. But after going live, problems concentrate in three areas: lax token authentication leads to connection abuse; offline messages are lost after disconnection; and message ordering becomes inconsistent when multiple devices are online. This article provides runnable code in the order of connection establishment, message frame design, server push, and disconnection recovery, and notes the gaps compared with off-the-shelf IM software at each step to help you decide whether building your own is worthwhile before you start.
Establishing a WebSocket Connection: Client and Server Handshake and Authentication
Client code:
const socket = new WebSocket(
`wss://example.com/ws?token=${encodeURIComponent(token)}`
);
socket.onopen = () => console.log('connected');
socket.onerror = (e) => console.error('ws error', e);
The server uses Node.js's ws library to handle the upgrade:
const { WebSocketServer } = require('ws');
const wss = new WebSocketServer({ noServer: true });
server.on('upgrade', (req, socket, head) => {
const url = new URL(req.url, 'http://localhost');
const token = url.searchParams.get('token');
if (!verifyToken(token)) {
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
socket.destroy();
return;
}
wss.handleUpgrade(req, socket, head, (ws) => {
wss.emit('connection', ws, req);
});
});
Do not put the token into Sec-WebSocket-Protocol, some proxies and clients will drop or truncate it. In production, you can put only a one-time ticket in the query parameters; after validation, the server binds the user identity and issues a short-lived session id. After the connection is established, the client completes a second verification by carrying a signature in the first message. Off-the-shelf IM SDKs usually already encapsulate handshake, authentication, and reconnection, so developers do not need to handle these edge cases.
Define a unified message frame: uplink and downlink structures
All messages use the same JSON structure,seq generated by the client and monotonically increasing, used for acknowledgment and ordering.
{
"type": "chat",
"seq": 1024,
"to": "user_42",
"payload": { "text": "hello" }
}
{
"type": "chat",
"seq": 1024,
"from": "user_7",
"to": "user_42",
"ts": 1710000000000,
"payload": { "text": "hello" }
}
When the server returns an ack, it carries the same seq; before receiving the ack, the client keeps a copy for retries. In group chat, replace to with room_id, and the server is responsible for expanding recipients. Do not rely on WebSocket's single-connection ordering: when the same user is online on multiple devices, the server must push after sorting by seq before pushing; otherwise, the display order may be inconsistent across devices.
Server push: from single connections to room/user targeting
const clients = new Map(); // userId -> Set<WebSocket>
function addClient(userId, ws) {
if (!clients.has(userId)) clients.set(userId, new Set());
clients.get(userId).add(ws);
ws.on('close', () => {
const set = clients.get(userId);
if (!set) return;
set.delete(ws);
if (set.size === 0) clients.delete(userId);
});
}
function sendToUser(userId, message) {
const sockets = clients.get(userId);
if (!sockets) return false;
for (const ws of sockets) {
if (ws.readyState === 1) {
ws.send(JSON.stringify(message));
}
}
return true;
}
The connection registry must handle multi-device online status and abnormal disconnection; otherwise, Set will grow indefinitely. For one-to-one chat, write to both sender and receiver simultaneously; for group chat, query room members first and then broadcast. If the connection is not OPEN when sending, write the message to the offline queue instead of dropping it directly. When server push latency increases, for the layered troubleshooting method by network, server, and client, see How to troubleshoot high instant messaging latency? Layered troubleshooting steps from network and server to client.
Disconnect/Reconnect and Heartbeat: The Boundary Where Messages Are Not Lost
- The client sends every 30 seconds
{"type":"heartbeat"}; if the server receives no frames within 35 seconds, it considers the connection invalid and closes it. - The client
onclosethen reconnects with backoff at 1s, 2s, 4s, 8s, capped at 30s; after successful reconnection, first pulls the local lastseqmessages after that. - The server does not resend messages to users who have been offline for a long time unless you persist messages on the server and maintain cursors; if guaranteed offline delivery is required, you must persist to disk, pull missed messages, and use idempotent deduplication.
WebSocket only addresses the bidirectional channel; it does not provide guaranteed message delivery, offline storage, or multi-device sync. For specific implementation of these capabilities, refer toKey mechanisms for ensuring guaranteed message delivery: how to achieve no message loss in instant messagingandOffline messages always lost? Common causes and fixes for not receiving offline messages in instant messaging.
Self-built WebSocket vs. Off-the-shelf IM Software Feature Comparison Review
| Dimension | Self-built WebSocket | Off-the-shelf IM software |
|---|---|---|
| Message reliability | Requires self-implemented ack, retry, and idempotency | Built-in acknowledgment, retry, and redelivery strategies |
| Offline messages | Requires persistence and cursor maintenance | Provides offline queue and backfill by default |
| Multi-device sync | Requires the server to sort and push by seq. | Already handles multi-device cursors and read states. |
| Admin console | Requires building a separate console. | Provides member, group, and audit backend features. |
| Operations and maintenance costs | Long-term monitoring, scaling, and fixes. | Vendor-maintained, subscription-based billing. |
If your team only has frontend developers, or you need to launch multi-device sync and offline messaging within two weeks, self-hosting is usually not cost-effective; you can first refer toBudget-limited SMB instant messaging selection: focus on these 5 dimensions.Validate requirements. When you have decided to self-host, first calculate the long-term costs and refer toCost breakdown: how much self-hosting instant messaging costs and what hidden expenses are involved..
FAQ
Can WebSocket guarantee message delivery?
No. WebSocket only provides a full-duplex channel. Disconnects, server failures, and clients not sending ack can all cause message loss. Guaranteed delivery requires persistence, seq catch-up, ack retries, and idempotent deduplication.
What is a suitable heartbeat interval?
For desktop clients, 30 seconds is common, and the server can consider the connection broken if no frame is received for 35 seconds; on mobile, it can be relaxed to 60 seconds, but you must listen for network changes and actively reconnect.
Can you quickly implement instant messaging without backend experience?
You can get a single-page demo running, but handling offline messages, multi-device sync, and operations requires backend and storage fundamentals. Without backend experience, it is recommended to use an existing SDK or commercial IM directly.
When should you stop building in-house and purchase directly?
When the requirements include any two or more of offline guaranteed delivery, multi-device sync, admin management, and audit compliance, or when the launch timeline is less than two weeks, purchasing a commercial IM is usually more stable and cheaper than building in-house.
