Receiving Notifications

Receiving Notifications

Once a client is connected, it receives notifications through handlers registered on the SignalR connection. Each handler is bound to a specific notification type.

Handlers

A handler is a callback function bound to a specific notification type. When a notification of that type arrives, Notiway invokes the matching handler and passes the notification object. You define what happens inside — display a toast, update a list, refresh a chart, play a sound, or anything else your application needs.

connection.on("order-shipped", (notification) => {
    // This runs every time an "order-shipped" notification arrives.
    // The notification object contains the full payload from the producer.
});

Register one handler per notification type. You can register as many types as your application needs.

What you can do inside a handler:

// Show a toast or banner
connection.on("payment-confirmed", (notification) => {
    showToast(`Payment of $${notification.body.amount} confirmed`);
});

// Add to a notification list
connection.on("invoice-ready", (notification) => {
    notifications.push(notification);
    updateBadgeCount(notifications.length);
    renderNotificationList(notifications);
});

// Update a live dashboard
connection.on("dashboard-update", (notification) => {
    const { metric, value, timestamp } = notification.body;
    updateChart(metric, value, timestamp);
});

// Update UI state (e.g. disable a button, show a progress bar)
connection.on("import-progress", (notification) => {
    const { taskId, progress, status } = notification.body;
    setProgressBar(taskId, progress);
    if (status === "completed") {
        enableDownloadButton(taskId);
    }
});

// Trigger a browser notification
connection.on("urgent-alert", (notification) => {
    new Notification(notification.body.title, {
        body: notification.body.message
    });
});

Handlers are plain JavaScript (or C#, Dart, etc.) — there are no restrictions on what you can do inside them. Use the notification.body payload to drive your application logic however you need.

Register handlers before they’re needed. Handlers for Global, User, and Connection audiences must be registered before calling start(). Handlers for Tenant and Group audiences must be registered before calling Subscribe. Persisted notifications are replayed at the moment of connect or subscribe — any handler not yet registered will miss them.

Notification Object

Every notification received by the client has the same structure, regardless of how it was routed or whether it was delivered live or replayed from persistence.

{
  "id": "order-service-order-shipped-2025-01-15T10:00:00Z",
  "type": "order-shipped",
  "body": {
    "orderId": "ORD-12345",
    "message": "Your order has been shipped!"
  },
  "routing": {
    "audienceType": 4,
    "audienceValue": "user-123"
  },
  "metadata": {
    "producer": "order-service",
    "timeStamp": "2025-01-15T10:00:00Z",
    "isPersisted": true
  },
  "audienceId": "user:user-123",
  "isRead": false,
  "isDeleted": false
}
FieldTypeDescription
idstringUnique notification identifier
typestringNotification type — matches the handler name
bodyobjectCustom payload defined by the producer
routingobjectAudience targeting (type, value, optional tenantId)
metadataobjectProducer name, timestamp, persistence settings
audienceIdstringAudience identifier — needed for MarkAsRead and MarkAsDeleted
isReadbooleanWhether the notification has been marked as read (persisted only)
isDeletedbooleanWhether the notification has been marked as deleted (persisted only)

Live vs Replayed Notifications

Notifications arrive through the same handler regardless of how they were delivered. Your handler code does not need to distinguish between the two.

Live — the client is connected when the notification is published. It arrives immediately through the matching handler.

Replayed — the client connects (or subscribes to an audience) and Notiway replays all persisted, non-expired notifications for that audience. Each replayed notification triggers the same handler as a live one would.

  sequenceDiagram
    participant Notiway
    participant Client

    Note over Client: Client connects
    Client->>Notiway: Connect
    Notiway->>Client: Replay persisted notifications
    Client->>Client: Handlers fire for each replayed notification

    Note over Client: While connected
    Notiway->>Client: Live notification arrives
    Client->>Client: Handler fires

This means you can use a single handler to build a notification list that includes both historical and live notifications:

const notifications = [];

connection.on("invoice-ready", (notification) => {
    notifications.push(notification);
    renderNotificationList(notifications);
});

await connection.start();

Managing Persisted Notifications

For persisted notifications, clients can update read and deleted state. These changes are synced across all of the user’s connected devices.

// Mark as read
await connection.invoke("MarkAsRead", notification.audienceId, notification.id);

// Mark as unread
await connection.invoke("MarkAsRead", notification.audienceId, notification.id, false);

// Mark as deleted
await connection.invoke("MarkAsDeleted", notification.audienceId, notification.id);

// Restore (undelete)
await connection.invoke("MarkAsDeleted", notification.audienceId, notification.id, false);

Cross-Device Sync

When a user marks a notification as read or deleted on one device, Notiway broadcasts the change to all of that user’s other connected clients. Listen for these events to keep the UI in sync:

connection.on("EventReadStatusChange", (change) => {
    // change.eventId — the notification ID
    // change.isRead — new read state
    updateNotificationStatus(change.eventId, { isRead: change.isRead });
});

connection.on("EventDeletedStatusChange", (change) => {
    // change.eventId — the notification ID
    // change.isDeleted — new deleted state
    updateNotificationStatus(change.eventId, { isDeleted: change.isDeleted });
});

Delivery Rules

A notification is delivered to a client only when both conditions are true:

  1. The client has joined the target audience — automatic for Global, User, and Connection; requires Subscribe for Tenant and Group.
  2. The client has registered a handler for the notification type.

If the client is in the audience but has no handler registered, the notification is delivered but silently ignored.

For details on joining and leaving audiences, see Connection Management.