Notifications#

These endpoints back the Notifications and Sounds settings screen. All require authentication.

Preferences are two-layer:

  • Account-level (NotifPrefs) — synced across every device the user is signed in on. Covers per-category defaults (private / group chats), the global message-preview switch, and the badge-counter rules.
  • Per-device (DeviceNotifPref) — scoped to the current session (the s_… token's device). The enabled field is the master kill switch for that one device; in_app.* describes in-app banner behaviour and is client-enforced (the server stores and echoes it but never consults it when building an APNs payload).

Per-conversation exceptions are not here — they stay in the existing mute system (POST /api/setChatNotifications).

Everything defaults to on, so a brand-new user/device receives notifications out of the box without writing any prefs.

POST /api/getNotificationPrefs#

Returns the account-level prefs plus this device's prefs (resolved from the caller's session). Takes no body.

curl -X POST http://localhost:4000/api/getNotificationPrefs \
  -H "authorization: Bearer s_…"
{
  "ok": true,
  "result": {
    "account": {
      "private_chats": { "enabled": true, "preview": true, "sound": true },
      "group_chats":   { "enabled": true, "preview": true, "sound": true },
      "message_preview": true,
      "badge": { "enabled": true, "include_groups": true, "count_messages": true }
    },
    "device": {
      "enabled": true,
      "in_app": { "sounds": true, "vibrate": true, "preview": true }
    },
    "vapid_public_key": "BLgvK3lCr09ssJ6dsmxX9kFD…"
  }
}

vapid_public_key is present only when the server has Web Push configured (the VAPID_* env vars). The web client passes it to PushManager.subscribe({ applicationServerKey }); when it's absent the client hides the "enable browser push" affordance.

POST /api/setNotificationPrefs#

Replace the account-level prefs (send the full NotifPrefs object). Returns the stored account.

curl -X POST http://localhost:4000/api/setNotificationPrefs \
  -H "authorization: Bearer s_…" -H "content-type: application/json" \
  -d '{
    "private_chats": { "enabled": false, "preview": true, "sound": true },
    "group_chats":   { "enabled": true,  "preview": true, "sound": true },
    "message_preview": true,
    "badge": { "enabled": true, "include_groups": true, "count_messages": true }
  }'

POST /api/setDeviceNotificationPrefs#

Replace this device's prefs (the current session). Returns the stored device. Fails with 400 invalid_argument if the caller has no session (e.g. a dev: or bot mb_ token).

curl -X POST http://localhost:4000/api/setDeviceNotificationPrefs \
  -H "authorization: Bearer s_…" -H "content-type: application/json" \
  -d '{ "enabled": true, "in_app": { "sounds": true, "vibrate": false, "preview": true } }'

Push delivery & device tokens#

POST /api/registerPushToken ({ token, platform? }) registers a push endpoint and binds it to the caller's current session. Revoking that session (terminateSession / terminateOtherSessions) drops its token, so that device stops receiving push. Tokens registered with a session-less token (dev: / mb_) are stored unbound and still deliver, just without a per-device switch.

platform selects the transport:

  • ios (or omitted) — token is the APNs device token; delivered over APNs.
  • webtoken is the browser's PushSubscription serialized as JSON ({ "endpoint": …, "keys": { "p256dh": …, "auth": … } }); delivered as an encrypted Web Push (RFC 8291 aes128gcm + VAPID). The payload is { "title", "body" }, which the service worker reads off event.data.json().
# web push registration (token is the stringified subscription)
curl -X POST http://localhost:4000/api/registerPushToken \
  -H "authorization: Bearer s_…" -H "content-type: application/json" \
  -d '{ "platform": "web", "token": "{\"endpoint\":\"https://…\",\"keys\":{\"p256dh\":\"…\",\"auth\":\"…\"}}" }'

A dead web subscription (the push service returns 404/410 Gone) is pruned automatically on the next send. The server needs the VAPID_PRIVATE_PEM_BASE64, VAPID_PUBLIC_KEY, and VAPID_SUBJECT env vars; without them web pushes are silently skipped (APNs is unaffected).

When a human recipient is offline (no live WebSocket), the server pushes to each of the recipient's registered endpoints (dispatched by platform) only if all of the following hold:

  1. the recipient's category for that conversation (private_chats for a DM, group_chats for a group) has enabled: true;
  2. the conversation is not muted for the recipient (is_muted);
  3. the recipient's device pref for the token's session has enabled: true (unbound/legacy tokens are treated as on).

The notification body shows the message text only when both the global message_preview and that category's preview are on; otherwise it falls back to a generic "New message". This gate is shared by APNs and Web Push.