WebSocket Streaming
Receive real-time notifications and per-article live updates over a persistent WebSocket connection.
Overview
MisarBlog runs a WebSocket server on its own application host (the custom Next.js server), not on the REST API host. Connect to wss://www.misar.blog/ws/* to receive events without polling. Two channels are available: user notifications and per-article live updates.
SSE — AI Research
Stream a typed AI research run over Server-Sent Events.
SSE — Newsletter Progress
Track newsletter send progress via Server-Sent Events.
Channels
The connection path is /ws/<channel>/<channelId?>. If no channel segment is given, the connection defaults to notifications.
wss://www.misar.blog/ws/notifications
User-scoped events for the authenticated key owner (the account whose API key opened the connection).
const ws = new WebSocket(
`wss://www.misar.blog/ws/notifications?token=${apiKey}`
);
ws.onmessage = (e) => {
const event = JSON.parse(e.data);
// event.type === "connected" on open; other objects are pushed notifications
};wss://www.misar.blog/ws/articles/:slug
Live updates (view counts, reactions) for a single article, identified by its slug in the path.
const ws = new WebSocket(
`wss://www.misar.blog/ws/articles/my-first-post?token=${apiKey}`
);
ws.onmessage = (e) => {
const event = JSON.parse(e.data);
// updates for the "my-first-post" article are delivered here
};Push payloads are delivered as JSON objects to every connection subscribed to the relevant user or article. Handle each message defensively by inspecting its fields — the only messages the server guarantees are the connected handshake and pong replies described below.
Authentication
Authentication happens during the HTTP upgrade, using an API key (mbk_…). An invalid or missing key is rejected with 401 Unauthorized and the socket is closed.
Query parameter vs header
Browser clients cannot set an Authorization header on a WebSocket connection — pass the key as ?token=mbk_…. Node.js clients may use either the query parameter or the Authorization: Bearer header.
const ws = new WebSocket(`wss://www.misar.blog/ws/notifications?token=${apiKey}`);import WebSocket from "ws";
const ws = new WebSocket("wss://www.misar.blog/ws/notifications", {
headers: { Authorization: `Bearer ${apiKey}` },
});Connection handshake
Immediately after a successful upgrade, the server sends a connected message:
{ "type": "connected", "channel": "notifications", "userId": "…" }| Field | Type | Description |
|---|---|---|
type | string | Always "connected" for the handshake. |
channel | string | "notifications" or "articles". |
userId | string | The authenticated account id. |
Application-level ping
You can send a ping message and the server replies with a pong. This is separate from the protocol-level heartbeat below.
ws.send(JSON.stringify({ type: "ping" }));
// server → { "type": "pong" }Heartbeat
The server also runs a protocol-level heartbeat, sending a WebSocket ping frame every 30 seconds. Browsers and standard WebSocket libraries reply with a pong automatically. A connection that fails to respond by the next interval is terminated by the server.
Reconnection
function connectWs(apiKey: string, channel: string) {
let delay = 1_000;
let ws: WebSocket;
function connect() {
ws = new WebSocket(`wss://www.misar.blog/ws/${channel}?token=${apiKey}`);
ws.onopen = () => { delay = 1_000; };
ws.onclose = () => setTimeout(connect, delay = Math.min(delay * 2, 30_000));
return ws;
}
return connect();
}