Skip to main content
Push Notifications lets your app deliver native OS push notifications to users on iOS (via APNs), Android (via FCM), and the browser (via Web Push) — all from a single server-side call. Sublay stores each user’s registered devices, fans a message out to every platform, and prunes stale tokens automatically.
Requires the push bundle. Install it from your project’s Database page in the dashboard before using any push feature. See Bundles for details.

How It Works

  1. Configure credentials in the dashboard — paste your APNs .p8 key, FCM service account JSON, or enable Web Push (keypair generated server-side).
  2. Register devices — your client app calls register() from usePushRegistration at a moment that makes sense for your UX (settings screen, first-run prompt, etc.). The SDK handles permission, token retrieval, and server registration.
  3. Send notifications — your backend calls sublay.push.send({ userIds, title, body, data }). Sublay fans the message out to every registered device for those users across all platforms.

Dashboard Setup

Installing the bundle

Open Database → Bundles in the dashboard and install push. Once provisioning completes, the Push Notifications settings page becomes active.

Configuring providers

Go to Settings → Push Notifications in the dashboard. Configure each platform you want to support: APNs (iOS) FCM (Android) Upload or paste your Firebase service account JSON. This is the file downloaded from the Firebase console under Project Settings → Service Accounts → Generate new private key. Web Push No credentials to paste — click Enable. Sublay generates a VAPID EC keypair server-side and stores the private key encrypted. Only the public key is returned and displayed; it is also available via the unauthenticated GET /vapid-public-key endpoint for use in your service worker.

Automatic push settings

Sublay can fan a push out automatically whenever an in-app notification is created (a new comment, reply, mention, reaction, follow, connection, RSVP-event change, space-membership approval) or a chat message is sent — no push.send() call required. The Settings → Push Notifications page lets you tune what those automatic pushes look like per event type: These settings affect only the automatic path. The manual push.send() call already accepts title, sound, channelId, priority, and everything else directly. See Automatic Pushes below for the full delivery-config, title-template, and tap-routing contract.

Test-send UI

The Push Notifications settings page includes a Send Test Push panel for firing a one-off notification at a single device — the fastest way to verify a provider’s credentials before wiring up the SDK. Pick a platform, paste a device token (iOS/Android) or a web push subscription, set a title and body, and click Send Test Push. The catch is getting the token/subscription to paste in. It isn’t shown anywhere in the dashboard — it’s produced on the receiving device. Here’s how to obtain each.
“Test push sent successfully” verifies credentials, not delivery. A success result means the provider (APNs / FCM / Web Push) accepted the message — your keys are valid. It does not mean a notification appeared on screen. Rendering is a separate step that depends on the receiving device (a web service worker’s push handler, and the OS notification settings). If the send is green but nothing shows, see Troubleshooting below — the problem is almost never the credentials at that point.

Web: generating a Subscription JSON

The Subscription JSON is created by a browser subscribing to the Push API. You generate it from the DevTools console of a page that (a) is served over HTTPS or localhost and (b) has a service worker registered (await navigator.serviceWorker.ready hangs forever otherwise).
1

Copy the VAPID public key

In the dashboard, go to Settings → Push Notifications → Push Providers. The Web Push row shows your project’s VAPID public key with a copy button. (It’s also available programmatically from the unauthenticated GET /vapid-public-key endpoint.) The subscription must be created with this exact key, or the send fails.
2

Run the subscribe snippet

Open DevTools → Console on your service-worker-enabled page and run:
The browser prompts for notification permission — click Allow. The snippet prints an object like:
3

Paste it into the panel

Copy the printed object into Subscription JSON, set Platform to Web (Web Push), and send. (expirationTime is harmless if included; only endpoint and keys are read.)
This manual console flow is a debugging convenience for the test panel. In a real app you never do this by hand — the SDK’s usePushRegistration creates and registers the subscription for you. See the Web section of the SDK reference for service-worker setup.

iOS / Android: obtaining a device token

