Skip to content

Keeping FCM tokens current

When an FCM token changes, how to send it to your server on every start, when to delete it (UNREGISTERED, 270 days, one month stale), and the move to Firebase Installation IDs.

PushBy Rushi Patel, Senior iOS developerUpdated 7 November 20266 min read

An FCM token is not permanent. It changes when the app moves to a new phone or its data is cleared, and it stops working when a device has been gone for long enough. If your server keeps the old one, pushes fail quietly and your delivery numbers fall for no visible reason. This page covers when tokens change, how to keep the server’s copy current, and Firebase’s move from registration tokens to Firebase Installation IDs (FIDs).

Tokens are becoming installation IDs

Firebase’s current docs mark registration tokens as deprecated. The replacement is the Firebase Installation ID, and the docs say both are “fully co-supported” during the transition:

Registration token (deprecated) Firebase Installation ID
Android callback onNewToken(token) onRegistered(installationId)
Android opt-in none firebase_messaging_installation_id_enabled meta-data set to true
Apple callback messaging(_:didReceiveRegistrationToken:) messaging(_:didReceiveRegistration:)
Apple opt-in none FirebaseMessagingInstallationIdEnabled = YES in Info.plist
HTTP v1 field token fid (the token field accepts an FID during the transition)
Refresh your own monthly job the SDK calls the callback on routine syncs at app start

The server side is the same either way: store one identifier per app install with the time it was last uploaded, and delete it when FCM says it is gone. The rest of this page says “token” for both.

When the token changes

Firebase lists three cases:

  • The app is restored on a new device.
  • The user uninstalls and reinstalls the app.
  • The user clears the app’s data.

Each gives a new token through the callback. The old one stops working, and your next send to it returns UNREGISTERED.

Separately, a token can go stale without changing. Firebase calls a registration stale when the device has not connected to FCM for over a month. On Android, after 270 days without a connection FCM treats it as expired, deletes it and rejects sends to it. Apple devices have no such rule, because FCM relies on APNs there.

Send it to the server on every start

On Android, with installation IDs:

class PushService : FirebaseMessagingService() {
    // Called on routine syncs at app start and whenever the ID changes.
    override fun onRegistered(installationId: String) {
        sendRegistrationToServer(installationId)
    }
}

With the deprecated token API, onNewToken fires when a token is first made and when it changes, but not on every start. Fetch the current one at startup too, so a failed upload is retried:

override fun onNewToken(token: String) {
    sendRegistrationToServer(token)
}

// In your Application or main activity:
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
    if (task.isSuccessful) sendRegistrationToServer(task.result)
}

On Apple platforms, set Messaging.messaging().delegate after FirebaseApp.configure() and upload from the delegate method. The token only arrives once the APNs device token has been mapped, so call registerForRemoteNotifications() on every launch.

On the web, getToken(messaging, { vapidKey }) returns the current token, from cache after the first call. Call it on each visit where permission is granted and upload the result.

What the server keeps

One row per app install: the user, the platform, the token and a last_seen time. Firebase “strongly” recommends the timestamp, updated on every upload even when the token has not changed.

CREATE TABLE device (
  token      TEXT PRIMARY KEY,
  user_id    TEXT NOT NULL,
  platform   TEXT NOT NULL,
  last_seen  INTEGER NOT NULL
);

-- On every upload: insert, or move the token to this user and touch the time.
INSERT INTO device (token, user_id, platform, last_seen) VALUES (?, ?, ?, ?)
ON CONFLICT (token) DO UPDATE SET user_id = excluded.user_id, last_seen = excluded.last_seen;

Key the row on the token, not the user: one person has several phones, and one phone can change hands.

Delete dead tokens

Two kinds of removal:

  1. On a send error. With HTTP v1, UNREGISTERED (404) means the token will never work again. INVALID_ARGUMENT (400) means the same only if the payload is valid, because it is also the answer to a malformed message. Firebase: “it is safe to delete your record of this registration”. See FCM error codes for the rest.
  2. By age. Firebase’s default staleness window is one month, and it suggests a daily job that deletes rows older than it:
const MONTH = 1000 * 60 * 60 * 24 * 30;
await db.prepare("DELETE FROM device WHERE last_seen < ?").bind(Date.now() - MONTH).run();

Pick the window for your app: one month may be too short for something people open quarterly. A device that comes back after pruning uploads its token again at the next start, so nothing is lost for good.

How often to refresh

With installation IDs and auto-initialization, the SDK keeps the registration fresh and calls onRegistered on routine syncs; Firebase says you do not need a scheduled job in the app. With the deprecated tokens, Firebase recommends fetching and uploading the token periodically, for example with WorkManager. Its advice: about once a month “strikes a good balance between battery impact and detecting inactive registration tokens”, and there is “no benefit” to refreshing more often than weekly.

Topics

If you use topic messaging, subscribe again whenever the token changes, since the subscription belongs to the old token. Firebase also suggests unsubscribing installs that have been idle for your staleness window, so fan-outs are faster and delivery numbers are honest.

With SignOps

SignOps Push keeps the device table for you. Your server adds each token with POST /v1/devices:

curl https://api.signops.dev/v1/devices \
  -H "Authorization: Bearer $SIGNOPS_KEY" \
  -d user_id=usr_8f21c4 -d platform=android -d token=$FCM_TOKEN

Adding the same token again updates its last-seen time, moves it to the given user if it changed hands, and brings it back if it had been removed. When FCM answers UNREGISTERED, SENDER_ID_MISMATCH, or INVALID_ARGUMENT about the registration token, SignOps removes that device and records the reason. A user keeps at most 20 devices; a 21st drops the one seen longest ago. SignOps sends to FCM in the token field, which Firebase says also accepts an installation ID during the transition.

The app side is still yours: upload the token on each start, as above. Setup is in the Android push guide.

Sources: Firebase: Best practices for FCM registration management, Firebase: Set up an FCM client app on Android, Firebase: Set up an FCM client app on Apple platforms, Firebase: Get started with FCM in a web app, Firebase: FCM HTTP v1 message.

Frequently asked questions

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

hello@signops.dev
When does an FCM registration token change?

Firebase lists three cases. The app is restored on a new device, the user uninstalls and reinstalls the app, or the user clears the app's data. The new token arrives in onNewToken on Android and in the messaging delegate on Apple platforms.

Is onNewToken deprecated?

Firebase now marks registration tokens as deprecated and recommends Firebase Installation IDs (FIDs) instead. On Android the FID arrives in onRegistered, on Apple platforms in messaging didReceiveRegistration. Both patterns are supported during the transition, and the HTTP v1 token field also accepts an FID.

How long does an FCM token stay valid?

On Android, FCM treats a registration as expired after 270 days without the device connecting and rejects sends to it. Firebase calls a registration stale after one month without a connection. iOS has no 270-day rule, so prune by your own timestamp.

How often should my app send its token to the server?

Firebase recommends sending it on every app start and refreshing it about once a month, with a timestamp saved on the server each time. It says there is no benefit to refreshing more often than weekly.

When should I delete a token on my server?

When a send returns UNREGISTERED (404), or INVALID_ARGUMENT (400) for a message whose payload you know is valid. Also delete tokens whose last upload is older than your staleness window, one month by Firebase's default.

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.