Skip to main content

Notifications GraphQL API

Real-time notification system with queries, mutations, and WebSocket subscriptions.

Notification Targeting​

Notifications support three targeting modes:

ModeConditionAudience
DirecttargetUserId is setSingle user
Role-basedtargetUserId is null, targetRoles is setAll users whose groups grant any of the specified roles
BroadcastBoth targetUserId and targetRoles are nullAll active users in the organization

targetUserId and targetRoles are mutually exclusive — setting both is a validation error. targetRoles must not be an empty list.

Read Status​

Read status (isRead, readAt) is tracked per user via a UserNotification junction table. UserNotification rows are created lazily — only when a user marks a notification as read. Until then, the notification appears as unread.

When a notification includes both entityType and entityId, the web UI attempts to open the matching app route as a dialog. It builds a path in the form entityType/entityId, checks the matched route's permission, permissions, or requiredPermissions, and passes matched route parameters plus organizationId and currentUser into the dialog component. If no route exists or the user lacks permission, the UI shows an error instead of opening the item.

entityId is a String, not an Int — it needs to hold any entity's id, including a GUID such as an agent session's agentSessionId, not just an integer primary key. See Breaking change below.

Queries​

getNotification​

Fetch a single notification for the current user.

query {
getNotification(organizationId: 1, notificationId: 42) {
notificationId
title
message
type
priority
targetUserId
targetRoles
entityType
entityId
isRead
readAt
created
}
}

Parameters:

ParameterTypeRequiredDescription
organizationIdIntYesOrganization scope
notificationIdIntYesNotification to fetch

Returns null if the notification doesn't exist or isn't visible to the current user (see Notification Targeting).

getNotifications​

Paginated list of notifications for the current user. Supports filtering, searching, and sorting.

query {
getNotifications(organizationId: 1, filter: "isRead:false", orderBy: "-created", skip: 0, take: 20) {
items {
notificationId
title
message
type
priority
targetRoles
isRead
created
}
totalCount
}
}

Parameters:

ParameterTypeRequiredDescription
organizationIdIntYesOrganization scope
filterStringNoLucene filter syntax (e.g. isRead:false, type:Alert)
searchStringNoFull-text search
orderByStringNoSort field (default: -created = newest first)

getUnreadNotificationCount​

Returns the count of unread notifications for the current user.

query {
getUnreadNotificationCount(organizationId: 1)
}

Mutations​

createNotification​

Create a new notification. Targeting is determined by targetUserId and targetRoles — see Notification Targeting.

mutation {
createNotification(organizationId: 1, values: {
title: "Order #1234 shipped"
message: "Your order has been picked up by the carrier."
type: ORDER_UPDATE
priority: NORMAL
entityType: "Order"
entityId: "1234"
}) {
notificationId
title
created
}
}

Input fields (CreateNotificationCommandValues):

FieldTypeRequiredDefaultDescription
titleStringYes—Notification title
messageStringNo—Notification body (supports Markdown)
typeNotificationTypeYes—System, OrderUpdate, TaskAssignment, Alert, Info
priorityNotificationPriorityNoNormalLow, Normal, High, Urgent
targetUserIdStringNo—Target a specific user (mutually exclusive with targetRoles)
targetRoles[String]No—Target users by role names (mutually exclusive with targetUserId; must not be empty if provided)
entityTypeStringNo—Linked entity type name
entityIdStringNo—Linked entity ID. Any entity's id as a string — an integer primary key ("1234") or a GUID, such as an agent session's agentSessionId.
expiresAtDateTimeNo—Optional expiration

Breaking change: entityId is a String​

entityId (on notification and on createNotification's values) changed from Int to String, so a notification can link to an entity whose id isn't an integer — such as entityType: "AgentSession", whose tool-approval notification carries a GUID entityId. Clients that parsed entityId as a number, or sent an integer literal for it, need to switch to plain string handling.

markNotificationRead​

Mark a single notification as read for the current user.

mutation {
markNotificationRead(organizationId: 1, notificationId: 42)
}

Returns true on success.

markAllNotificationsRead​

Mark all notifications as read for the current user. Returns the count of notifications marked.

mutation {
markAllNotificationsRead(organizationId: 1)
}

deleteNotification​

Delete a notification (removes for all users).

mutation {
deleteNotification(organizationId: 1, notificationId: 42) {
deletedCount
id
}
}

Subscriptions​

onNotificationReceived​

Real-time WebSocket subscription for new notifications. Uses PostgreSQL NOTIFY/LISTEN under the hood.

subscription {
onNotificationReceived(organizationId: 1, userId: "user-abc-123") {
notificationId
title
message
type
priority
entityType
entityId
isRead
created
createdBy
}
}

Topic format: {organizationId}_{userId}_notifications

WebSocket Authentication​

GraphQL WebSocket subscriptions authenticate during connection_init. Send the access token in connection parameters as either a raw token or a Bearer header value:

{ "Authorization": "Bearer <access-token>" }

Connections without a token, with an empty token, or with a token that fails OpenIddict validation are rejected. The server also enforces a 10-second initialization timeout for clients that open a socket but never send connection_init.

For notification subscriptions, the userId argument must exactly match the authenticated user ID. Subscribing to another user's notification topic returns a FORBIDDEN GraphQL error.

The subscription fires whenever a new notification is created that targets the specified user (either directly or via broadcast).

Frontend Integration​

The frontend uses the useNotifications React hook which:

  1. Fetches notifications via GetNotifications query (RTK Query)
  2. Fetches unread count via GetUnreadNotificationCount query
  3. Subscribes to onNotificationReceived via graphql-ws WebSocket client
  4. Shows toast notifications for real-time updates
  5. Provides markAsRead, markAllAsRead, and deleteNotification actions

The NotificationsDropdown component in the navbar renders the notification bell icon with unread badge and a dropdown panel with the notification list. Message content is rendered as Markdown.

Mobile push device registration​

Native iOS and Android builds register an Expo push token after both authentication and organization selection are available. Registration is repeated when the selected organization changes or Expo rotates the token, while unchanged token/organization pairs are deduplicated. Push permission denial and token-registration failures do not block login.

mutation RegisterUserDevice($input: RegisterUserDeviceCommandInput!) {
registerUserDevice(input: $input) {
userDeviceId
token
platform
appId
appVersion
}
}

mutation UnregisterUserDevice($input: UnregisterUserDeviceCommandInput!) {
unregisterUserDevice(input: $input)
}

Registration values include the Expo token, Ios or Android platform, white-label application ID, and app version, scoped by organizationId. Normal logout unregisters the token before clearing the authenticated session so shared driver devices do not continue receiving the previous user's notifications. If token refresh has already invalidated the session, logout skips the network unregister to avoid a refresh deadlock. Unregister waits at most five seconds and always clears the local registration state.