Live in-app notifications
Show replies, mentions and approvals the moment they happen in every open tab, and fall back to a push notification when the user is offline.
Users notice when a product reacts the moment something happens: a comment on their post, an approved request, a teammate's mention. Polling for that every 30 seconds feels slow and wastes requests. With Hibira, your server sends the notification once and every tab and device the user has open shows it within milliseconds. When they are offline, a push notification takes over.
What your users get
- A bell badge and toast that update the moment something happens, in every open tab.
- The same notification on their phone when the app is closed.
- No reload to see new activity, and no stale counters.
Built with
- User authentication (
pusher.signin()), so each user has their own channel and only they can read it. sendToUserin the official server SDKs, which reaches every socket of that user on every node.- Push notifications through the Beams-compatible API for users who are not connected.
- Private encrypted channels (
private-encrypted-) when a notification carries data that should stay end-to-end encrypted.
Step 1: sign users in on the socket
Add a user authentication route next to your channel authorization route. It runs on your server and uses the app secret.
// Express
app.post('/pusher/user-auth', requireLogin, express.urlencoded({ extended: false }), (req, res) => {
const auth = pusher.authenticateUser(req.body.socket_id, {
id: String(req.user.id),
user_info: { name: req.user.name },
});
res.json(auth);
});In the browser, connect as in the JavaScript guide and sign in:
const pusher = new Pusher(import.meta.env.VITE_PUSHER_APP_KEY, {
cluster: 'ap',
wsHost: 'ws.hibira.io',
wsPort: 443,
wssPort: 443,
forceTLS: true,
enabledTransports: ['ws', 'wss'],
userAuthentication: { endpoint: '/pusher/user-auth', transport: 'ajax' },
});
pusher.signin();
pusher.user.bind('notification', (n) => {
bell.increment();
toast.show(n.title, { href: n.url });
});Step 2: send from your server
Save the notification in your database first, so the list is still there after a reload, then send it:
const n = await db.notifications.create({ userId, title: 'Ann replied to your comment', url: '/posts/42#c7' });
await pusher.sendToUser(String(userId), 'notification', { id: n.id, title: n.title, url: n.url });One sendToUser reaches all of the user's tabs and devices and counts as one message, however many sockets receive it.
Step 3: fall back to push when they are offline
Give each signed-in page a private channel such as private-user-42, and subscribe a webhook endpoint to channel_occupied and channel_vacated. Your server then knows who is online without asking:
app.post('/pusher/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
const webhook = pusher.webhook({ headers: req.headers, rawBody: req.body.toString() });
if (!webhook.isValid()) return res.sendStatus(401);
for (const e of webhook.getEvents()) {
const id = e.channel.match(/^private-user-(.+)$/)?.[1];
if (id) await db.users.update(id, { online: e.name === 'channel_occupied' });
}
res.sendStatus(200);
});When the user is offline, publish to their devices with the push notifications guide:
if (!(await db.users.isOnline(userId))) {
await beams.publishToUsers([String(userId)], {
web: { notification: { title: n.title, deep_link: 'https://your-site.example' + n.url } },
fcm: { notification: { title: n.title } },
apns: { aps: { alert: { title: n.title } } },
});
}Tips
- Keep the payload small (an ID, a title and a link) and fetch details when the user opens it. Events are limited to 10 KB (4 KB on the Free plan).
- Mark notifications as read on your server and send a
notifications-readevent, so the badge clears in every tab at once. - For health, finance or HR data, send to a
private-encrypted-user-42channel instead: Hibira only relays ciphertext. - On the Free plan, list your site's domains as allowed origins in the app settings.