How to fix messaging/registration-token-not-registered: Previously valid token now inactive in Firebase

FirebaseINTERMEDIATEHIGHUpdated Dec 29, 2025· 126 views

This error occurs when Firebase Cloud Messaging detects that a registration token was once valid but has become inactive. The token typically becomes inactive after the app is uninstalled, user data is cleared, the device remains inactive for extended periods, or after a token refresh cycle. Fixing this requires removing stale tokens and implementing proper token lifecycle management.

Quick fix

Fixes messaging/registration-token-not-registered: Previously valid token now inactive

// Node.js Admin SDK - Single token deletion
async function handleInactiveToken(userId, fcmToken) {
  try {
    const response = await admin.messaging().send({
      token: fcmToken,
      notification: {
        title: 'Test',
        body: 'Test'
      }
    });
  } catch (error) {
    if (error.code === 'messaging/registration-token-not-registered') {
      console.log('Token is inactive, removing from database:', fcmToken);

      // Delete the inactive token
      await prisma.user.update({
        where: { id: userId },
        data: {
          fcmToken: null,
          fcmTokenStatus: 'INACTIVE',
          fcmTokenInactivatedAt: new Date()
        }
      });

      return { success: false, reason: 'Token removed - was inactive' };
    }
    throw error;
  }
}

// Batch send with inactive token detection
async function sendBatchNotifications(recipients, message) {
  const response = await admin.messaging().sendEachForMulticast({
    tokens: recipients.map(r => r.fcmToken),
    notification: message
  });

  // Handle failures for inactive tokens
  const inactiveTokens = [];

  response.responses.forEach((resp, index) => {
    if (!resp.success && resp.error.code === 'messaging/registration-token-not-registered') {
      const token = recipients[index].fcmToken;
      console.warn('Inactive token detected:', token.substring(0, 20) + '...');
      inactiveTokens.push({
        userId: recipients[index].id,
        token: token
      });
    }
  });

  // Bulk delete all inactive tokens
  if (inactiveTokens.length > 0) {
    await prisma.user.updateMany({
      where: {
        fcmToken: { in: inactiveTokens.map(t => t.token) }
      },
      data: {
        fcmToken: null,
        fcmTokenStatus: 'INACTIVE',
        fcmTokenInactivatedAt: new Date()
      }
    });

    console.log(`Removed ${inactiveTokens.length} inactive tokens`);
  }

  return {
    successCount: response.successCount,
    failureCount: response.failureCount,
    inactiveTokensRemoved: inactiveTokens.length
  };
}
  1. Immediately delete the inactive token from your database
  2. Implement robust token refresh detection on client
  3. Create an API endpoint to receive token updates
  4. Check token age before sending and skip old ones
firebase — error
// Node.js Admin SDK - Single token deletion
async function handleInactiveToken(userId, fcmToken) {
try {
const response = await admin.messaging().send({
token: fcmToken,
notification: {
title: 'Test',
body: 'Test'
}
});
} catch (error) {
if (error.code === 'messaging/registration-token-not-registered') {
console.log('Token is inactive, removing from database:', fcmToken);
// Delete the inactive token
await prisma.user.update({
where: { id: userId },
data: {
fcmToken: null,
fcmTokenStatus: 'INACTIVE',
fcmTokenInactivatedAt: new Date()
}
});
return { success: false, reason: 'Token removed - was inactive' };
}
throw error;
}
}
// Batch send with inactive token detection
async function sendBatchNotifications(recipients, message) {
const response = await admin.messaging().sendEachForMulticast({
tokens: recipients.map(r => r.fcmToken),
notification: message
});
// Handle failures for inactive tokens
const inactiveTokens = [];
response.responses.forEach((resp, index) => {
if (!resp.success && resp.error.code === 'messaging/registration-token-not-registered') {
const token = recipients[index].fcmToken;
console.warn('Inactive token detected:', token.substring(0, 20) + '...');
inactiveTokens.push({
userId: recipients[index].id,
token: token
});
}
});
// Bulk delete all inactive tokens
if (inactiveTokens.length > 0) {
await prisma.user.updateMany({
where: {
fcmToken: { in: inactiveTokens.map(t => t.token) }
},
data: {
fcmToken: null,
fcmTokenStatus: 'INACTIVE',
fcmTokenInactivatedAt: new Date()
}
});
console.log(`Removed ${inactiveTokens.length} inactive tokens`);
}
return {
successCount: response.successCount,
failureCount: response.failureCount,
inactiveTokensRemoved: inactiveTokens.length
};
}
messaging/registration-token-not-registered: Previously valid token now inactive