Native device tokens come from the OS (APNs / FCM), surfaced by the SDK’s push-token adapter. To capture one for a test, wrap the built-in adapter so it logs the identifier it produces, then register as usual:
Run the app on a real device (simulators/emulators don’t receive push on iOS), trigger registration, copy the token from the logs, then paste it into the panel with Platform set to iOS or Android.
APNs sandbox vs. production. A development/debug iOS build talks to the APNs sandbox gateway; a TestFlight/App Store build talks to production. The Production toggle on your APNs provider config must match the build the token came from, or the send is rejected. This is the most common iOS test-push failure.

Troubleshooting a test push

“Sent successfully” only means Web Push accepted the message. Rendering happens in your service worker’s push handler — Sublay ships no service worker. Check, in order:
  1. Does your SW render pushes? Its push handler must call registration.showNotification(...). Isolate the rendering step by running this in the page console — it calls showNotification directly, bypassing the network:
    If this shows nothing, the problem is OS-level (next point), not your code. If it shows but real pushes don’t, your push handler is missing or not calling showNotification.
  2. OS notification settings. Even with Notification.permission === "granted", the OS can suppress everything. On macOS: System Settings → Notifications → [your browser] must be allowed (alert style not “None”), and Focus / Do Not Disturb must be off. This silently swallows notifications and is the single most common cause of “green but nothing.”
  3. DevTools “Push” button throws a JSON error. The DevTools Application → Service Workers “Push” tool sends plain text; a handler that calls event.data.json() will throw on it. That’s a test-tool artifact — real Sublay pushes are JSON. Test with a real send instead.
The push service returned HTTP 410 (Gone). Usual causes:
  • VAPID key mismatch — the subscription was created with a different public key than the project’s. Re-copy the key from Settings → Push Providers and re-subscribe. Chrome caches the old subscription, so unsubscribe() first (the snippet above already does).
  • Truncated endpoint — make sure you pasted the full endpoint, not an abbreviated https://.../... copy.
  • Stale subscription — regenerate a fresh one if it was minted a while ago.
  • Test on a real device, not a simulator/emulator.
  • Confirm the APNs Production toggle matches the build (sandbox for dev builds, production for TestFlight/App Store).
  • Verify the provider credentials: APNs bundleId must match the app exactly; FCM service account must belong to the same Firebase project as the app’s google-services.json / config.
  • Re-copy the token — device tokens rotate and can go stale.

Client SDK — Registering Devices

Use usePushRegistration with the adapter for your platform. Call register() in response to a deliberate user action — not on mount — because requesting OS push permission is a one-shot prompt that users cannot undo.
The web adapter requires a service worker. Register one before calling register(). See SDK Reference → Push Notifications for per-platform setup and usePushRegistration for the full hook API.

Server SDK — Sending Notifications

Call client.push.send() from your backend whenever you want to notify users:
A single call fans the notification out to all platforms. Capped at 100 user IDs per request. See Node SDK — Push Notifications for the full module reference.
A common pattern is to bridge Sublay’s in-app notifications to push: subscribe to the notification.created webhook and call push.send() when it fires. See Webhooks → Push Notification Bridge.

Rich Notification Payloads

Beyond title, body, and data, push.send() accepts optional fields for sound, badges, images, grouping, priority, and lifetime. Each maps to the native APNs / FCM / Web Push capability and is silently ignored on platforms that don’t support it — so you can set subtitle (iOS-only) and channelId (Android-only) in the same call without branching.

Client-side requirements

Three of these need setup in your app — Sublay forwards the field but cannot do this part for you:
On Android 8+ the notification channel owns the sound, importance, and vibration — the payload can’t override it. Create the channel once in your app (with the bundled sound) and pass its id as channelId:
Then push.send({ ..., channelId: "messages", sound: "notification.wav" }). The sound field alone is only a pre-Android-8 fallback. Sublay does not create channels for you.
iOS does not download remote images from the payload on its own. To show imageUrl on iOS, add a Notification Service Extension to your app that reads the URL from the notification payload, downloads it, and attaches it. Sublay sets mutable-content automatically whenever imageUrl is present so the extension is allowed to run. Android and Web render imageUrl with no extra work.
Sublay ships no service worker — your app’s own SW renders web notifications. The server forwards the web-renderable fields (sound, image, tag) in the push JSON; your push event handler decides how to display them via registration.showNotification(...).

