Cloud Messaging
Push notifications for your Flutter and React Native users via FCM — device tokens come from a native module, so this is not available in the browser; the web uses Web Push through your own service worker. Devices register themselves from your app; your backend or a Koolbase Function sends.
How it works
- Your app obtains an FCM token using
firebase_messaging - The SDK registers the token with Koolbase — tied to a stable device ID, using the publishable key
- Your backend (or a Koolbase Function, or the dashboard broadcast UI) sends with a secret API key
- Koolbase authenticates to FCM's HTTP v1 API and delivers to Android and iOS
Why sends require a secret key
kb_live_ key (write scope) that never leaves your backend. Send requests carrying a publishable key answer 401.Setup
Delivery goes through FCM's HTTP v1 API, which authenticates with short-lived OAuth tokens minted from a Firebase service account. Koolbase handles the minting and caching; you provide the service account once.
- Firebase Console → your project → ⚙️ Project settings → Service accounts → Generate new private key. A JSON file downloads.
- Koolbase dashboard → your project → Secrets → create a secret named exactly
FCM_SERVICE_ACCOUNT. - Paste the entire JSON file as the value — from the opening
{to the closing}, unmodified. Don't extract fields, don't strip the\ns inside the private key.
Upgrading from FCM_SERVER_KEY
FCM_SERVER_KEY. Google removed the legacy API in June 2024, and that path no longer exists in Koolbase. Replace it with the FCM_SERVICE_ACCOUNT secret above — the old secret is ignored and can be deleted.Rotation is instant
iOS requires an APNs key — or sends silently don't arrive
FCM delivers to iOS by forwarding to Apple's APNs, so your Firebase project needs an APNs authentication key. Without it, sends to iOS devices succeed at the API (200) but never reach the device — the most confusing failure mode there is, because nothing errors.
In the Apple Developer account → Keys → create a key with the Apple Push Notifications service (APNs) capability, and download the .p8 file. Then in Firebase Console → Project settings → Cloud Messaging → your iOS app → upload it under APNs Authentication Key (you'll also need your Key ID and Team ID). Android needs none of this.
Use an APNs key (.p8), not a certificate — and why iOS may show sent:1 but no banner
Two iOS-only failure modes both look like a successful send (sent: 1) with nothing on screen:
Wrong environment. A debug build registers a sandbox APNs token; a release/TestFlight build registers a production one. An APNs Auth Key (.p8) works for both, so FCM routes automatically — a production certificate won't deliver to a sandbox token. Use the .p8 key (above), not a certificate, and this resolves itself.
Silent payload. Koolbase sets an explicit APNs alert (title + body + sound) so iOS presents the banner automatically — you don't need to build a local notification for a standard title/body send. If you're testing on a debug build and see no banner, it's almost always the environment/key mismatch above, not the payload.
Flutter SDK — register devices
Install firebase_messaging, then register the FCM token after initialization. Pass the signed-in user's id to target notifications per user later. Messaging is on by default; setting messagingEnabled: false in KoolbaseConfig disables the client entirely.
// After Firebase is initialized
final fcmToken = await FirebaseMessaging.instance.getToken();
if (fcmToken != null) {
await Koolbase.messaging.registerToken(
token: fcmToken,
platform: 'android', // or 'ios'
userId: currentUser?.id,
);
}
// Tokens rotate — keep the registration fresh:
FirebaseMessaging.instance.onTokenRefresh.listen((newToken) {
Koolbase.messaging.registerToken(token: newToken, platform: 'android');
});Registration is idempotent per device: re-registering updates the stored token rather than duplicating it.
React Native SDK — register devices
import messaging from '@react-native-firebase/messaging';
const fcmToken = await messaging().getToken();
await Koolbase.messaging.registerToken({
token: fcmToken,
platform: 'android', // or 'ios'
});
messaging().onTokenRefresh(async (newToken) => {
await Koolbase.messaging.registerToken({
token: newToken,
platform: 'android',
});
});Send from your backend
Sends are a single POST with a secret key. project_id is required in the body — keys are organization-scoped, and the project must belong to the key's organization.
curl -X POST https://api.koolbase.com/v1/messaging/send \
-H "Content-Type: application/json" \
-H "Authorization: Bearer kb_live_..." \
--data '{
"project_id": "your-project-id",
"token": "the-device-fcm-token",
"title": "Order shipped",
"body": "Your order #1042 is on its way",
"data": { "order_id": "1042" }
}'To notify a user rather than a specific device, send user_id instead of token (exactly one is required). Koolbase resolves all of that user's registered devices, sends to each, and prunes dead tokens — so your backend or Function never handles raw device tokens:
curl -X POST https://api.koolbase.com/v1/messaging/send \
-H "Content-Type: application/json" \
-H "Authorization: Bearer kb_live_..." \
--data '{
"project_id": "your-project-id",
"user_id": "the-project-user-id",
"title": "Order shipped",
"body": "Your order #1042 is on its way"
}'
# Response: { "sent": 1, "failed": 0, "pruned": 0, "devices": 1 }A user with no registered devices is not an error
200 with devices: 0 — not an error. A cron Function notifying many users won't crash on the ones who can't receive.Register tokens after login
registerToken before the user is signed in, the token is stored without an owner and user_id-targeted sends won't find it. Register (or re-register) once the user is authenticated.The recommended pattern for app-driven notifications is a Koolbase Function: a database trigger or invoked Function runs server-side with access to your secrets, so it can send without any credential ever reaching the client.
Koolbase.messaging.send() is removed
send() method authenticated with the publishable key. It is rejected by the API for the security reason above and removed from the SDK. Move sends to your backend or a Function.Responses and errors
200Delivered to FCM.400Caller error, named in the message: invalid FCM registration token, missing field, or missing/malformed FCM_SERVICE_ACCOUNT secret.401No usable credential — sends require a secret kb_live_ key; the publishable key is rejected by design.403insufficient_scope: the key is read-only. Sending requires write scope.404The project does not exist in this key’s organization. Cross-org requests answer not-found, never confirming what exists elsewhere.410The device token was valid once but is no longer registered (app uninstalled, token rotated). Koolbase has already deleted it — stop sending to it.Dead tokens are pruned automatically
410) and during broadcasts (reported in the pruned count).Dashboard broadcast
Send to all registered devices from the dashboard: project → Messaging → title, body, optional data → Broadcast. Broadcast authenticates with your dashboard session, not an API key.
- Total registered devices — Android and iOS count
- Sent today / Failed today — delivery stats
- Recent sends log — status per message
- Broadcast to all — one-click send to every registered token, with pruned count
What Koolbase does not handle
- Receiving messages in the app — use firebase_messaging for that
- Notification permissions — request via the platform SDK
- Background message handlers — configure in firebase_messaging
- Web push notifications — mobile only in v1
API reference
| Surface | Description |
|---|---|
Koolbase.messaging.registerToken(token, platform, userId?) | Register an FCM device token (publishable key, from the app). |
POST /v1/messaging/send | Send to one device (secret kb_live_ key, from your backend or a Function). |
Dashboard → Messaging → Broadcast | Send to all registered devices for the project. |