This error occurs when Firebase Cloud Messaging rejects a registration token because it is invalid, expired, unregistered, or no longer recognized by FCM. The fix involves validating tokens, removing stale tokens, refreshing tokens on the client side, and implementing proper token lifecycle management.
The "messaging/invalid-registration-token" error indicates that the registration token provided to Firebase Cloud Messaging (FCM) is not recognized as a valid, active FCM token. FCM registration tokens are unique identifiers that allow your backend server to send push notifications to specific devices. This error is different from "token format incorrect" - the token string itself may be properly formatted, but FCM considers it invalid because: - The token has expired (FCM marks tokens as invalid after 270 days of inactivity on Android) - The app that generated the token was uninstalled - The app was updated but not configured to receive messages - The APNS token on iOS was reported as invalid by the APNS Feedback Service - The device deleted its Instance ID - The user cleared the app data - The token was never properly registered with FCM in the first place When this error occurs, FCM returns HTTP status 400 with error code "INVALID_ARGUMENT", indicating the token should no longer be used to send messages.
Ensure you're sending notifications using the correct Firebase project and credentials that match where the client tokens were generated.
// Node.js Admin SDK
const admin = require('firebase-admin');
// Check your initialized app
console.log('Firebase app:', admin.app().name);
console.log('Project ID:', admin.apps[0].options.projectId);
// Verify credentials are for the same project
const serviceAccountJson = require('./path/to/service-account-key.json');
console.log('Service account project:', serviceAccountJson.project_id);
// They must match!
if (admin.apps[0].options.projectId !== serviceAccountJson.project_id) {
throw new Error('Firebase project mismatch!');
}Common mistake: Using development tokens against a production Firebase project, or vice versa. Always use the same project for both client generation and server sending.
Firebase recommends removing tokens that haven't been refreshed in 2 months. Tokens older than 270 days become invalid on Android.
Store the token timestamp with each device token:
// Schema example (Prisma)
model UserDevice {
id String @id @default(cuid())
userId String
fcmToken String @unique
fcmTokenReceivedAt DateTime // Track when token was last updated
fcmTokenInvalidatedAt DateTime? // Track when it failed
}
// Create migration
CREATE TABLE UserDevice (
id VARCHAR(255) PRIMARY KEY,
userId VARCHAR(255) NOT NULL,
fcmToken VARCHAR(255) NOT NULL UNIQUE,
fcmTokenReceivedAt TIMESTAMP NOT NULL,
fcmTokenInvalidatedAt TIMESTAMP
);Implement automatic cleanup:
// Remove stale tokens (older than 2 months) daily
async function cleanupStaleTokens() {
const twoMonthsAgo = new Date();
twoMonthsAgo.setDate(twoMonthsAgo.getDate() - 60);
const staleTokens = await db.userDevice.findMany({
where: {
fcmTokenReceivedAt: {
lt: twoMonthsAgo
}
}
});
console.log(`Found ${staleTokens.length} stale tokens`);
// Remove them
await db.userDevice.deleteMany({
where: {
id: {
in: staleTokens.map(t => t.id)
}
}
});
}
// Schedule to run daily (using node-cron or similar)
cron.schedule('0 2 * * *', cleanupStaleTokens);When your app is updated, ensure the client regenerates and resends the FCM token to your server. Implement token refresh listeners.
Web (JavaScript):
import { getMessaging, onMessage, getToken } from 'firebase/messaging';
async function setupMessaging() {
try {
const messaging = getMessaging();
// Get initial token
const token = await getToken(messaging, {
vapidKey: 'YOUR_VAPID_KEY'
});
if (token) {
await sendTokenToServer(token);
console.log('FCM Token initialized:', token);
}
// Listen for token changes
// This fires when the token is refreshed (app updates, user reinstalls, etc.)
onMessage(messaging, (payload) => {
console.log('Message received:', payload);
});
} catch (error) {
console.error('Error setting up messaging:', error);
}
}
async function sendTokenToServer(token) {
await fetch('/api/devices/register-fcm', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ fcmToken: token })
});
}Android (Kotlin):
// Firebase automatically refreshes tokens
// Handle new tokens by listening to the refresh event
class MyFirebaseMessagingService : FirebaseMessagingService() {
override fun onNewToken(token: String) {
Log.d(TAG, "New token: $token")
// Send to your server immediately
sendTokenToServer(token)
}
private fun sendTokenToServer(token: String) {
val request = Request.Builder()
.url("https://yourapi.com/api/devices/register-fcm")
.post(RequestBody.create("application/json".toMediaType(),
"""{"fcmToken":"$token"}"""))
.build()
val client = OkHttpClient()
client.newCall(request).execute()
}
}iOS (Swift):
import Firebase
import UserNotifications
class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
// Listen for token changes
Messaging.messaging().delegate = self
// Request user permission
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { _, _ in }
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
return true
}
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
guard let token = fcmToken else { return }
print("FCM token: \(token)")
// Send to your server
sendTokenToServer(token)
}
private func sendTokenToServer(_ token: String) {
let url = URL(string: "https://yourapi.com/api/devices/register-fcm")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let body = ["fcmToken": token]
request.httpBody = try? JSONSerialization.data(withJSONObject: body)
URLSession.shared.dataTask(with: request).resume()
}
}When you encounter this error, immediately remove or mark the token as invalid to prevent repeated failures.
// Node.js Admin SDK with proper error handling
async function sendNotification(userId, message) {
const device = await db.userDevice.findFirst({
where: { userId, fcmTokenInvalidatedAt: null }
});
if (!device || !device.fcmToken) {
throw new Error('User has no valid FCM token');
}
try {
const response = await admin.messaging().send({
token: device.fcmToken,
notification: {
title: message.title,
body: message.body
}
});
console.log('Message sent:', response);
return response;
} catch (error) {
console.error('FCM error:', error.code, error.message);
// Check for invalid/unregistered token errors
if (error.code === 'messaging/invalid-registration-token' ||
error.code === 'messaging/registration-token-not-registered') {
console.log('Marking token as invalid:', device.fcmToken);
// Mark as invalid instead of deleting (for auditing)
await db.userDevice.update({
where: { id: device.id },
data: {
fcmTokenInvalidatedAt: new Date(),
fcmTokenInvalidReason: error.code
}
});
// Notify client to request new token
// (could send another message or return error response)
}
throw error;
}
}For batch sends to multiple tokens:
async function sendBulkNotification(userIds, message) {
const devices = await db.userDevice.findMany({
where: {
userId: { in: userIds },
fcmTokenInvalidatedAt: null
}
});
const tokens = devices.map(d => d.fcmToken);
const response = await admin.messaging().sendEachForMulticast({
tokens,
notification: {
title: message.title,
body: message.body
}
});
console.log('Batch send results:');
console.log(`Successes: ${response.successCount}`);
console.log(`Failures: ${response.failureCount}`);
// Process failures and remove invalid tokens
const tokensToInvalidate = [];
response.responses.forEach((resp, idx) => {
if (!resp.success) {
const error = resp.error;
console.error(`Token ${idx} failed:`, error.code, error.message);
if (error.code === 'messaging/invalid-registration-token' ||
error.code === 'messaging/registration-token-not-registered') {
tokensToInvalidate.push({
token: tokens[idx],
deviceId: devices[idx].id,
errorCode: error.code
});
}
}
});
// Update invalid tokens in database
for (const item of tokensToInvalidate) {
await db.userDevice.update({
where: { id: item.deviceId },
data: {
fcmTokenInvalidatedAt: new Date(),
fcmTokenInvalidReason: item.errorCode
}
});
}
console.log(`Invalidated ${tokensToInvalidate.length} tokens`);
return response;
}Firebase recommends refreshing tokens at least once per month to maintain an up-to-date token store.
// Web - Refresh token monthly
const THIRTY_DAYS = 30 * 24 * 60 * 60 * 1000;
async function refreshFCMTokenIfNeeded() {
const lastRefresh = localStorage.getItem('fcmTokenLastRefresh');
const now = Date.now();
// Refresh if never done or older than 30 days
if (!lastRefresh || (now - parseInt(lastRefresh)) > THIRTY_DAYS) {
try {
const messaging = getMessaging();
const token = await getToken(messaging, {
vapidKey: 'YOUR_VAPID_KEY'
});
if (token) {
await sendTokenToServer(token);
localStorage.setItem('fcmTokenLastRefresh', now.toString());
console.log('Token refreshed successfully');
}
} catch (error) {
console.error('Failed to refresh token:', error);
}
}
}
// Call on app load
document.addEventListener('DOMContentLoaded', refreshFCMTokenIfNeeded);
// Also refresh on visibility change (user returns to app)
document.addEventListener('visibilitychange', () => {
if (!document.hidden) {
refreshFCMTokenIfNeeded();
}
});// Android - Refresh token monthly
fun refreshFCMTokenIfNeeded(context: Context) {
val prefs = context.getSharedPreferences("fcm_prefs", Context.MODE_PRIVATE)
val lastRefresh = prefs.getLong("lastRefresh", 0)
val thirtyDaysMs = 30 * 24 * 60 * 60 * 1000L
if (System.currentTimeMillis() - lastRefresh > thirtyDaysMs) {
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
if (task.isSuccessful) {
val token = task.result
Log.d(TAG, "Token refreshed: $token")
sendTokenToServer(token)
prefs.edit().putLong("lastRefresh", System.currentTimeMillis()).apply()
}
}
}
}
// Call in Activity.onCreate() or Service
override fun onCreate() {
super.onCreate()
refreshFCMTokenIfNeeded(this)
}Token validity windows: FCM doesn't provide a guaranteed time window for token validity, but Firebase's best practices recommend treating tokens as stale after 2 months of inactivity and definitely invalid after 270 days (for Android). Implement a two-pronged approach: (1) Remove stale tokens proactively based on timestamp, and (2) clean up invalid tokens reactively when send attempts fail.
Handling token rotation: FCM may rotate tokens without warning. Implement a listener on the client side that fires whenever a new token is generated, and ensure the client immediately sends the new token to your server. This forms a self-healing system where stale tokens are naturally replaced.
Multi-device users: Users often have multiple devices (phone, tablet, web). Store tokens separately per device and platform, and implement cleanup to handle when a user removes a device. Don't assume a user has only one valid token.
Development vs. production tokens: Tokens generated against your development Firebase project cannot be used with your production Firebase project's credentials, and vice versa. Always ensure your backend credentials match the project where tokens were generated. Use environment-specific configuration.
Silent notification edge case: There's a known issue where FCM returns "invalid-registration-token" for valid tokens when sending iOS silent (APNs-only) notifications. If you encounter this with genuinely valid tokens on iOS, try including a notification payload instead of sending data-only or APNs silent notifications.
Rate limiting recovery: If you receive many invalid token errors in a short period, it may indicate you're sending to a large batch of stale tokens. Rather than retrying immediately, implement exponential backoff or batch your sends to avoid hitting FCM rate limits while cleaning up your token database.
Callable Functions: INTERNAL - Unhandled exception
How to fix "Callable Functions: INTERNAL - Unhandled exception" in Firebase
auth/invalid-hash-algorithm: Hash algorithm doesn't match supported options
How to fix "auth/invalid-hash-algorithm: Hash algorithm doesn't match supported options" in Firebase
Hosting: CORS configuration not set up properly
How to fix CORS configuration in Firebase Hosting
auth/reserved-claims: Custom claims use reserved OIDC claim names
How to fix "reserved claims" error when setting custom claims in Firebase
Callable Functions: UNAUTHENTICATED - Invalid credentials
How to fix "UNAUTHENTICATED - Invalid credentials" in Firebase Callable Functions