Automatic Pushes

Whenever Sublay writes an in-app notification (a new comment, reply, mention, reaction, milestone, follow, connection request/accept, RSVP-event invite/update/cancel, or space-membership approval) or a chat message is sent, it can fan a native push out to the recipient’s registered devices automatically — you don’t call push.send() for these. What each automatic push looks like is configured per project on the Settings → Push Notifications page.
Everything in this section applies to the automatic path only. The manual push.send() call takes title, body, data, sound, channelId, priority, and the rich fields directly and is unchanged.

Title & body templates

Each event type has a body template (shipped with sensible defaults) and an optional title template. Both are interpolated against the same per-event {variable} palette shown next to each row in the dashboard. The title template ships empty for every event type — an unset title renders as an empty string, which is the historical behavior. Set a title only for the events where you want one; leaving it blank is a no-op.

Delivery: sound, channel & priority

You can set a project-wide default delivery block and, optionally, a per-event override:
  • Default — a single { sound, channelId, priority } applied to every automatic push.
  • Override — a per-event-type { sound, channelId, priority }. Any field you set on the override wins for that event; any field you leave unset inherits the project default. Precedence is per-event override → project default → none.
These are the same three fields push.send() accepts, wired to the automatic path. The other rich fields (badge, imageUrl, tag, etc.) are configurable on the manual path only.

Tap-routing data

Every automatic push carries a flat data object of string identifiers so your app’s tap handler can deep-link to the right screen. All data values are flat top-level strings (FCM coerces data values with String(), so nested objects are not used). Two keys are on every automatic push: Beyond type and action, each push forwards every identifier form for each object the notification references, so you can route on whichever id your app uses (UUID, short id, foreign id, slug, or username). Per referenced object: Which of those apply depends on the event type:
Content and RSVP-event notifications also carry the parent space. A notification about an Entity or Comment (a “content notification”) or an RSVP-event notification additionally forwards the parent Space id set (spaceId, spaceShortId, spaceSlug) when the subject lives in a space, so you can route within a space. Don’t confuse the message event type or an RSVP Event (the data model) with the generic idea of a “notification event.”
These data keys are additive and non-breaking. Existing tap handlers that ignore unknown keys are unaffected — nothing that was present before was removed or renamed. A key whose underlying value is absent (e.g. a user with no foreignId, a space with no slug) is simply omitted rather than sent empty.
The chat-message path builds its data from the send context rather than notification metadata, so it always carries exactly { type: "message", action: "open-conversation", conversationId, messageId }.

Device Lifecycle

  • Re-registration: registering the same physical device again (same token or endpoint) updates the existing record instead of duplicating it.
  • Device reassignment: if the same device is registered by a different user (e.g. a shared device after logout/login), the record is reassigned to the new user.
  • Stale token cleanup: tokens or subscriptions permanently rejected by APNs, FCM, or Web Push during a send are automatically deleted — no separate cleanup pass is needed.
  • Explicit logout: call unregister() in your logout flow so the device stops receiving notifications after sign-out.

References

Everything related to push notifications across the docs:

SDK Reference — Push Notifications

Per-platform client setup for Expo, React Native, and Web

usePushRegistration

Request permission, register, and unregister the current user’s device

Node SDK — Push Notifications

The push.send() server module reference

Webhooks — Push Bridge

Forward notification.created events to push

API Endpoints

Register Device

POST /push-notifications/devices

Deregister Device

DELETE /push-notifications/devices

Send Push

POST /push-notifications/send

Get VAPID Public Key

GET /push-notifications/vapid-public-key