I am facing an issue with Intercom Push Notification when it is a cold start (App is terminated, you receive a Push Notification and this notification will wake the App). While testing on Simulator that, if i am running my app from Xcode, terminate the App (on Simulator, not on Xcode), send a message to my test user, wait the push notification, receive it, tap on it but when i tap on it, Intercom Messenger does not opens the modal chat, it keeps me on my WorkListView (WorkList is a subview of RootView, that appears after SplashViews finish animation and transit to WorkListView).
I have already made installation (API Key, API ID and Push Notifications are enabled on Settings > Channel > Messenger > Install > Install for Mobiles), Apple Developer is already configured, when i open the Messenger on my App, i can send and receive messages. When App is open but in background i am able to receive and wake the app in the correct modal Intercom window chat, but only the cold start is not opening in the Modal Window Chat when Push Notification is tapped when App is terminated (not open on iOS)
Here is a little of the architecture of my App:
Simulator: iPhone 17 Pro with iOS 26
Intercom dependency version: 19.5.1
Issue is similar to (at least, the title):
and (at least, the title):
The Info.plist of the App has Push Notifications (IntercomAutoIntegratePushNotifications = NO), Remote Notifications, also Camera and Microphone (this both is not interesting but just saying i have it enabled) Push Notifications capability is enabled also app has the entitlement file
FYI: I have a legacy App written in Obj-c. It was updated to version 19.4.1 some time ago. Cold start on this legacy app is working fine. I tested, just in case, use version 19.4.1 in the problematic SwiftUI App presenting the cold start problem but did not worked too.
Best answer by Dara K
Hey ​@Vitor Gomes da SilvaÂ
Yes, this is the classic cold-start presentation timing issue. Intercom tries to present the chat before your SwiftUI hierarchy is ready or while a transition (Splash → Root) is in-flight.
Do this:
Defer handling until UI is active and the user is logged in
Save userInfo in didReceive if Intercom.isIntercomPushNotification(userInfo) and either UI isn’t ready or Intercom.isUserLoggedIn() is false.
When scenePhase becomes .active and after your splash/transition completes, handle it on main.
SwiftUI pattern:
AppDelegate: buffer pending push
In App: process when active and UI ready (after splash)
// AppDelegate final class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate { var pending: [AnyHashable:Any]? func application( app: UIApplication, didFinishLaunchingWithOptions : [UIApplication.LaunchOptionsKey:Any]? = nil) -> Bool { Intercom.setApiKey("<API_KEY>", forAppId: "<APPID>") UNUserNotificationCenter.current().delegate = self return true } func userNotificationCenter( c: UNUserNotificationCenter, didReceive r: UNNotificationResponse, withCompletionHandler done: @escaping () -> Void) { let info = r.notification.request.content.userInfo if Intercom.isIntercomPushNotification(info) { if Intercom.isUserLoggedIn() { pending = info  // defer until UI ready } else { // login, then set pending = info on success } } done() } }
// SwiftUI App @main struct MyApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate @Environment(.scenePhase) var phase @State private var uiReady = false
 var body: some Scene { WindowGroup { RootView() // Splash → WorkList inside .onAppear { uiReady = true } .onChange(of: phase) { p in guard p == .active, uiReady, let info = appDelegate.pending else { return } appDelegate.pending = nil DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { // let transitions finish Intercom.handlePushNotification(info) } } } } }
Tips to avoid the “frozen” splash:
Present after the splash animation completes (small delay, or post a “UIReady” notification when WorkList is visible).
Don’t call Intercom.present() in the push handler; Intercom.handlePushNotification does the presentation.
Ensure you’ve completed Intercom.loginUser(...) before handling the push; if login is async, chain handling in the success callback.
Always call handlePush on the main thread and return the UNUserNotificationCenter completionHandler immediately.
final class IntercomManager { static let shared = IntercomManager()
private let apiKey = "xxxxxxx" private let appId = "xxxxxxx"
private init() {}
var isUserLoggedIn: Bool { Intercom.isUserLoggedIn() }
// MARK: Setup func configure() { Intercom.setApiKey(apiKey, forAppId: appId) Intercom.setThemeOverride(.light) // TODO: Change to .system when we implement Dark Mode
#if DEBUG Intercom.enableLogging() #endif }
// MARK: User Management func loginUser(email: String) { let attributes = ICMUserAttributes() attributes.email = email
Intercom.loginUser(with: attributes) { result in switch result { case .success: #if DEBUG debugPrint("💬 🔓 ✅ Intercom Login successful with email: \(email)") #endif case .failure(let error): #if DEBUG debugPrint("💬 🔒 ❌ Intercom Error when logging in: \(error.localizedDescription)") #endif } } }
// MARK: Messenger func presentMessenger() { // Ensure push notification registration if user authorized after initial app launch PushNotificationManager.shared.requestPermissionIfNeeded()
// Always present Intercom messenger, regardless of push notification permission status Intercom.present() }
// MARK: Push Notifications // Called from AppDelegate when device token is received func registerDeviceToken(_ deviceToken: Data) { Intercom.setDeviceToken(deviceToken) { result in switch result { case .success: #if DEBUG debugPrint("💬 🔑 ✅ Device token registered successfully with Intercom") #endif case .failure(let error): #if DEBUG debugPrint("💬 🔑 ❌ Failed to register device token with Intercom: \(error.localizedDescription)") #endif } } } }
Somethings i changed from yesterday (small changes but, the overall problem still NOT fixed): I moved IntercomManager.shared.configure() from startup init file to first line inside didFinishLaunchingWithOptions of AppDelegate Also, noticed that, i was not making using sessionManager login information to login on Intercom in cases that user has already used app before and there was information about his account in the app on Defaults, so now, also on didFinishLaunchingWithOptions, after IntercomManager.shared.configure(), i use my SessionManager information to login the user on Intercom
It seems that RootView > SplashView > WorkList flow somehow is breaking presentation of the chat conversation (not confirmed, i am still investigating). The bottom ballon preview shows everytime, but the chat does not
Add same bundle id of the main project (just to reuse on Push Notifications tests configured on Intercom on Messenger Install panel
Added exactly the same AppDelegate, IntercomManager but using my emails and keys hardcoded
The project presented exactly the same problem as the main one Checking with Claude Code, i was suspecting that cold start push notifications does not have a scene yet to draw, thats why it was presenting the problem on the sample project, here is what the agent said:
userNotificationCenter(_:didReceive:) is called almost immediately
Intercom.handlePushNotification(userInfo) tries to present the chat
But the SwiftUI view hierarchy (ContentView) hasn't been built yet - there's no UIWindow with a ready root view controller
Intercom tries to present the chat, doesn't find a window/controller, and fails silently
When the app is already in the background, the UI already exists, so it works normally.
The solution
Save the notification payload during cold start and process it only when the UI is ready. Do you want me to implement this fix? The approach would be:
In didReceive, detect if it's cold start (UI not ready) and save the userInfo
Process the pending notification when ContentView appears (via onAppear) or when the scene becomes active (sceneDidBecomeActive)"
It fixed to me (in the simple project) by this:
class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate { /// Stores the push notification payload when the app is cold-started from a notification tap. /// The UI hierarchy is not ready at that point, so we defer handling until the scene is active. var pendingNotificationUserInfo: [AnyHashable: Any]? private var isUIReady = false
@main struct TestPushNotificationApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene { WindowGroup { ContentView() .onAppear { appDelegate.markUIReady() } } } }
I was able to replicate this on the main project but… since i have RootView » that shows a SplashView » Then shows WorkList (the view that i want to Chat appears on cold start) the chat shows immediately after cold start with Intercom push notification but it freezes the SplashView and does not let the App continue with RootView flow if i do not close the chat Speaking with analogy, there is something on the iOS Intercom Messenger dependency like this "if you do not load the chat right way the app start, i will not show to you"
Yes, this is the classic cold-start presentation timing issue. Intercom tries to present the chat before your SwiftUI hierarchy is ready or while a transition (Splash → Root) is in-flight.
Do this:
Defer handling until UI is active and the user is logged in
Save userInfo in didReceive if Intercom.isIntercomPushNotification(userInfo) and either UI isn’t ready or Intercom.isUserLoggedIn() is false.
When scenePhase becomes .active and after your splash/transition completes, handle it on main.
SwiftUI pattern:
AppDelegate: buffer pending push
In App: process when active and UI ready (after splash)
// AppDelegate final class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate { var pending: [AnyHashable:Any]? func application( app: UIApplication, didFinishLaunchingWithOptions : [UIApplication.LaunchOptionsKey:Any]? = nil) -> Bool { Intercom.setApiKey("<API_KEY>", forAppId: "<APPID>") UNUserNotificationCenter.current().delegate = self return true } func userNotificationCenter( c: UNUserNotificationCenter, didReceive r: UNNotificationResponse, withCompletionHandler done: @escaping () -> Void) { let info = r.notification.request.content.userInfo if Intercom.isIntercomPushNotification(info) { if Intercom.isUserLoggedIn() { pending = info  // defer until UI ready } else { // login, then set pending = info on success } } done() } }
// SwiftUI App @main struct MyApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate @Environment(.scenePhase) var phase @State private var uiReady = false
 var body: some Scene { WindowGroup { RootView() // Splash → WorkList inside .onAppear { uiReady = true } .onChange(of: phase) { p in guard p == .active, uiReady, let info = appDelegate.pending else { return } appDelegate.pending = nil DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { // let transitions finish Intercom.handlePushNotification(info) } } } } }
Tips to avoid the “frozen” splash:
Present after the splash animation completes (small delay, or post a “UIReady” notification when WorkList is visible).
Don’t call Intercom.present() in the push handler; Intercom.handlePushNotification does the presentation.
Ensure you’ve completed Intercom.loginUser(...) before handling the push; if login is async, chain handling in the success callback.
Always call handlePush on the main thread and return the UNUserNotificationCenter completionHandler immediately.