Skip to main content

Android — FCM setup

Android delivery uses Firebase Cloud Messaging's HTTP v1 API with a service account. The legacy /fcm/send server key API was shut down in 2024; HTTP v1 is the only path.

Part A — Firebase project

1 Open the Firebase console and Add project (or pick the existing one your Android app already uses). Google Analytics is not needed.

2 In the project, click the Android icon under Get started by adding Firebase to your app.

3 Enter your Android package name (com.acme.shop, must match applicationId in app/build.gradle). Nickname and SHA-1 are optional for push. Register app.

4 Download google-services.json and put it in android/app/ (the module directory, next to build.gradle).

5 Add the Google services Gradle plugin as the console instructs:

// android/build.gradle (project)
buildscript {
dependencies {
classpath "com.google.gms:google-services:4.4.2"
}
}
// android/app/build.gradle (module)
apply plugin: "com.google.gms.google-services"

(Kotlin DSL: id("com.google.gms.google-services") version "4.4.2" apply false at the project level and id("com.google.gms.google-services") in the module.)

Part B — Service account

6 Click the gear icon next to Project OverviewProject settings.

7 Open the Service accounts tab. You will see the Firebase Admin SDK service account (firebase-adminsdk-xxxxx@<project>.iam.gserviceaccount.com).

8 Click Generate new private key, then Generate key in the confirmation dialog. A JSON file downloads (<project>-firebase-adminsdk-xxxxx-xxxxxxxx.json).

This file is a credential

Anyone holding it can send push notifications as your app. Do not commit it, do not e-mail it. Open Notification seals it on upload and never gives it back.

9 Confirm the Firebase Cloud Messaging API (V1) is enabled: Project settingsCloud Messaging tab → Firebase Cloud Messaging API (V1) shows Enabled. If it is disabled, click the ⋮ menu → Manage API in Google Cloud ConsoleEnable.

Part C — Upload to Open Notification

In the dashboard — Credentials › FCM — choose the JSON file and Save. The project id is read from the file.

FCM tab

Or on the command line:

bun run app:create -- --name "Acme" --slug acme \
--fcm-service-account ./acme-shop-prod-firebase-adminsdk.json

Part D — Verify

Press Test connection. The API mints an OAuth2 token from the service account and performs a validate_only send to an invalid token:

ResultMeaning
INVALID_ARGUMENT / UNREGISTEREDGoogle authenticated you; the fake token was rejected. Ready.
PERMISSION_DENIEDThe service account lacks the Firebase Cloud Messaging API role, or the API is disabled in Google Cloud.
401 / invalid_grantThe JSON is corrupt or the key was deleted in the console.

The app side

The SDK handles this — see Android SDK or React Native — but two things are on you:

  • A monochrome status-bar icon (res/drawable/ic_notification.png, white on transparent). Android renders anything else as a grey square.
  • Android 13+ needs the POST_NOTIFICATIONS runtime permission; the SDKs request it.

Why data-only messages

The server never sends an FCM notification block. It sends data only, with android.priority: "high":

{
"message": {
"token": "…",
"android": { "priority": "high", "ttl": "259200s", "collapse_key": "daily-streak" },
"data": {
"title": "New match", "body": "3 people liked you",
"image": "https://…", "url": "acme://matches",
"msgId": "<signed>", "campaignId": "…",
"actions": "[{\"id\":\"view\",\"title\":\"View\"}]",
"sound": "default", "group": "matches"
}
}
}

With a notification block Android draws the notification itself while the app is backgrounded and your code never runs — so there is no delivered event, no custom layout, no buttons. With data-only the SDK draws it and you keep all three. The trade-off: on a force-stopped app, or on some OEMs with aggressive battery savers (Xiaomi, Oppo, vivo), a data message may not be processed. priority: high is the mitigation.

Error handling on the server

FCM errorWorker action
UNREGISTERED, SENDER_ID_MISMATCH, INVALID_ARGUMENT (bad token)Subscription invalidated. Never retried.
QUOTA_EXCEEDED, UNAVAILABLE, INTERNALExponential backoff, up to 3 attempts.
THIRD_PARTY_AUTH_ERROROnly relevant when sending to iOS via FCM — which Open Notification never does.

SENDER_ID_MISMATCH after a migration means the tokens were registered against a different Firebase project than the service account you uploaded.