---
name: intercom-flutter-sendtoken-required-on-ios
description: |
  Fix silent iOS Intercom push delivery failure when `Intercom.instance.sendTokenToIntercom()`
  is gated behind a `Platform.isAndroid` check (so it never runs on iOS). Use when: (1) Flutter
  app has APNS token correctly retrieved on iOS (`[Push] APNS token: ...` appears in logs),
  (2) `IntercomService.sendPushToken` or similar method skips the actual Intercom call on iOS,
  (3) code contains a pattern like `if (Platform.isAndroid) { await Intercom.instance.sendTokenToIntercom(token); }`
  with a comment claiming iOS handles it "automatically via APNs" or "via the native SDK",
  (4) Intercom messenger pushes work on Android but never arrive on iOS despite the iOS
  device having a valid APNS token, (5) Intercom dashboard user-device list shows no iOS
  device registered. Root cause: the misleading comment is technically correct for NATIVE
  iOS apps (where the Intercom iOS AppDelegate auto-detects the APNS device token via
  `application:didRegisterForRemoteNotificationsWithDeviceToken:`) but FALSE for Flutter
  apps because firebase_messaging owns the APNs registration callback chain — the native
  Intercom AppDelegate never gets called. Intercom's iOS native code therefore never sees
  the token unless Dart explicitly forwards it via `sendTokenToIntercom`. Intercom's
  official Flutter docs call `sendTokenToIntercom` unconditionally on both platforms.
author: Claude Code
version: 1.0.0
date: 2026-05-14
---

# Intercom Flutter on iOS: Must Call `sendTokenToIntercom` Explicitly

## Problem

Your Flutter app correctly fetches the iOS APNS token via
`FirebaseMessaging.instance.getAPNSToken()` and logs confirm it. But Intercom
messenger pushes still never arrive on iOS, and the user's Intercom dashboard
device list shows no iOS device registered.

The Android side works fine — same `sendPushToken` method, just runs through
a different platform branch.

## Trigger Conditions

ALL of these together:

- iOS log shows `[Push] APNS token: <prefix>...` — token is retrieved successfully
- iOS log shows `[IntercomService] FCM token forwarded` (or similar) — message is
  printed even though the actual native call was skipped
- Code contains a pattern like:
  ```dart
  Future<void> sendPushToken(String token) async {
    try {
      if (Platform.isAndroid) {
        await Intercom.instance.sendTokenToIntercom(token);
      }
      // iOS tokens are handled automatically by Intercom SDK via APNs   ← THE LIE
    } catch (e) { ... }
  }
  ```
- iOS device permissions are granted, Xcode "Push Notifications" capability is
  enabled, APNs key uploaded to Intercom dashboard
- Android pushes from the same Intercom workspace work fine

## Solution

Remove the platform guard. Always call `sendTokenToIntercom`:

```dart
Future<void> sendPushToken(String token) async {
  if (!_isInitialized) return;
  try {
    // Unconditional — Intercom's Flutter plugin does NOT auto-detect APNS
    // on iOS because firebase_messaging owns the registration callback.
    await Intercom.instance.sendTokenToIntercom(token);
    if (kDebugMode) {
      final label = Platform.isIOS ? 'APNS' : 'FCM';
      debugPrint('[IntercomService] $label token forwarded to Intercom');
    }
  } catch (e) {
    if (kDebugMode) {
      debugPrint('[IntercomService] sendPushToken failed (non-critical): $e');
    }
  }
}
```

Then make sure the caller passes the **right** token on iOS:

```dart
final intercomToken = Platform.isIOS
    ? await fm.getAPNSToken()  // raw APNS device token
    : await fm.getToken();     // FCM token

if (intercomToken != null) {
  await IntercomService.sendPushToken(intercomToken);
}
```

That's two separate concerns:

1. Get the right token type (covered by sibling skill
   `intercom-flutter-apns-token-ios`)
2. Actually forward it via `sendTokenToIntercom` (this skill)

Both must be correct for iOS push to work.

## Why The Misleading Comment Is So Sticky

The comment "iOS tokens are handled automatically by Intercom SDK via APNs"
isn't wholesale wrong — it's true for **native iOS** apps. There, you set up
your `AppDelegate.swift` like:

```swift
func application(_ application: UIApplication,
                 didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
  Intercom.setDeviceToken(deviceToken, failure: nil)
}
```

…and Intercom's iOS SDK reads the APNS token directly from iOS's registration
callback. No Dart code needed. The comment in your `IntercomService` likely
came from someone who:

- Read Intercom's native iOS docs (which describe this auto-detection)
- Assumed the same behavior carries through the Flutter wrapper
- Wrote the platform guard "to avoid duplicating what the iOS SDK already does"

The problem: **in a Flutter app, firebase_messaging owns
`didRegisterForRemoteNotificationsWithDeviceToken`**. The Intercom iOS
AppDelegate hook never fires because the app's actual AppDelegate is
Flutter's, with firebase_messaging registered as the notification delegate.
So the only way the Intercom iOS SDK ever learns of the APNS token is via
the explicit `sendTokenToIntercom` MethodChannel call.

## Verification

1. Restart the app on a physical iOS device (push doesn't work on simulator)
2. Sign in
3. Look for this log line:
   ```
   [IntercomService] APNS token forwarded to Intercom
   ```
   (If you still see `[IntercomService] FCM token forwarded` on iOS, the
   platform guard is still in place.)
4. From the Intercom dashboard, send a test message to the logged-in user
5. Push should arrive on the iPhone within seconds
6. Bonus check: in Intercom dashboard → People → click the user → expand
   "Devices/Sessions" — you should now see an iOS device with a token that
   matches an APNS format (64 hex chars, no colons/underscores)

## Why The Log Message Was Also Misleading

You might find that even after the platform guard is gone, the debug log
still says `[IntercomService] FCM token forwarded` on iOS. That's a
secondary cosmetic bug — the log message was hardcoded as "FCM" regardless
of platform. Update it to be platform-aware:

```dart
final label = Platform.isIOS ? 'APNS' : 'FCM';
debugPrint('[IntercomService] $label token forwarded to Intercom');
```

Otherwise you'll spend time wondering why "FCM" is being forwarded on iOS
when it should be APNS — even though the right value was actually sent.

## Related Skills

- `intercom-flutter-apns-token-ios` — Use `getAPNSToken()` not `getToken()`
  on iOS when fetching the token to pass to `sendTokenToIntercom`. That
  skill is about token type; this skill is about whether the call happens
  at all. Both must be correct.
- `intercom-android-push-credential-invalid` — Different platform, but
  same root project (setting up Intercom push on a Flutter app). Covers
  the Firebase service account JSON gotcha.

## References

- Intercom Flutter package docs (push notifications section): the official
  example calls `Intercom.instance.sendTokenToIntercom(intercomToken)`
  unconditionally with no platform guard.
- Intercom iOS native SDK docs (the AUTO-DETECTION docs that mislead Flutter
  developers): describe `Intercom.setDeviceToken` being called from
  `AppDelegate.swift`. This auto-pickup does not work through Flutter
  because the app's actual AppDelegate is Flutter's, with firebase_messaging
  as the notification delegate.
- `firebase_messaging` iOS APNs registration flow: documented at
  https://firebase.flutter.dev/docs/messaging/notifications#apns-token
