When to use this module

  • After background task completes, frontend needs real-time updates.
  • To show unread badge count to users.
  • User reads a notification (mark as read).

Endpoints

MethodRouteAuthDescription
GET/api/v1/notification?page=1&pageSize=20🔒 BearerPaginated list
GET/api/v1/notification/unread-count🔒 BearerCount for badge
PUT/api/v1/notification/{id}/read🔒 BearerMark one as read
PUT/api/v1/notification/mark-all-read🔒 BearerMark all as read

Notification Delivery Flow

Worker completes task (e.g., WhatsApp message sent) │ ▼ IAppNotificationService.CreateAndPushAsync(firmId, userId, module, title, message) │ ├──→ DB: INSERT into Notifications table │ └──→ WorkerSignalRClient → API NotificationHub → Frontend browser │ ▼ Toast/bell update
GET

/api/v1/notification

Response

JSON
{
  "isSuccess": true,
  "data": {
    "items": [
      {
        "id": "guid",
        "title": "Templates Synced",
        "message": "12 templates synced from Meta successfully",
        "module": "Template",
        "status": "Unread",
        "createdAt": "2026-05-26T10:00:00Z"
      }
    ],
    "totalCount": 5,
    "page": 1,
    "pageSize": 20
  }
}
GET

/api/v1/notification/unread-count

Response

JSON
{ "isSuccess": true, "data": 3, "message": "" }

Notification Entity

PropertyTypeDescription
FirmIdGuidOwning firm
UserIdGuid?Target user (null = firm-wide)
ModuleenumSource module (WhatsApp, Contact, Workflow, Template, System)
ModuleIdGuid?Related entity ID (deep-link)
Titlestring(150)Notification title
Messagestring(500)Body text
MetadataJsonDocument?JSONB flexible per-module data
StatusenumUnread / Read
ReadAtDateTime?When marked read

Client JS Example

JavaScript
const connection = new signalR.HubConnectionBuilder()
  .withUrl('/notificationhub', { accessTokenFactory: () => getToken() })
  .build();

connection.on('ReceiveNotification', (notification) => {
  showToast(notification.title, notification.message);
  updateUnreadBadge();
});

await connection.start();