Skip to content

Push payloads

Chat sends a web push notification to a user’s browser when a new message arrives and the user is not online. This page defines what the notification contains, when Chat sends it, what the service worker does with it and how a browser registers to receive it. For the task, read Web push; for the model, read Push notifications.

Chat sends push only for a new message. Edits, deletions, reactions, typing and membership changes never produce one. For each new message Chat picks recipients like this:

  1. Candidates. In a DIRECT or GROUP, every member except the sender. In a CHANNEL, only the members the message mentions, because a channel can have too many members to notify.
  2. Still a member. Chat checks again at send time that the candidate is an active member.
  3. Offline only. Chat drops candidates who are online, meaning their app has sent a heartbeat recently. See Presence.
  4. Every device. The notification goes to every device the user registered.

Presence is per account, not per conversation. A user who is online and looking at another conversation gets no push for this one. They get the realtime event instead; see Realtime events.

Delivery is best effort. If the browser’s push service reports that a subscription no longer exists, Chat stops sending to it. A failed push never fails the message send.

Chat sends the browser a JSON document with two parts: what to show and data for your app.

Field Type Meaning
notification.title string The sender’s display name. If the message has no sender, such as a SYSTEM message, the conversation’s name; if that is missing too, New message.
notification.body string The first 200 characters of the message, with … added when it is longer. Sent an attachment when the message has no text.
data.tenant_id string The tenant.
data.application_id string The application.
data.conversation_id string The conversation the message was sent to.
data.message_id string The new message.
{
"notification": {
"title": "Ana Coach",
"body": "Squad, 6:30 at the track. Bring water."
},
"data": {
"tenant_id": "fitness",
"application_id": "fitness-app",
"conversation_id": "3f7c2a9e-5b1d-4e8a-9c63-1a2b3c4d5e6f",
"message_id": "7a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d"
}
}

The data fields are always present. They are the ids you need to open the right conversation; the message text is not in data, so fetch it with Messages_list if you need more than the preview.

The browser runs your service worker, not Chat’s code. The ccs-chat-notifications-client library ships a template, push-sw-template.js, that handles two events. Copy it to your site’s static root as push-sw.js, so its scope covers your whole app:

Terminal window
cp node_modules/@central-code-station/ccs-chat-notifications-client/push-sw-template.js public/push-sw.js
Event What the template does
push Does nothing when the push has no data. Otherwise parses it as JSON and shows a notification with notification.title (New message if missing) and notification.body, and keeps data on the notification.
notificationclick Closes the notification, focuses a tab of your app that is already open, or opens / when none is.

The template does not deep-link. It does keep data on the notification, so you can open the conversation yourself by changing the click handler to read event.notification.data:

const { conversation_id } = event.notification.data ?? {};
return self.clients.openWindow(`/chat/${conversation_id}`);

Browsers require that you show a notification for every push you receive. Keep the push handler that shows one.

A browser registers after the user allows notifications. The first permission request must come from a real click, because browsers silently withhold the prompt otherwise. ChatPushClient performs the steps below; this section is for apps that make the calls themselves.

  1. Register your service worker.
  2. Fetch the VAPID public key.
  3. Call pushManager.subscribe() with that key as applicationServerKey.
  4. Send the resulting subscription to Chat.

POST /api/v1/tenants/{tenant_id}/notifications/push/devices

User scope: chat:access

Registration belongs to the tenant, not to one application. One registration serves every application of the tenant, and a user can register several devices. Operation: Notifications_registerDevice.

RegisterPushDeviceDto:

Field Type Required Meaning
platform web, fcm, apns yes The kind of device. Use web for a browser.
subscription object for web What pushManager.subscribe() returned.
subscription.endpoint string yes, in subscription The push service address of this browser.
subscription.keys.p256dh string yes, in subscription The browser’s public encryption key.
subscription.keys.auth string yes, in subscription The browser’s authentication secret.
token string for fcm, apns The native device token.

The fcm and apns values accept a native device token. No Chat library registers native devices, and the rest of this page covers web push only.

Terminal window
curl -X POST "$CHAT_API_URL/tenants/fitness/notifications/push/devices" \
-H "Authorization: Bearer $CHAT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"platform": "web",
"subscription": {
"endpoint": "https://push.example/send/abc",
"keys": { "p256dh": "BN…", "auth": "k8…" }
}
}'

The response is 204 No Content. Registering the same subscription again is safe. Check the response status: a failed request leaves the browser subscribed but unknown to Chat, so nothing arrives.

DELETE /api/v1/tenants/{tenant_id}/notifications/push/devices

User scope: chat:access

UnregisterPushDeviceDto:

Field Type Required Meaning
token string yes For a browser, the subscription’s endpoint. For a native device, its token.
Terminal window
curl -X DELETE "$CHAT_API_URL/tenants/fitness/notifications/push/devices" \
-H "Authorization: Bearer $CHAT_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "token": "https://push.example/send/abc" }'

The response is 204 No Content. Call it when the user signs out or turns notifications off. Operation: Notifications_unregisterDevice.

Both routes are for users only. A backend client token is refused with principal_not_allowed.

GET /api/v1/notifications/vapid-public-key

This route is public: send no token. The key is not a secret, and browsers on any origin can read it. Operation: Notifications_getVapidPublicKey.

Terminal window
curl "$CHAT_API_URL/notifications/vapid-public-key"
{
"public_key": "BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U"
}

Pass public_key to pushManager.subscribe() as applicationServerKey. An empty key means web push is not available in that environment.

  • The browser must support service workers and the Push API. React Native is not supported by the notifications client.
  • A user who denied notification permission cannot subscribe. pushManager.subscribe() fails in the browser, so tell the user why nothing happened.
  • Presence is account-level, so push depends on whether the user is online anywhere, not on which conversation is open.