What this error means

The "messaging/registration-token-not-registered: Previously valid token now inactive" error is a specific variant of the registration-token-not-registered error. It explicitly indicates that FCM previously recognized this token as valid but it has since become inactive and unusable. This error message is particularly informative because it confirms the token was never malformed or fake—it was legitimately registered at some point. The inactivity status means the token's registration has expired or been invalidated by FCM due to device or app state changes. This is a normal part of FCM's token lifecycle and is expected in production applications. The key to handling this gracefully is to remove these tokens immediately and ensure your system has mechanisms to obtain fresh tokens from active users.

Typical symptoms

  • Push notifications fail with "Previously valid token now inactive" message
  • Error occurs specifically for tokens that previously worked
  • Batch sends show failures for some tokens but not others
  • Token status changes from working to inactive without user action on their device
  • Error appears after user has been inactive for days or weeks
  • Application logs show increasing inactive token errors over time
  • Notifications stop reaching users who haven't opened the app recently

Common causes

  1. Device or app has been inactive for 30+ days, causing token to expire naturally
  2. User uninstalled the app after previously installing it
  3. User cleared app data or cache in device settings
  4. User cleared app storage or disabled notifications
  5. App was uninstalled and reinstalled, generating a new token while old one in database
  6. App updated and lost previously stored token or failed to re-register
  7. User disabled notifications for the app, causing token invalidation
  8. Device was factory reset, clearing all app tokens
  9. APNS certificate expired on iOS (iOS-specific token lifecycle)
  10. FCM server-side token refresh cycle invalidated old tokens
  11. Device removed from Google Play Services or disabled FCM

How to fix "messaging/registration-token-not-registered: Previously valid token now inactive"

1Immediately delete the inactive token from your database

When you receive the "previously valid token now inactive" error, the token cannot be reactivated. Remove it from your database immediately to prevent repeated failed sends and improve application efficiency.

javascript
// Node.js Admin SDK - Single token deletion
async function handleInactiveToken(userId, fcmToken) {
  try {
    const response = await admin.messaging().send({
      token: fcmToken,
      notification: {
        title: 'Test',
        body: 'Test'
      }
    });
  } catch (error) {
    if (error.code === 'messaging/registration-token-not-registered') {
      console.log('Token is inactive, removing from database:', fcmToken);

      // Delete the inactive token
      await prisma.user.update({
        where: { id: userId },
        data: {
          fcmToken: null,
          fcmTokenStatus: 'INACTIVE',
          fcmTokenInactivatedAt: new Date()
        }
      });

      return { success: false, reason: 'Token removed - was inactive' };
    }
    throw error;
  }
}

// Batch send with inactive token detection
async function sendBatchNotifications(recipients, message) {
  const response = await admin.messaging().sendEachForMulticast({
    tokens: recipients.map(r => r.fcmToken),
    notification: message
  });

  // Handle failures for inactive tokens
  const inactiveTokens = [];

  response.responses.forEach((resp, index) => {
    if (!resp.success && resp.error.code === 'messaging/registration-token-not-registered') {
      const token = recipients[index].fcmToken;
      console.warn('Inactive token detected:', token.substring(0, 20) + '...');
      inactiveTokens.push({
        userId: recipients[index].id,
        token: token
      });
    }
  });

  // Bulk delete all inactive tokens
  if (inactiveTokens.length > 0) {
    await prisma.user.updateMany({
      where: {
        fcmToken: { in: inactiveTokens.map(t => t.token) }
      },
      data: {
        fcmToken: null,
        fcmTokenStatus: 'INACTIVE',
        fcmTokenInactivatedAt: new Date()
      }
    });

    console.log(`Removed ${inactiveTokens.length} inactive tokens`);
  }

  return {
    successCount: response.successCount,
    failureCount: response.failureCount,
    inactiveTokensRemoved: inactiveTokens.length
  };
}

