WebSocket
Subscribe to real-time MisarBlog events over a persistent WebSocket connection.
Overview
MisarBlog exposes a WebSocket server at wss://api.misar.io/blog/ws/* for real-time event delivery. Use WebSocket channels to receive live notifications and article engagement updates without polling.
Authentication
Connect with your API key in the Authorization header or as a query parameter: ?token=mbk_.... WebSocket connections cannot set Authorization headers from browsers, so the ?token= query parameter is supported for browser clients.
Channels
| Channel | Path | Description |
|---|---|---|
| User notifications | wss://api.misar.io/blog/ws/notifications | Receive notification events for the authenticated user |
| Article live updates | wss://api.misar.io/blog/ws/articles/:slug | Receive live view count and reaction updates for an article |
Connect
const token = "mbk_your_api_key";
const ws = new WebSocket(`wss://api.misar.io/blog/ws/notifications?token=${token}`);
ws.onopen = () => console.log("Connected");
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log("Event:", data);
};
ws.onclose = (event) => {
console.log("Disconnected:", event.code, event.reason);
};
ws.onerror = (err) => console.error("WebSocket error:", err);import WebSocket from "ws";
const ws = new WebSocket("wss://api.misar.io/blog/ws/notifications", {
headers: { Authorization: "Bearer mbk_your_api_key" },
});Notifications channel
Path
wss://api.misar.io/blog/ws/notifications
Event types
type | Description |
|---|---|
new_subscriber | Someone subscribed to your newsletter |
new_comment | A new comment was posted on your article |
article_reaction | A reader reacted to your article |
milestone | View count milestone reached |
Example events
{
"type": "new_subscriber",
"subscriber": {
"email": "[email protected]",
"subscribed_at": "2026-05-27T10:30:00Z"
}
}{
"type": "new_comment",
"articleSlug": "my-first-post",
"comment": {
"id": "comment-uuid",
"body": "Great article!",
"author": "reader123",
"createdAt": "2026-05-27T10:31:00Z"
}
}Article live updates channel
Path
wss://api.misar.io/blog/ws/articles/:slug
Substitute :slug with the article's URL slug. Article channels are open to any authenticated user — they do not require ownership of the article.
Event types
type | Description |
|---|---|
view_update | View count changed |
reaction_update | Reaction counts changed |
Example event
{
"type": "view_update",
"slug": "my-first-post",
"viewCount": 1042,
"timestamp": "2026-05-27T10:35:00Z"
}const ws = new WebSocket(
`wss://api.misar.io/blog/ws/articles/my-first-post?token=${token}`
);
ws.onmessage = (event) => {
const { type, viewCount } = JSON.parse(event.data);
if (type === "view_update") {
document.getElementById("view-count")!.textContent = String(viewCount);
}
};Heartbeat
The server sends a ping frame every 30 seconds. Respond with a pong frame to keep the connection alive. Most WebSocket libraries handle this automatically.
If no pong is received within 30 seconds of a ping, the server closes the connection with code 1001 (Going Away).
Close codes
| Code | Meaning |
|---|---|
1000 | Normal closure |
1001 | Server restart or timeout |
1008 | Policy violation (auth failed) |
4401 | Unauthorized — API key missing or invalid |
4403 | Forbidden — access denied |
Reconnection
Implement exponential backoff when reconnecting:
function connect(token: string, slug?: string) {
const path = slug
? `wss://api.misar.io/blog/ws/articles/${slug}`
: "wss://api.misar.io/blog/ws/notifications";
const ws = new WebSocket(`${path}?token=${token}`);
let retryDelay = 1000;
ws.onclose = () => {
console.log(`Reconnecting in ${retryDelay / 1000}s...`);
setTimeout(() => connect(token, slug), retryDelay);
retryDelay = Math.min(retryDelay * 2, 30_000);
};
ws.onopen = () => {
retryDelay = 1000; // reset on successful connection
};
return ws;
}