How to fix messaging/registration-token-not-registered in Firebase
This error occurs when Firebase Cloud Messaging attempts to send a message to a registration token that is no longer valid or has been unregistered. The token may have expired due to inactivity, the user may have uninstalled the app, or the app data may have been cleared. The fix involves removing invalid tokens from your database and implementing proper token lifecycle management.
Quick fix
Fixes messaging/registration-token-not-registered
// Node.js Admin SDK
async function sendNotificationWithErrorHandling(userId, message) {
const user = await db.users.findUnique({
where: { id: userId },
select: { fcmToken: true }
});
if (!user.fcmToken) {
console.log('User has no FCM token');
return;
}
try {
const response = await admin.messaging().send({
token: user.fcmToken,
notification: {
title: message.title,
body: message.body
}
});
console.log('Notification sent:', response);
return response;
} catch (error) {
// Check for registration token errors
if (error.code === 'messaging/registration-token-not-registered' ||
error.code === 'messaging/invalid-registration-token') {
console.log('Token is no longer valid, removing:', user.fcmToken);
// Delete the invalid token
await db.users.update({
where: { id: userId },
data: {
fcmToken: null,
fcmTokenInvalidatedAt: new Date(),
fcmTokenInvalidReason: error.code
}
});
console.log('Invalid token removed for user:', userId);
return null;
}
throw error;
}
}- Delete the invalid token from your database immediately
- Implement token refresh handling on the client
- Implement server-side token validation before sending
- Set up token refresh request endpoint
// Node.js Admin SDKasync function sendNotificationWithErrorHandling(userId, message) {const user = await db.users.findUnique({where: { id: userId },select: { fcmToken: true }});if (!user.fcmToken) {console.log('User has no FCM token');return;}try {const response = await admin.messaging().send({token: user.fcmToken,notification: {title: message.title,body: message.body}});console.log('Notification sent:', response);return response;} catch (error) {// Check for registration token errorsif (error.code === 'messaging/registration-token-not-registered' ||error.code === 'messaging/invalid-registration-token') {console.log('Token is no longer valid, removing:', user.fcmToken);// Delete the invalid tokenawait db.users.update({where: { id: userId },data: {fcmToken: null,fcmTokenInvalidatedAt: new Date(),fcmTokenInvalidReason: error.code}});console.log('Invalid token removed for user:', userId);return null;}throw error;}}messaging/registration-token-not-registered
What this error means
The "messaging/registration-token-not-registered" error indicates that the Firebase Cloud Messaging (FCM) server rejected your message because the provided registration token is not registered or has expired. Unlike the "invalid-registration-token" error which means the token string itself is malformed, this error means the token was once valid but is no longer usable. FCM registration tokens are unique identifiers assigned to each device/app combination when the app first registers with Firebase. These tokens are designed to be long-lived, but they can become unregistered for several legitimate reasons related to app lifecycle and device state. This is one of the most common FCM errors in production, and it's expected that some tokens will occasionally become invalid. The key is to handle these errors gracefully by removing stale tokens from your database rather than repeatedly trying to send to them.
Typical symptoms
- Push notification sends fail with 404 or "not registered" response
- Error occurs intermittently for specific user accounts or devices
- Tokens that worked previously suddenly start failing
- Error message specifically includes "registration-token-not-registered"
- Batch notification sends have partial failures for some tokens but not others
- Users report not receiving notifications even though app is installed
Common causes
- App was uninstalled by the user, causing automatic FCM unregistration
- User cleared app data or cache, triggering token reset
- Device has been inactive for extended period (typically 30+ days for stale tokens)
- User reinstalled the app, generating a new token, but old token is still in your database
- Token refreshed on client but your server still uses the old token
- APNS token expired on iOS devices due to certificate or network issues
- App was updated but new version lost previously stored token or failed to register with FCM
- Device storage corruption or database sync issues prevented token persistence
How to fix "messaging/registration-token-not-registered"
When you receive this error, the token is definitely no longer valid. Remove it from your database to prevent repeated failures and improve application performance.
// Node.js Admin SDK
async function sendNotificationWithErrorHandling(userId, message) {
const user = await db.users.findUnique({
where: { id: userId },
select: { fcmToken: true }
});
if (!user.fcmToken) {
console.log('User has no FCM token');
return;
}
try {
const response = await admin.messaging().send({
token: user.fcmToken,
notification: {
title: message.title,
body: message.body
}
});
console.log('Notification sent:', response);
return response;
} catch (error) {
// Check for registration token errors
if (error.code === 'messaging/registration-token-not-registered' ||
error.code === 'messaging/invalid-registration-token') {
console.log('Token is no longer valid, removing:', user.fcmToken);
// Delete the invalid token
await db.users.update({
where: { id: userId },
data: {
fcmToken: null,
fcmTokenInvalidatedAt: new Date(),
fcmTokenInvalidReason: error.code
}
});
console.log('Invalid token removed for user:', userId);
return null;
}
throw error;
}
}For batch sends, handle individual token failures:
// Send to multiple users
const response = await admin.messaging().sendEachForMulticast({
tokens: registrationTokens,
notification: {
title: 'Notification Title',
body: 'Notification body'
}
});
// Process failures
const tokensToDelete = [];
response.responses.forEach((resp, index) => {
if (!resp.success) {
const error = resp.error;
console.error(`Send to token ${index} failed:`, error.code);
// Mark for deletion
if (error.code === 'messaging/registration-token-not-registered' ||
error.code === 'messaging/invalid-registration-token') {
tokensToDelete.push(registrationTokens[index]);
}
}
});
// Bulk delete invalid tokens
if (tokensToDelete.length > 0) {
await db.users.updateMany({
where: {
fcmToken: { in: tokensToDelete }
},
data: {
fcmToken: null,
fcmTokenInvalidatedAt: new Date()
}
});
console.log(`Removed ${tokensToDelete.length} invalid tokens`);
}
console.log(`Send result: ${response.successCount} succeeded, ${response.failureCount} failed`);Ensure your client app properly handles token refresh events and sends updated tokens back to your server. FCM can refresh tokens periodically, and if your app doesn't notify the server of the new token, you'll quickly accumulate stale tokens in your database.
Web (JavaScript/TypeScript):
import { getMessaging, onMessage } from 'firebase/messaging';
import { initializeApp } from 'firebase/app';
const app = initializeApp(firebaseConfig);
const messaging = getMessaging(app);
// Listen for token refresh
messaging.onTokenRefresh(() => {
getToken(messaging, { vapidKey: 'YOUR_VAPID_KEY' })
.then(newToken => {
console.log('FCM Token refreshed:', newToken);
// Send new token to server
fetch('/api/update-fcm-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: newToken })
})
.catch(err => console.error('Failed to update token:', err));
});
});Android (Kotlin):
class MyFirebaseMessagingService : FirebaseMessagingService() {
override fun onNewToken(token: String) {
super.onNewToken(token)
Log.d(TAG, "Refreshed token: $token")
// Send to your backend
sendTokenToServer(token)
}
private fun sendTokenToServer(token: String) {
val apiService = RetrofitClient.getInstance().create(ApiService::class.java)
apiService.updateFcmToken(UpdateTokenRequest(token))
.enqueue(object : Callback<Void> {
override fun onResponse(call: Call<Void>, response: Response<Void>) {
Log.d(TAG, "Token sent to server successfully")
}
override fun onFailure(call: Call<Void>, t: Throwable) {
Log.e(TAG, "Failed to send token to server", t)
}
})
}
}iOS (Swift):
import Firebase
import FirebaseMessaging
class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
if let token = fcmToken {
print("FCM token refreshed: \(token)")
// Send to server
sendTokenToServer(token)
}
}
private func sendTokenToServer(_ token: String) {
let urlString = "https://yourserver.com/api/update-fcm-token"
guard let url = URL(string: urlString) else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try? JSONSerialization.data(
withJSONObject: ["token": token]
)
URLSession.shared.dataTask(with: request).resume()
}
}Check token age and validity before attempting to send. Implement a system to proactively refresh or remove tokens that may be stale.
// Token validation utility
function shouldSkipToken(tokenRecord) {
// Skip if token is NULL
if (!tokenRecord.fcmToken) {
return true;
}
// Skip if token was invalidated recently
if (tokenRecord.fcmTokenInvalidatedAt) {
return true;
}
// Skip if token has not been updated in 30 days (potential stale token)
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
if (new Date(tokenRecord.fcmTokenUpdatedAt) < thirtyDaysAgo) {
console.warn('Token not updated in 30 days, skipping:', tokenRecord.id);
return true;
}
return false;
}
// Before sending notifications
async function sendBulkNotifications(userIds, message) {
const users = await db.users.findMany({
where: { id: { in: userIds } },
select: {
id: true,
fcmToken: true,
fcmTokenUpdatedAt: true,
fcmTokenInvalidatedAt: true
}
});
const validTokens = users
.filter(user => !shouldSkipToken(user))
.map(user => user.fcmToken);
if (validTokens.length === 0) {
console.log('No valid tokens available');
return;
}
const response = await admin.messaging().sendEachForMulticast({
tokens: validTokens,
notification: message
});
console.log(`Sent to ${response.successCount} devices, ${response.failureCount} failed`);
}Create an API endpoint that allows clients to update their FCM token when it changes. This should be called both at app startup and whenever the token is refreshed.
// Express.js example
app.post('/api/update-fcm-token', authenticateUser, async (req, res) => {
try {
const { token } = req.body;
const userId = req.user.id;
// Validate token format (FCM tokens are typically 150+ chars)
if (!token || typeof token !== 'string' || token.length < 100) {
return res.status(400).json({
error: 'Invalid FCM token format'
});
}
// Update user's token
const updated = await db.users.update({
where: { id: userId },
data: {
fcmToken: token,
fcmTokenUpdatedAt: new Date(),
fcmTokenInvalidatedAt: null,
fcmTokenInvalidReason: null
}
});
console.log('Token updated for user:', userId);
res.json({
success: true,
message: 'Token updated',
tokenLength: token.length
});
} catch (error) {
console.error('Token update failed:', error);
res.status(500).json({
error: 'Failed to update token',
message: error.message
});
}
});
// Next.js API route example
export async function POST(request: NextRequest) {
try {
const session = await getSession(request);
if (!session) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const { token } = await request.json();
if (!token || token.length < 100) {
return NextResponse.json(
{ error: 'Invalid token format' },
{ status: 400 }
);
}
await prisma.user.update({
where: { id: session.user.id },
data: {
fcmToken: token,
fcmTokenUpdatedAt: new Date()
}
});
return NextResponse.json({ success: true });
} catch (error) {
return NextResponse.json(
{ error: 'Failed to update token' },
{ status: 500 }
);
}
}Make sure this endpoint is called:
- On app startup (to ensure server has current token)
- When token refresh is detected (via onTokenRefresh)
- After user login (token might have changed)
Implement scheduled tasks to detect and remove tokens that haven't been updated in a long time, preventing accumulation of dead tokens.
// Scheduled job to clean up stale tokens (run daily/weekly)
async function cleanupStaleTokens() {
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
const staleTokenCount = await db.users.updateMany({
where: {
fcmToken: { not: null },
fcmTokenUpdatedAt: { lt: thirtyDaysAgo }
},
data: {
fcmToken: null,
fcmTokenInvalidatedAt: new Date(),
fcmTokenInvalidReason: 'AUTO_CLEANUP_STALE'
}
});
console.log(`Cleaned up ${staleTokenCount.count} stale tokens`);
return staleTokenCount.count;
}
// Bull Queue example for scheduled job
const cleanupQueue = new Queue('token-cleanup', {
connection: redis
});
cleanupQueue.process(async () => {
return cleanupStaleTokens();
});
// Schedule to run daily at 2 AM
cleanupQueue.add(
{},
{
repeat: {
cron: '0 2 * * *'
}
}
);
// Node.js cron example
import cron from 'node-cron';
// Run cleanup every Sunday at 3 AM
cron.schedule('0 3 * * 0', async () => {
console.log('Running scheduled token cleanup...');
try {
const removed = await cleanupStaleTokens();
console.log(`Cleanup completed: removed ${removed} tokens`);
} catch (error) {
console.error('Cleanup failed:', error);
}
});You can also add logging to track token invalidation patterns:
// Add to your error handling
async function logTokenError(userId, token, errorCode) {
await db.tokenErrors.create({
data: {
userId,
token: token.substring(0, 20) + '...', // Log partial token for privacy
errorCode,
timestamp: new Date(),
deviceInfo: userAgent
}
});
}
// Use in error handler
if (error.code === 'messaging/registration-token-not-registered') {
await logTokenError(userId, token, error.code);
// ... delete token
}Advanced notes
Token lifecycle on different platforms: Android tokens can go stale after 30+ days of inactivity, while iOS tokens depend on APNS certificate validity. Web tokens (using VAPID) are browser-specific and will regenerate when cookies/storage is cleared. Implement platform-aware token management.
Batch send error patterns: When using sendEachForMulticast(), if a significant portion of tokens fail with "registration-token-not-registered", it may indicate app uninstalls are spiking (seasonal apps, version bugs, etc.). Monitor the failure rate as a health metric.
Token format confusion: The error code "messaging/registration-token-not-registered" is specifically for tokens that are syntactically valid but no longer registered. This is different from "messaging/invalid-registration-token" (malformed token string). Both should result in token deletion, but "not-registered" suggests the user likely uninstalled the app or cleared app data.
GDPR and token deletion: Deleting tokens from your database when they become invalid is also GDPR-compliant (you're not retaining data for users who've uninstalled). However, if you're storing token error logs, ensure you have a retention policy.
Database index optimization: If you frequently query fcmToken to find users by token or update them by token status, add indexes:
CREATE INDEX idx_users_fcm_token ON users(fcm_token);
CREATE INDEX idx_users_fcm_updated_at ON users(fcm_token_updated_at);
CREATE INDEX INDEX idx_users_fcm_invalidated ON users(fcm_token_invalidated_at);Metrics to track: Monitor these metrics to catch FCM delivery issues early:
- Percentage of tokens that fail with "not-registered" vs. other errors
- Token refresh frequency and response times
- Average age of valid tokens in your database
- Time to cleanup after token invalidation
Frequently asked questions
- What does "messaging/registration-token-not-registered" mean?
- The "messaging/registration-token-not-registered" error indicates that the Firebase Cloud Messaging (FCM) server rejected your message because the provided registration token is not registered or has expired. Unlike the "invalid-registration-token" error which means the token string itself is malformed, this error means the token was once valid but is no longer usable. FCM registration tokens are unique identifiers assigned to each device/app combination when the app first registers with Firebase.
- How do I fix "messaging/registration-token-not-registered"?
- This error occurs when Firebase Cloud Messaging attempts to send a message to a registration token that is no longer valid or has been unregistered. The token may have expired due to inactivity, the user may have uninstalled the app, or the app data may have been cleared. The fix involves removing invalid tokens from your database and implementing proper token lifecycle management.
- What causes "messaging/registration-token-not-registered"?
- App was uninstalled by the user, causing automatic FCM unregistration User cleared app data or cache, triggering token reset Device has been inactive for extended period (typically 30+ days for stale tokens)
- Is the "messaging/registration-token-not-registered" error serious?
- Yes. This is a high-severity error that usually blocks work until it is fixed, but the steps above resolve it.
Official resources & further reading
- ๐Firebase Official Documentation
- ๐firebase.google.com - error codes(Official Docs)
- ๐firebase.google.com - manage tokens(Official Docs)
- ๐github.com - 533(GitHub)
- ๐