Skip to content

Set up push on Android

From a new Firebase project to a push on your Android phone: google-services.json, the service account key, the device token, and a first send.

PushUpdated 28 October 20268 min read

This guide takes an Android app from no push at all to a notification on your own phone. You need a Google account, Android Studio and your SignOps test key. It takes about 20 minutes.

How it fits together: your app gets a token from Firebase and gives it to your server. Your server adds it to SignOps under your own user id. From then on, your server sends to the user id, and SignOps hands the message to Firebase with your project’s key.

1. Make a Firebase project

  1. Open the Firebase console and choose Create a project (or open the one your app already uses). Google Analytics is optional; push does not need it.
  2. On the project’s home page, choose Add app, then the Android icon.
  3. Type your app’s package name: the applicationId in app/build.gradle.kts, for example com.kiranaexpress.app. It must match exactly.
  4. Download google-services.json and put it in your app module folder (app/), next to build.gradle.kts.

2. Add Firebase Messaging to the app

The Firebase setup screen shows the current version numbers; use those.

// build.gradle.kts (project)
plugins {
    id("com.google.gms.google-services") version "<version>" apply false
}

// app/build.gradle.kts
plugins {
    id("com.google.gms.google-services")
}
dependencies {
    implementation(platform("com.google.firebase:firebase-bom:<version>"))
    implementation("com.google.firebase:firebase-messaging")
}

3. Ask for permission and get the token

Android 13 and later asks the user before an app can show notifications. Ask at a moment the user understands, such as after they place an order, not at first launch.

<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

<application>
    <service android:name=".PushService" android:exported="false">
        <intent-filter>
            <action android:name="com.google.firebase.MESSAGING_EVENT" />
        </intent-filter>
    </service>
    <!-- The channel and icon used when the system draws the notification -->
    <meta-data android:name="com.google.firebase.messaging.default_notification_channel_id" android:value="orders" />
    <meta-data android:name="com.google.firebase.messaging.default_notification_icon" android:resource="@drawable/ic_notification" />
</application>
// After sign-in, once the user allows notifications
if (Build.VERSION.SDK_INT >= 33) {
    requestPermissions(arrayOf(Manifest.permission.POST_NOTIFICATIONS), 1)
}
FirebaseMessaging.getInstance().token.addOnSuccessListener { token ->
    api.saveDevice(token)   // your own endpoint, with the signed-in user
}

// PushService.kt: Firebase changes the token now and then
class PushService : FirebaseMessagingService() {
    override fun onNewToken(token: String) { api.saveDevice(token) }
}

Make the orders notification channel at app start with NotificationManager.createNotificationChannel. A push can name another channel with android_channel.

4. Add the token on your server

Your endpoint (api.saveDevice above) knows who is signed in. It adds the token under that user’s id. Adding the same token again only updates it.

curl https://api.signops.dev/v1/devices \
  -H "Authorization: Bearer $SIGNOPS_KEY" \
  -d user_id=usr_8f21c4 -d platform=android -d token=$FCM_TOKEN
# -> { "id": "dev_...", "user_id": "usr_8f21c4", "platform": "android", ... }

Keep the returned id. When the user signs out on that phone, remove it with DELETE /v1/devices/:id so they stop getting their pushes there. With the Node library this is signops.devices.add({ user_id, platform: "android", token }).

5. Give SignOps your Firebase service account

  1. In the Firebase console, open the gear icon, Project settings, then the Service accounts tab.
  2. Choose Generate new private key. A JSON file downloads. Keep it private: it can send to every user of the project.
  3. In the SignOps dashboard, open Push, Setup, and choose Set up on Android.
  4. Upload the JSON file. SignOps checks it with Google before saving, then shows the project id and Google accepted the key.

The same service account sends to iOS apps that use Firebase too (see iOS push).

6. Send a test

In the same Android panel, type the user id from step 4 under Send a test and choose Send test push, then See what Google said. Or from your server:

curl https://api.signops.dev/v1/push \
  -H "Authorization: Bearer $SIGNOPS_KEY" \
  -H "content-type: application/json" \
  -d '{"user_id":"usr_8f21c4","title":"Your order is out for delivery","body":"Ravi is 10 minutes away.","android_channel":"orders"}'
# -> { "status": "sent", "devices": 1, "sent": 1, "failed": 0 }

A push to one user is sent before the API answers, so sent and failed are the real result. Pushes to a topic or to everyone answer sending, and the result reaches your webhook.

Test and live keys keep separate device lists. A token your server added with the test key gets pushes sent with the test key, and nothing else does. When you switch your server to the live key, devices are added to the live list as users open the app.

7. Before you ship

  • Close the app and send again. Pushes that arrive only while the app is open usually mean the phone is stopping it in the background. Xiaomi, Oppo, Vivo and Realme do this by default; see why pushes do not arrive on those phones.
  • Open the app with a push. Put a deep link in link, and read it from the launch intent.
  • Know when tokens die. When Google says a token is no longer valid (the app was uninstalled), SignOps removes it and posts device.removed to your webhook.
  • Count opens (optional). The SignOps Android SDK reports opens with signops.push.opened(intent). It is not on Maven Central yet; the call it makes is POST /v1/push/:id/opened, with the id every push carries as signops_id.

When something goes wrong

What you see What it means
SENDER_ID_MISMATCH The token is from another Firebase project than the service account
UNREGISTERED The app was uninstalled or its data cleared; the token is removed
sent: 1 but nothing on the phone The app is in the foreground, notifications are off for the app, or the phone is stopping it in the background
Upload refused The file is not a service account key (a google-services.json is not one)

Every other code Google can answer is in FCM error codes.

Frequently asked questions

More questions? Email us and the answer goes on this page.

hello@signops.dev
Do I need the SignOps SDK for push on Android?

No. The app uses Firebase Cloud Messaging as usual and gives its token to your server. SignOps sends with your Firebase project's service account. The SignOps SDK is only needed for Approve, passkeys and counting opens.

Why does SignOps need my Firebase service account?

A device token belongs to one Firebase project, and only that project's credentials can send to it. The service account JSON is how SignOps sends as your project. It is checked with Google on upload, encrypted with AES-256-GCM, and never shown again.

What does SENDER_ID_MISMATCH mean?

The token was made by a different Firebase project from the service account you uploaded. Check that google-services.json in the app and the service account in SignOps come from the same Firebase project.

My push arrives when the app is closed but not when it is open. Why?

When the app is in the foreground, Android hands a notification message to your FirebaseMessagingService.onMessageReceived instead of showing it. Draw it yourself there with NotificationCompat if you want it shown.

Send your first push today.

Free for 1,000 push devices, 100 approvals and 1,000 passkey sign-ins a month, and 500 email codes to try. No card until you pass a free amount.