Documentation
Firebase Cloud Messaging
Native push via your Firebase project
Firebase Cloud Messaging (FCM) lets you send push notifications from your Firebase project and backend. WebToApp embeds the native SDK; it does not send messages or store device tokens. You cannot enable FCM and OneSignal in the same build. FCM builds also include native Firebase Analytics so Messaging Reports can show Android received/impressions, and Firebase In-App Messaging so Console campaigns can appear while the app is open. That is not the Integrations “Google Analytics” G- measurement ID (WebView GA4).
Prerequisites
- A Firebase project
- Android application ID:
com.yourcompany.yourapp - iOS bundle ID:
<your-bundle-id> - For iOS: an APNs authentication key uploaded to Firebase Console (not to WebToApp)
Step 1: Register apps in Firebase
- Open Firebase Console and create or select a project.
- Add an Android app with package name
com.yourcompany.yourapp. Downloadgoogle-services.json. - Add an iOS app with bundle ID
<your-bundle-id>. DownloadGoogleService-Info.plist. - iOS: enable the Push Notifications capability on your App ID in the Apple Developer Portal.
Step 1b: Upload an APNs key to Firebase (iOS)
iOS devices receive pushes through Apple. Firebase needs an APNs Authentication Keyto deliver those messages. Upload it in Firebase Console — not in WebToApp. The .p8 on the iOS tab is an App Store Connect API key for uploading builds; it is a different file.
- Open Apple Developer → Keys, click +, enable Apple Push Notifications service (APNs), register, and download the
.p8(once only). Copy the Key ID and your Team ID. One APNs key per Apple team is enough. - In Firebase Console → your project → the gear → Project settings → Cloud Messaging, under Apple app configuration, upload that APNs key and paste the Key ID and Team ID.
Your Firebase project ID (after you upload a config file): <your-firebase-project-id>
Step 2: Configure in WebToApp
- Open Integrations and unlock Firebase Cloud Messaging (9 credits, once per app).
- Turn FCM on. If OneSignal is on, you will be asked to confirm turning it off.
- Upload
google-services.jsonandGoogleService-Info.plist. - Pick an Android status-bar notification icon: a preset silhouette, or upload a custom PNG (transparent background). A full-color square shows as a white square.
- Request a new Android build and a new iOS build.
In-App Messaging (while the app is open)
FCM builds also include Firebase In-App Messaging. Create campaigns in Firebase Console → Engage → In-App Messaging (not Cloud Messaging). They appear as a card, modal, banner, or image while the app is in the foreground. No extra WebToApp unlock. Rebuild after enabling FCM.
- Triggers:
app_launch,on_foreground, or a custom event from your site. - JS
userId/userTagsalso set Analytics user id and user properties so you can target logged-in users or a plan tag. - Custom trigger:
{ type: 'analyticsEvent', name: 'checkout_start', params: {} } - Action URL on your website’s domain loads in the WebView. A different domain opens in an in-app browser tab (same rule as in-app links and push
openUrl). - Campaigns wait until splash and the first page load finish. If an opening interstitial is enabled, they also wait until that ad has shown or failed. They stay hidden while interstitial or rewarded ads are on screen.
Step 3: Identify users from your website
The same userId message used for OneSignal subscribes the device to an FCM topic so your backend can target that user (including multiple devices). WebToApp does not store tokens or user mappings — the native app tells your webapp who is subscribed so you can save that on your side.
window.FlutterWebView.postMessage(JSON.stringify({
type: 'userId',
userId: 'user_12345'
}));
window.FlutterWebView.postMessage(JSON.stringify({
type: 'userId',
userId: null
}));
window.FlutterWebView.postMessage(JSON.stringify({
type: 'userTags',
tags: { plan: 'premium' }
}));Topic name: wta_user_ plus a sanitized id (characters outside [a-zA-Z0-9-_.~%] become _). Prefer opaque IDs, not email addresses.
Read the subscription in your webapp
After FCM is ready, and again after every page load, the app sets window.FlutterFcm and dispatches a FlutterFcmToken event. Use either.
| Field | Meaning |
|---|---|
token | FCM registration token for this device (or null until registered, or after permission is denied) |
previousToken | The last token before a rotation or deny, or null |
userId | The id you posted, or null if logged out |
topic | Exact user topic, e.g. wta_user_user_12345 (null if logged out) |
broadcastTopic | Always wta_all — every device joins this |
tags | Key/value tags you sent via userTags |
tagTopics | Sanitized topic names for those tags |
permission | authorized, provisional, denied, or notDetermined |
alreadyOptedIn | true if the OS already had notification permission (no new prompt) |
platform | ios or android — send this to your mapping API so devices are not labeled from User-Agent |
function savePushMapping(detail) {
if (!detail.token || detail.permission === 'denied') {
const stale = detail.previousToken || detail.token;
if (stale) {
fetch('/api/push/fcm', {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ token: stale, remove: true })
});
}
return;
}
fetch('/api/push/fcm', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({
token: detail.token,
previousToken: detail.previousToken,
userId: detail.userId,
topic: detail.topic,
broadcastTopic: detail.broadcastTopic,
tags: detail.tags,
platform: detail.platform
})
});
}
window.addEventListener('FlutterFcmToken', (event) => {
savePushMapping(event.detail);
// After switching from OneSignal, re-send userId if the user is already logged in
if (!event.detail.userId && window.currentUserId) {
window.FlutterWebView.postMessage(JSON.stringify({
type: 'userId',
userId: window.currentUserId
}));
}
});
if (window.FlutterFcm) {
savePushMapping(window.FlutterFcm);
}
window.FlutterWebView.postMessage(JSON.stringify({ type: 'getFcmToken' }));Listen first, then read window.FlutterFcm, then call getFcmToken so you do not miss a payload that arrived before your script ran. To target a user, send to detail.topic (all of that user’s devices). To target one device, send to detail.token. On token refresh, send previousToken so your backend can delete the old row. If token is null or permission is denied, delete the stored mapping. Uninstall does not notify the WebView — when you send to a stored token and FCM returns unregistered / not-registered, delete that row.
Switching from OneSignal
Notification permission belongs to the app (bundle ID / package name), not to OneSignal or FCM. After you disable OneSignal, enable FCM, and users install the new build:
- Users who already allowed notifications are not asked again. The app registers with FCM and subscribes the device to
wta_allon first launch. - Users who never decided are prompted once (same as a new install). Users who denied are not prompted again; the app does not join
wta_alland emits a null token so your backend can delete the mapping. - OneSignal player IDs and tags do not transfer. The FCM token is new. Per-user topics require your site to post
userIdagain (on login, or when you receiveFlutterFcmTokenwith a nulluserIdwhile the user is already logged in).
Step 4: Send from your backend
Use FCM HTTP v1 against project <your-firebase-project-id>. Include a notification block so a system banner is shown, plus data.openUrl to open a page in the app.
POST https://fcm.googleapis.com/v1/projects/<project-id>/messages:send
{
"message": {
"topic": "wta_user_user_12345",
"notification": {
"title": "New order",
"body": "Tap to view"
},
"data": {
"openUrl": "https://yoursite.com/orders/42"
},
"fcm_options": {
"analytics_label": "targeted"
},
"android": {
"priority": "high",
"notification": { "channel_id": "webtoapp_push" },
"fcm_options": { "analytics_label": "targeted" }
},
"apns": {
"payload": { "aps": { "sound": "default" } },
"fcm_options": { "analytics_label": "targeted" }
}
}
}Data-only messages (no notification title/body) do not show a banner. In Firebase Console, add Custom data key openUrl (value: the https URL). Tapping the notification opens that page even if the app was killed. Set HTTP v1 fcm_options.analytics_label (and the Android/APNs equivalents) so Messaging Reports can filter by campaign. Labels must match ^[a-zA-Z0-9-_.~%]{1,50}$.
Every installed app also subscribes to topic wta_all. Use that from Firebase Console or HTTP v1 to reach all devices. Do not target “All users” / user segments in Console — that still needs Analytics audiences. Native Analytics is included for delivery reports, not for replacing topic broadcasts.
Messaging Reports
Firebase Console → Messaging → Reports needs Google Analytics enabled for the Firebase project (Project settings → Integrations → Google Analytics) and a native app rebuild after this FCM Analytics SDK. Envios (sends) count HTTP v1 accepts. Recebidas, Impressões, and opens are Android-only and only for background system notification messages. iOS and web still show sends without those columns. Reports can lag up to 24 hours. Filter by the Android app.
- The Integrations “Google Analytics”
G-ID is WebView GA4. It does not fill FCM Reports. - Foreground Android banners shown via local notifications are not FCM impressions. Foreground iOS uses the system banner only.
- FCM Analytics does not show an App Tracking Transparency prompt. Advertising ID / ad personalization collection is off.
Troubleshooting
- Package or bundle ID in the Firebase file must match
com.yourcompany.yourapp/<your-bundle-id>. - iOS: APNs key must be in Firebase Cloud Messaging, and Push must be enabled on the App ID (Apple Developer → Identifiers).
- Allow the notification permission dialog on the device, then keep the app open once so it can register.
- iOS Simulator cannot receive Firebase Console / topic sends (no APNs token, especially iPhone 17 / iOS 26). You can only inject a local
.apnsfile (must includegcm.message_id). Test real FCM on a physical iPhone. Android emulator/device is fine. - From Firebase Console Messaging, send to topic
wta_all(or a user topic), not an Analytics audience. Include a Notification title and body. For Reports, enable Google Analytics under Project settings → Integrations, rebuild the FCM app, wait up to 24h, and filter by Android. - Android status bar shows a white square: in Integrations → FCM, pick a silhouette or upload a custom transparent PNG and rebuild. Do not use the full launcher icon.
- Enabling OneSignal turns FCM off (and the reverse), after you confirm in the dialog. They cannot run in the same build.
- Rebuild after changing files or toggling the add-on.