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.
When Chat sends a push
Section titled “When Chat sends a push”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:
- 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.
- Still a member. Chat checks again at send time that the candidate is an active member.
- Offline only. Chat drops candidates who are online, meaning their app has sent a heartbeat recently. See Presence.
- 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.
The notification
Section titled “The notification”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.
How the service worker uses it
Section titled “How the service worker uses it”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:
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.
Register a device
Section titled “Register a device”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.
- Register your service worker.
- Fetch the VAPID public key.
- Call
pushManager.subscribe()with that key asapplicationServerKey. - Send the resulting subscription to Chat.
Register request
Section titled “Register request”POST /api/v1/tenants/{tenant_id}/notifications/push/devices
chat:accessRegistration 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.
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.
Unregister request
Section titled “Unregister request”DELETE /api/v1/tenants/{tenant_id}/notifications/push/devices
chat:accessUnregisterPushDeviceDto:
| Field | Type | Required | Meaning |
|---|---|---|---|
token |
string | yes | For a browser, the subscription’s endpoint. For a native device, its token. |
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 the VAPID public key
Section titled “Get the VAPID public key”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.
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.
Limits to know
Section titled “Limits to know”- 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.