Important: Don't retry sends to inactive tokens. Each retry wastes resources and API quota. Delete them once detected.

2Implement robust token refresh detection on client

Ensure your client app captures token refresh events and immediately sends the new token to your server. Inactive tokens often result from the app losing sync with the server about which token is current.

Web (JavaScript):

javascript
import { getMessaging, getToken, onMessage } from 'firebase/messaging';
import { initializeApp } from 'firebase/app';

const app = initializeApp(firebaseConfig);
const messaging = getMessaging(app);

async function registerFCMToken() {
  try {
    const token = await getToken(messaging, {
      vapidKey: 'YOUR_VAPID_KEY'
    });

    if (token) {
      console.log('FCM Token obtained:', token);

      // Send to server immediately
      await fetch('/api/update-fcm-token', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          token: token,
          timestamp: new Date().toISOString()
        })
      });
    }
  } catch (error) {
    console.error('Failed to get FCM token:', error);
  }
}

// Call on app initialization
registerFCMToken();

// Listen for token refresh (important!)
messaging.onTokenRefresh(async () => {
  console.log('FCM token refreshed');
  await registerFCMToken();
});

Android (Kotlin):

kotlin
class MyFirebaseMessagingService : FirebaseMessagingService() {
  override fun onNewToken(token: String) {
    super.onNewToken(token)
    Log.d("FCM", "Token refreshed: $token")

    // Send immediately to server
    sendTokenToServer(token)

    // Also save to SharedPreferences as backup
    saveTokenLocally(token)
  }

  private fun sendTokenToServer(token: String) {
    val retrofit = RetrofitClient.getInstance()
    val apiService = retrofit.create(ApiService::class.java)

    apiService.updateFcmToken(FcmTokenRequest(token))
      .enqueue(object : Callback<Void> {
        override fun onResponse(call: Call<Void>, response: Response<Void>) {
          Log.d("FCM", "Token sent to server: ${response.code()}")
        }

        override fun onFailure(call: Call<Void>, t: Throwable) {
          Log.e("FCM", "Failed to send token", t)
          // Retry later or queue for sync
        }
      })
  }

  private fun saveTokenLocally(token: String) {
    val prefs = getSharedPreferences("fcm", Context.MODE_PRIVATE)
    prefs.edit().putString("fcm_token", token).apply()
  }
}

iOS (Swift):

swift
import Firebase
import FirebaseMessaging

class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {

  func application(_ application: UIApplication,
                   didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

    FirebaseApp.configure()
    Messaging.messaging().delegate = self

    // Get initial token
    Messaging.messaging().token { token, error in
      if let token = token {
        print("FCM Token: \(token)")
        self.sendTokenToServer(token)
      }
    }

    return true
  }

  func messaging(_ messaging: Messaging,
                didReceiveRegistrationToken fcmToken: String?) {
    if let token = fcmToken {
      print("Token refreshed: \(token)")
      sendTokenToServer(token)
    }
  }

  private func sendTokenToServer(_ token: String) {
    let request = URLRequest(url: URL(string: "https://yourapp.com/api/update-fcm-token")!)
    var request = request
    request.httpMethod = "POST"
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.httpBody = try? JSONSerialization.data(withJSONObject: ["token": token])

    URLSession.shared.dataTask(with: request).resume()
  }
}
3Create an API endpoint to receive token updates

Your clients need a reliable way to send updated tokens to your server. This endpoint should validate tokens and update the database.

javascript
// Express.js
import express from 'express';
import { authenticateUser } from './auth';
import { prisma } from './db';

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 150+ chars)
    if (!token || typeof token !== 'string' || token.length < 100) {
      return res.status(400).json({
        error: 'Invalid token format',
        receivedLength: token?.length
      });
    }

    // Update user's token
    const user = await prisma.user.update({
      where: { id: userId },
      data: {
        fcmToken: token,
        fcmTokenUpdatedAt: new Date(),
        fcmTokenStatus: 'ACTIVE',
        fcmTokenInactivatedAt: null
      },
      select: { id: true, email: true }
    });

    console.log(`Token updated for user ${user.email}`);

    return res.json({
      success: true,
      message: 'FCM token updated successfully',
      tokenHash: token.substring(0, 10) + '...'
    });

  } catch (error) {
    console.error('Token update error:', error);
    return res.status(500).json({
      error: 'Failed to update token',
      message: error.message
    });
  }
});

