What the error actually means

When the Firebase console or the FCM API reports that the APNs token is not set, it means Firebase has no working channel to Apple Push Notification service for your iOS app. The message is accepted, but there is no credential to hand it to, so it is silently dropped rather than delivered.

The confusing part is that the Android build often works at the same time. Firebase treats APNs as a separate credential, so a correct Android setup tells you nothing about iOS.

Step 1 — Confirm the iOS bundle identifier matches

The APNs topic is derived from your app bundle ID. If the bundle ID in Xcode, in the App Store Connect app record and in the Firebase iOS app configuration do not match exactly, registration fails quietly.

  • Xcode target Bundle Identifier
  • Firebase iOS app Bundle ID
  • App Store Connect app record Bundle ID
  • The topic used by your APNs key (com.company.app)
This is the most common cause. Check it before touching certificates.

Step 2 — Prefer an APNs auth key over a certificate

Apple issues two kinds of APNs credential: a push certificate tied to a specific app, and an auth key (a .p8 file) valid for every app in your development team. The auth key does not expire annually and does not need regenerating per app, which removes a whole class of "notifications stopped working" incidents.

Create the key once in the Apple Developer portal under Keys, enable Apple Push Notifications service, and download the .p8 file. You can only create a limited number of these keys, so store it somewhere safe.

Step 3 — Upload the key to Firebase

In Firebase, open Project settings, Cloud Messaging, and upload the APNs authentication key with its key ID and team ID. Firebase uses this key to negotiate with APNs on every send.

If you previously uploaded a certificate, remove it. Two conflicting credentials produce intermittent delivery rather than an obvious failure.

Firebase Console
  -> Project settings
     -> Cloud Messaging
        -> Apple app configuration
           -> Upload .p8 authentication key
              Key ID:    <10 characters>
              Team ID:   <10 characters>
              Bundle ID: com.example.app

Step 4 — Register the device token from the Flutter side

The client must request notification permission and then register for a token, and that token must be sent to your backend and stored against the user. A token that is never uploaded can never be targeted.

FirebaseMessaging messaging = FirebaseMessaging.instance;

await messaging.requestPermission(alert: true, badge: true, sound: true);

String? token = await messaging.getToken();
if (token != null) {
  await api.registerDeviceToken(token);
}

messaging.onTokenRefresh.listen((newToken) {
  api.registerDeviceToken(newToken);
});
Tokens rotate. If onTokenRefresh is ignored, notifications stop after a while with no error anywhere.

Step 5 — Handle the notification correctly in the app

Notification permission must be requested before a token is issued, and the app must handle both foreground and background messages. iOS additionally requires the notification category and, for rich media, a notification service extension.

Register notification categories and actions at startup so that tapping an action button behaves as expected rather than doing nothing.

Step 6 — Verify before blaming code

Use Firebase Cloud Messaging\u2019s own test send from the console against a single token. If that fails, the problem is the APNs credential or bundle ID. If the console test succeeds but your server-side send fails, the problem is your server payload.

Common payload faults that APNs rejects include a malformed apns-topic, a missing apns-priority for time-sensitive alerts, and a payload exceeding the size limit.

{
  "message": {
    "token": "DEVICE_TOKEN",
    "notification": {
      "title": "Order update",
      "body": "Your order has shipped"
    },
    "android": { "priority": "high" },
    "apns": {
      "headers": {
        "apns-topic": "com.example.app",
        "apns-priority": "10"
      },
      "payload": { "aps": { "sound": "default" } }
    }
  }
}

A checklist that catches most of it

Run this list top to bottom before investigating anything exotic.

  • Bundle ID matches across Xcode, Firebase and App Store Connect
  • APNs auth key uploaded to Firebase with correct Key ID and Team ID
  • No stale APNs certificate left alongside the auth key
  • Notification permission requested before getToken()
  • Token uploaded to the backend and refreshed on rotation
  • apns-topic header set to the bundle ID on server-side sends
  • Test send from the Firebase console to a single token succeeds