Skip to main content

Notification Feed

A simulated app with a notification bell that receives real-time notifications. Click the bell to see existing notifications, and watch as new ones arrive. The channel auto-subscribes on connect using a friendly alias. Deploy notifications also update the Recent Deploys list in real time.

Get the code for this example and run it yourself from the GitHub repository.

How it works

This example uses the @hotsock/hotsock-js client library to demonstrate per-user channels with channel aliases, autoSubscribe, and server-published notifications. Below is a walkthrough of each piece.

Authentication

The token grants subscribe access to a per-user notification channel with several features enabled:

{
"exp": 1693963905,
"scope": "connect",
"uid": "user-1",
"umd": { "name": "Sarah" },
"channels": {
"notifications-user-1": {
"subscribe": true,
"alias": "notifications",
"autoSubscribe": true,
"historyStart": 0
}
}
}

Key claims:

  • alias: The client uses "notifications" as the channel name instead of the real "notifications-user-1". This hides internal naming from client code.
  • autoSubscribe: The channel subscribes automatically when the connection is established — no explicit subscribe call needed.
  • historyStart: 0: Grants access to the full stored message history for loading past notifications.
  • No messages claim — notifications are server-published only.

Auto-subscribing with aliases

With autoSubscribe: true, the client doesn't need to call subscribe explicitly. It just references the channel using the alias and waits for the hotsock.subscribed event:

const channel = client.channels("notifications")

channel.bind("hotsock.subscribed", () => {
// Channel is ready — start receiving notifications
})

The client code only ever references "notifications" — it doesn't need to know the real channel name "notifications-user-1". This is useful when channel names contain dynamic IDs that the client shouldn't need to manage.

Receiving notifications

All notifications arrive as a single notification event. The notification type (info, success, warning) is included in the data payload and determines how the notification is styled:

channel.bind("notification", (message) => {
const { type, title, body, icon } = message.data
setNotifications((prev) => [
{
id: crypto.randomUUID(),
event: type || "info",
title,
body,
icon,
timestamp: new Date(),
read: false,
},
...prev,
])
})

Using a single event name with type inside the data is simpler than binding to multiple event names — adding new notification types later doesn't require any client code changes.

Updating multiple UI elements

A single notification can update multiple parts of the UI. When a deploy notification arrives, the app also extracts the version number and prepends it to the Recent Deploys list:

if (title === "Deploy complete") {
const match = body.match(/v[\d.]+/)
if (match) {
setDeploys((prev) => [match[0], ...prev])
}
}

This shows how real-time messages can drive updates across different components simultaneously.

Per-user channels

Each user has their own dedicated channel (notifications-user-{id}). This means notifications are private — only the intended recipient receives them. The server publishes to a specific user's channel using the Publish Messages API:

{
"channel": "notifications-user-1",
"event": "notification",
"data": {
"type": "info",
"title": "New follower",
"body": "Alex started following you",
"icon": "user-plus"
}
}

Cleanup

When the component unmounts, terminate the client to close the WebSocket connection:

useEffect(() => {
return () => {
client.terminate()
}
}, [])

Putting it all together

The full flow is: the user connects with a token that auto-subscribes to their private notification channel via a friendly alias, and receives new notifications in real time as a single notification event with the type in the data payload. Deploy notifications also update the Recent Deploys list, showing how one real-time message can drive updates across multiple UI elements. The client never needs to know the real channel name or explicitly subscribe — everything is handled by the token claims.

For a deeper dive, see the Channel Aliases documentation, the autoSubscribe claim, and the Publish Messages API.