// Next.js API Route
import { NextRequest, NextResponse } from 'next/server';
import { getSession } from '@/lib/auth';
import { prisma } from '@/lib/db';

export async function POST(request: NextRequest) {
  try {
    const session = await getSession(request);
    if (!session?.user?.id) {
      return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
    }

    const { token } = await request.json();

    if (!token || typeof token !== 'string' || 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(),
        fcmTokenStatus: 'ACTIVE'
      }
    });

    return NextResponse.json({
      success: true,
      message: 'Token updated'
    });

  } catch (error) {
    console.error('Token update failed:', error);
    return NextResponse.json(
      { error: 'Token update failed' },
      { status: 500 }
    );
  }
}

Call this endpoint:
- When user logs in
- On app startup (to sync server with current token)
- Whenever token refresh is detected
- Periodically (e.g., every 24 hours) to ensure sync

4Check token age before sending and skip old ones

Prevent accumulating inactive tokens by checking how long it's been since a token was last updated. Inactive tokens are often very old.

javascript
async function shouldSendToToken(userRecord) {
  // Skip null tokens
  if (!userRecord.fcmToken) return false;

  // Skip if explicitly marked inactive
  if (userRecord.fcmTokenStatus === 'INACTIVE') return false;

  // Skip if recently marked as inactive
  if (userRecord.fcmTokenInactivatedAt) return false;

  // Skip if token hasn't been updated in 30+ days (likely inactive)
  const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
  if (userRecord.fcmTokenUpdatedAt < thirtyDaysAgo) {
    console.warn(`Skipping token not updated in 30+ days for user ${userRecord.id}`);
    return false;
  }

  return true;
}

async function sendNotifications(userIds, message) {
  // Fetch users with their token info
  const users = await prisma.user.findMany({
    where: { id: { in: userIds } },
    select: {
      id: true,
      fcmToken: true,
      fcmTokenStatus: true,
      fcmTokenUpdatedAt: true,
      fcmTokenInactivatedAt: true
    }
  });

  // Filter out users with inactive/old tokens
  const validUsers = users.filter(user => shouldSendToToken(user));

  console.log(`Sending to ${validUsers.length} of ${users.length} users (skipped ${users.length - validUsers.length} with inactive tokens)`);

  if (validUsers.length === 0) {
    console.log('No users with active tokens');
    return { success: true, sent: 0, skipped: users.length };
  }

  // Send to valid tokens
  const response = await admin.messaging().sendEachForMulticast({
    tokens: validUsers.map(u => u.fcmToken),
    notification: message
  });

  return {
    success: true,
    sent: response.successCount,
    failed: response.failureCount,
    skipped: users.length - validUsers.length
  };
}
5Set up automated cleanup of inactive tokens

Implement a scheduled task to detect and remove tokens that have been inactive for extended periods, preventing database bloat and reducing lookup times.

javascript
// Clean up tokens not updated in 30 days
async function cleanupInactiveTokens() {
  const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);

  const result = await prisma.user.updateMany({
    where: {
      fcmToken: { not: null },
      fcmTokenUpdatedAt: { lt: thirtyDaysAgo }
    },
    data: {
      fcmToken: null,
      fcmTokenStatus: 'INACTIVE',
      fcmTokenInactivatedAt: new Date()
    }
  });

  console.log(`Cleaned up ${result.count} inactive tokens`);
  return result.count;
}

// Using node-cron for scheduling
import cron from 'node-cron';

// Run every day at 2 AM
cron.schedule('0 2 * * *', async () => {
  console.log('Running FCM token cleanup...');
  try {
    const cleaned = await cleanupInactiveTokens();
    console.log(`Cleanup completed: ${cleaned} tokens removed`);
  } catch (error) {
    console.error('Cleanup failed:', error);
  }
});

