If you use FCM APIs to build send requests programmatically, you may find that, over time, you are wasting resources by sending messages to inactive devices with stale registration tokens. This situation can affect the message delivery data reported in the Firebase console or data exported to BigQuery, showing up as a dramatic (but not actually valid) drop in delivery rates. This guide discusses some measures you can take to help ensure efficient message targeting and valid delivery reporting.
Basic best practices
There are some fundamental practices you should follow in any app that uses FCM APIs to build send requests programmatically. The main best practices are:
- Store registration tokens on your server. An important role for the server is to keep track of each client's token and keep an updated list of active tokens. We strongly recommend implementing a token timestamp in your code and your servers, and updating this timestamp at regular intervals.
- Remove stored tokens that become stale. In addition to removing tokens in obvious cases of invalid token responses, you'll probably need to monitor other signs that the token is stale. This guide discusses some of your options for achieving this.
Retrieve and store registration tokens
On initial startup of your app, the FCM SDK generates a registration token for the client app instance. This is the token that you must include in targeted send requests from the API, or add to topic subscriptions for targeting topics.
As noted in our client setup guides, your app should retrieve this token at initial startup and save it to your app server alongside a timestamp. This timestamp must be implemented by your code and your servers, as it is not provided for you by FCM SDKs.
Also, it's important to save the token to the server and update the timestamp whenever it changes, such as when:
- The app is restored on a new device
- The user uninstalls/reinstall the app
- The user clears app data.
Example: store tokens and timestamps in Cloud Firestore
For example, you could use Cloud Firestore to store tokens in a collection
called fcmTokens
. Each document ID in the collection corresponds to
a user ID, and the document stores the current registration token and its
last-updated timestamp. Use the set
function as shown in this Kotlin example:
/**
* Persist token to third-party servers.
*
* Modify this method to associate the user's FCM registration token with any server-side account
* maintained by your application.
*
* @param token The new token.
*/
private fun sendTokenToServer(token: String?) {
// If you're running your own server, call API to send token and today's date for the user
// Example shown below with Firestore
// Add token and timestamp to Firestore for this user
val deviceToken = hashMapOf(
"token" to token,
"timestamp" to FieldValue.serverTimestamp(),
)
// Get user ID from Firebase Auth or your own server
Firebase.firestore.collection("fcmTokens").document("myuserid")
.set(deviceToken)
}
Whenever a token is retrieved, it is stored in Cloud Firestore by calling
sendTokenToServer
:
/**
* Called if the FCM registration token is updated. This may occur if the security of
* the previous token had been compromised. Note that this is called when the
* FCM registration token is initially generated so this is where you would retrieve the token.
*/
override fun onNewToken(token: String) {
Log.d(TAG, "Refreshed token: $token")
// If you want to send messages to this application instance or
// manage this apps subscriptions on the server side, send the
// FCM registration token to your app server.
sendTokenToServer(token)
}
var token = Firebase.messaging.token.await()
// Check whether the retrieved token matches the one on your server for this user's device
val preferences = this.getPreferences(Context.MODE_PRIVATE)
val tokenStored = preferences.getString("deviceToken", "")
lifecycleScope.launch {
if (tokenStored == "" || tokenStored != token)
{
// If you have your own server, call API to send the above token and Date() for this user's device
// Example shown below with Firestore
// Add token and timestamp to Firestore for this user
val deviceToken = hashMapOf(
"token" to token,
"timestamp" to FieldValue.serverTimestamp(),
)
// Get user ID from Firebase Auth or your own server
Firebase.firestore.collection("fcmTokens").document("myuserid")
.set(deviceToken).await()
}
}
Detect invalid token responses from the FCM backend
Make sure to detect invalid token responses from FCM and respond by deleting from your system any registration tokens that are known to be invalid. With the HTTP v1 API, these error messages may indicate that your send request targeted stale or invalid tokens:
UNREGISTERED
(HTTP 404)INVALID_ARGUMENT
(HTTP 400)
Note that, because INVALID_ARGUMENT
is thrown also in cases of issues in the
message payload, it signals an invalid token only if the payload is completely
valid. See
ErrorCodes
for more information.
If you are certain that the message payload is valid and you receive either of these responses for a targeted token, it is safe to delete your record of this token, since it will never again be valid. For example, to delete invalid tokens from Cloud Firestore, you could deploy and run a function like the following:
// Registration token comes from the client FCM SDKs
const registrationToken = 'YOUR_REGISTRATION_TOKEN';
const message = {
data: {
// Information you want to send inside of notification
},
token: registrationToken
};
// Send message to device with provided registration token
getMessaging().send(message)
.then((response) => {
// Response is a message ID string.
})
.catch((error) => {
// Delete token for user if error code is UNREGISTERED or INVALID_ARGUMENT.
if (errorCode == "messaging/registration-token-not-registered") {
// If you're running your own server, call API to delete the token for the user
// Example shown below with Firestore
// Get user ID from Firebase Auth or your own server
Firebase.firestore.collection("fcmTokens").document(user.uid).delete()
}
});
Ensuring registration token freshness
Determining whether a token is fresh or stale is not always straightforward. To cover all cases, you should adopt a threshold for when you consider tokens stale; our recommendation is two months. Any token older than two months is likely to be an inactive device; an active device would have otherwise refreshed its token.
Update tokens on a regular basis
We recommend that you periodically retrieve and update all registration tokens on your server. This requires you to:
- Add app logic in your client app to retrieve the current token using the appropriate API call (such as
token(completion):
for Apple platforms orgetToken()
for Android) and then send the current token to your app server for storage (with timestamp). This could be a monthly job configured to cover all clients/tokens. - Add server logic to update the token’s timestamp at regular intervals, regardless of whether or not the token has changed.
For an example of Android logic for updating tokens using WorkManager, see Managing Cloud Messaging Tokens on the Firebase blog.
Whatever timing pattern you follow, make sure to update tokens periodically. An update frequency of once every two months likely strikes a good balance between battery impact vs. detecting inactive registration tokens. By doing this refresh, you also ensure that any device which goes inactive will refresh its registration when it becomes active again. There is no benefit to doing the refresh more frequently than weekly.
Remove stale registration tokens
Before sending messages to a device, ensure that the timestamp
of the device's registration token is within your staleness window period.
For example, you could implement Cloud Functions for Firebase to run a daily check
to ensure that the timestamp is within a defined staleness window period such as
const EXPIRATION_TIME = 1000 * 60 * 60 * 24 * 60;
and then
remove stale tokens:
exports.pruneTokens = functions.pubsub.schedule('every 24 hours').onRun(async (context) => {
// Get all documents where the timestamp exceeds is not within the past month
const staleTokensResult = await admin.firestore().collection('fcmTokens')
.where("timestamp", "<", Date.now() - EXPIRATION_TIME)
.get();
// Delete devices with stale tokens
staleTokensResult.forEach(function(doc) { doc.ref.delete(); });
});
Unsubscribe stale tokens from topics
Managing topics subscriptions to remove stale registration tokens is another consideration. It involves two steps:
- Your app should resubscribe to topics once per month and/or whenever the registration token changes. This forms a self-healing solution, where the subscriptions reappear automatically when an app becomes active again.
- If an app instance is idle for 2 months (or your own staleness window) you should unsubscribe it from topics using the Firebase Admin SDK to delete the token/topic mapping from the FCM backend.
The benefit of these two steps is that your fanouts will occur faster since there are fewer stale tokens to fan out to, and your stale app instances will automatically resubscribe once they are active again.
Measuring delivery success
Generally, we advise targeting messages based on actions observed or captured from actively used app instances. This is especially important if you regularly send messages to topics with large numbers of subscribers; if a portion of those subscribers are actually inactive, the impact on your delivery statistics can be significant over time.
Before targeting messages to a token, consider:
- Do Google Analytics, data captured in BigQuery, or other tracking signals indicate the token is active?
- Have previous delivery attempts failed consistently over a period of time?
- Has the registration token been updated on your servers in the past two months?
- For Android devices, does the FCM Data API report a high percentage of message delivery failures due to
droppedDeviceInactive
?
For more information about delivery, see Understanding message delivery.