Push Notifications — client guide¶
Partly out of date
Rendered from ProjectInfo/client/push-notifications.md on docs/encryption-modes-spec. Its protocol content is still useful, but parts of it describe an older client or an earlier API. Where it disagrees with the code, the code wins — see the spec errata.
The design, threat model and self-hosting trade are in
../push-notifications.md. This page is the
client's checklist. Full request/response detail: Server/docs/api.md §17.
Earlier revisions of this page described
POST /api/device-tokensand a push body of"Message from {senderUsername}". Neither exists: the endpoint was never routed, and a sender name in a push hands Google a log of who messages whom. Do not reintroduce either.
The model¶
The server never sends content. Every push is the same constant wake,
{"v":"1","t":"wake"} — no sender, channel, server or count. On a wake:
POST /api/push/drain— returns and clears this device's queue, one entry per channel:channel_id,server_id(null for DMs),is_dm,message_count,first_message_id,last_message_id.- Fetch each channel's messages from
first_message_idonwards through the ordinary endpoints, decrypt them with the keys the device already holds. - Decide, locally:
- DM or group DM → notify.
- Server channel → notify only if it mentions you (
<@your-id>) or@everyone. The server cannot see mentions — they are in the ciphertext — so it wakes for every unmuted message and the client filters. - Your own message, or a muted channel/server → nothing.
- Draw the notification on the device. Decrypted text never leaves it.
Only one process may decrypt at a time: a DM decrypt advances the Double Ratchet. If the app is running, hand the wake to it rather than decrypting in a second process.
Registering¶
GET /api/push/config → {"fcm": bool, "unifiedpush": true}
PUT /api/push/registration
{ "transport": "fcm", "token": "<FCM registration token>" }
{ "transport": "unifiedpush", "token": "<endpoint URL>",
"p256dh": "<base64url P-256 point>", "auth": "<base64url 16 bytes>" }
→ 200
DELETE /api/push/registration → 204
All scoped to the calling device by the token's device id. One registration per device; a new PUT replaces the old one, so switching transport is one call.
- Choose UnifiedPush when a distributor is installed; else FCM if the build
has Firebase config and
fcmis true; else no push, and tell the user why (on a self-hosted instance,fcmis normally false — they need a UnifiedPush distributor). - Send the UnifiedPush connector's RFC 8291 keys. With them the distributor sees only ciphertext.
- Re-register on every app start, sign-in and token/endpoint change.
- On logout,
DELETE /api/push/registrationbefore discarding the access token.
Lifecycle the server enforces¶
A registration is deleted when the transport reports it dead, after repeated failures, when its device is revoked, when the device has no live session (swept every 10 minutes), or after 90 days without a re-registration. A muted channel or server never wakes the device.
Platforms¶
- Android: implemented in
Client-Flutter/lib/features/push/. See that directory. - Web: no push. The open tab has the gateway.
- iOS / desktop: not built. APNs support was removed from the worker with the unreachable endpoint; an iOS client would need it re-added as another content-free transport.