// Using Bull Queue for more robust scheduling
import Bull from 'bull';

const cleanupQueue = new Bull('fcm-cleanup', {
  redis: { host: 'localhost', port: 6379 }
});

cleanupQueue.process(async (job) => {
  return cleanupInactiveTokens();
});

// Schedule to run daily
cleanupQueue.add(
  {},
  {
    repeat: {
      cron: '0 2 * * *',  // 2 AM daily
      timezone: 'UTC'
    }
  }
);

// Airflow or other orchestration
// Add as a daily DAG task to clean up inactive tokens

You can also log which tokens become inactive for analytics:

javascript
async function recordTokenInactivity(userId, token, reason) {
  await prisma.fcmTokenLog.create({
    data: {
      userId,
      tokenPreview: token.substring(0, 20) + '...',
      reason: reason,
      timestamp: new Date()
    }
  });
}

// Use in your error handling
catch (error) {
  if (error.code === 'messaging/registration-token-not-registered') {
    await recordTokenInactivity(userId, token, 'SEND_FAILED');
    // ... delete token
  }
}

Advanced notes

Token inactivity vs. expiration: FCM doesn't have explicit token expiration dates like JWT tokens. Instead, tokens become "inactive" when FCM detects the device/app is no longer actively connected (inactivity, uninstall, etc.). This is better for user privacy—tokens don't "expire"—they just become invalid naturally.

Platform-specific inactivity timelines: Android and web tokens typically go inactive after 30+ days of device inactivity. iOS tokens can become inactive when the APNS certificate expires or the device loses connectivity to Apple's push service. Always assume any token older than 30 days of inactivity may be inactive.

Batch send failure analysis: If you see a sudden spike in "previously valid token now inactive" errors during a batch send, it may indicate:
- Mass app uninstalls (new version bug, security issue, etc.)
- Device updates that invalidated tokens (OS update, factory reset wave)
- Your app lost token refresh capability (regression in recent release)

Track the percentage of tokens failing and alert if it exceeds 10% in a single batch.

Database schema for token tracking: Recommended fields to track token lifecycle:

prisma
model User {
  id String @id @default(cuid())

  // FCM Token management
  fcmToken String? @unique
  fcmTokenStatus String @default("ACTIVE")  // ACTIVE, INACTIVE, INVALID
  fcmTokenUpdatedAt DateTime?
  fcmTokenInactivatedAt DateTime?
  fcmTokenInactivationReason String?
}

model FcmTokenLog {
  id String @id @default(cuid())
  userId String
  tokenPreview String  // First 20 chars for privacy
  reason String        // SEND_FAILED, AUTO_CLEANUP, USER_UNINSTALL, etc.
  timestamp DateTime @default(now())

  @@index([userId])
  @@index([timestamp])
}

Privacy considerations: When logging token inactivity, never store complete tokens. Use just the first 20 characters for privacy and security. Also implement retention policies (delete logs after 30 days) to comply with GDPR.

Metrics to monitor for early warning:
- Daily percentage of inactive token errors vs. total sends
- Average token age in your database
- Days since last token update for users who still have tokens stored
- Token update success rate (can indicate a regression in client code)

Frequently asked questions

What does "messaging/registration-token-not-registered: Previously valid token no" mean?
The "messaging/registration-token-not-registered: Previously valid token now inactive" error is a specific variant of the registration-token-not-registered error. It explicitly indicates that FCM previously recognized this token as valid but it has since become inactive and unusable. This error message is particularly informative because it confirms the token was never malformed or fake—it was legitimately registered at some point.
How do I fix "messaging/registration-token-not-registered: Previously valid token no"?
This error occurs when Firebase Cloud Messaging detects that a registration token was once valid but has become inactive. The token typically becomes inactive after the app is uninstalled, user data is cleared, the device remains inactive for extended periods, or after a token refresh cycle. Fixing this requires removing stale tokens and implementing proper token lifecycle management.
What causes "messaging/registration-token-not-registered: Previously valid token no"?
Device or app has been inactive for 30+ days, causing token to expire naturally User uninstalled the app after previously installing it User cleared app data or cache in device settings
Is the "messaging/registration-token-not-registered: Previously valid token no" 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