Skip to main content

Notifications (iOS)

Paylisher delivers user engagement messages to iOS through Firebase Cloud Messaging (FCM), which relays them to Apple Push Notification service (APNs). The SDK supports two delivery types:

TypeDescription
PUSHStandard OS-level push notification shown in the notification tray/banner.
IN-APPA custom overlay rendered inside the app (banner, modal, fullscreen, carousel).

The SDK routes each payload to the correct renderer based on the type field in the notification data.

This guide targets Paylisher iOS 1.8.4. For the Android counterpart, see the Android Notification Integration guide.


Prerequisites

Dependencies

Push and in-app messaging build on top of Firebase. Add FirebaseMessaging alongside the Paylisher SDK.

CocoaPods — add to your Podfile:

pod 'Paylisher', '~> 1.8.4'
pod 'FirebaseMessaging'

Swift Package Manager — in Xcode → File → Add Packages:

https://github.com/paylisher/PAYLISHER-SDK-IOS

Minimum deployment target: iOS 13.0.

Firebase project setup

  1. Go to the Firebase ConsoleAdd an iOS app with your Bundle ID.
  2. Download GoogleService-Info.plist and add it to your Xcode project target.
  3. In Firebase Console → Project Settings → Cloud Messaging → Apple app configuration, upload your APNs Authentication Key (.p8) or an APNs Certificate.
  4. In the Paylisher dashboard, enter the same APNs credentials under Settings → Push Notifications → iOS.

Xcode project setup

Capabilities

In Xcode → select your Target → Signing & Capabilities, add:

CapabilitySetting
Push NotificationsJust enable it.
Background ModesCheck Remote notifications.
App GroupsAdd group.com.yourcompany.yourapp.

The App Group lets the SDK share analytics data with the notification service extension and persist notification state.

Info.plist

<!-- Disable Firebase's automatic swizzling — required when using a custom AppDelegate -->
<key>FirebaseAppDelegateProxyEnabled</key>
<false/>

<!-- Enable background push reception -->
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
</array>

Entitlements

Your .entitlements file should contain:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- development for debug builds, production for App Store builds -->
<key>aps-environment</key>
<string>development</string>
<!-- Must match the App Group registered in Xcode Capabilities -->
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.yourcompany.yourapp</string>
</array>
</dict>
</plist>

AppDelegate integration

Initialize Firebase and Paylisher

Initialize Firebase before the Paylisher SDK.

// AppDelegate.swift
import UIKit
import UserNotifications
import FirebaseCore
import FirebaseMessaging
import Paylisher

class AppDelegate: NSObject, UIApplicationDelegate,
UNUserNotificationCenterDelegate,
MessagingDelegate {

func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
FirebaseApp.configure() // 1. Firebase first
setupPaylisher() // 2. Paylisher SDK
setupNotifications(application) // 3. Notifications
return true
}

private func setupPaylisher() {
let config = PaylisherConfig(apiKey: "YOUR_API_KEY", host: "https://us.i.paylisher.com")
config.debug = false // true during development
config.captureApplicationLifecycleEvents = true
config.captureScreenViews = true
PaylisherSDK.shared.setup(config)

// Configure CoreData with the App Group so data can be shared with the extension
CoreDataManager.shared.configure(appGroupIdentifier: "group.com.yourcompany.yourapp")
}
}

Request notification permission

private func setupNotifications(_ application: UIApplication) {
UNUserNotificationCenter.current().delegate = self
Messaging.messaging().delegate = self
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, _ in
guard granted else { return }
DispatchQueue.main.async {
application.registerForRemoteNotifications()
}
}
}

Register the FCM token

The backend sends push via the Firebase Admin SDK, so it needs the FCM token (not the raw APNs device token). Forward the APNs token to Firebase, then register the resulting FCM token with Paylisher.

// APNs token → Firebase
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
Messaging.messaging().apnsToken = deviceToken
}

// FCM token → Paylisher
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
guard let token = fcmToken, !token.isEmpty else { return }
UserDefaults.standard.set(token, forKey: "fcm_token") // persist for identify
PaylisherSDK.shared.registerFCMToken(token) // enables targeted push
}

registerFCMToken(...) stores the token and emits a $fcm_token_registered event for token-lifecycle tracking.

SwiftUI entry point

Wire up the AppDelegate in your SwiftUI App:

@main
struct YourApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup { ContentView() }
}
}

Displaying notifications

Foreground in-app messages

When the app is in the foreground, route Paylisher in-app messages to the SDK's renderer; show everything else as a system banner.

func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
) {
let userInfo = notification.request.content.userInfo
let type = userInfo["type"] as? String ?? ""
let source = userInfo["source"] as? String ?? ""

if type == "IN-APP" && source == "Paylisher" {
let windowScene = UIApplication.shared.connectedScenes
.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene
if let scene = windowScene {
let content = notification.request.content.mutableCopy() as! UNMutableNotificationContent
NotificationManager.shared.customNotification(
windowScene: scene, userInfo: userInfo, content, notification.request
) { _ in }
completionHandler([]) // suppress the system banner; the SDK shows the overlay
} else {
completionHandler([.sound, .list, .banner, .badge])
}
} else {
completionHandler([.sound, .list, .banner, .badge])
}
}

Background in-app messages

To render in-app messages delivered while the app is in the background, implement didReceiveRemoteNotification:

func application(
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
let type = userInfo["type"] as? String ?? ""
let source = userInfo["source"] as? String ?? ""

if type == "IN-APP" && source == "Paylisher" {
DispatchQueue.main.async {
let windowScene = UIApplication.shared.connectedScenes
.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene
if let scene = windowScene {
let content = UNMutableNotificationContent()
content.userInfo = userInfo
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
NotificationManager.shared.customNotification(
windowScene: scene, userInfo: userInfo, content, request
) { _ in }
}
}
}
completionHandler(.newData)
}

When the user taps (or dismisses) a notification, hand the response to the SDK. A single call to handleNotificationResponse(_:) captures the analytics event (notificationOpen on tap, notificationDismiss on dismiss), deduplicates it, and opens the deep link carried in the payload's action field:

func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
) {
// Returns true if the notification belonged to Paylisher.
_ = NotificationManager.shared.handleNotificationResponse(response)
completionHandler()
}

See the API reference below for exactly what this method does. Deep links, deferred deep links, and journey attribution are covered in depth in the Deep Linking guide.


Identifying the user with their token

After login, identify the user and attach their FCM token and device ID so the backend can target them:

func loginUser(userId: String) {
var properties: [String: Any] = [
"platform": "ios",
"deviceID": UIDevice.current.identifierForVendor?.uuidString ?? ""
]
if let token = UserDefaults.standard.string(forKey: "fcm_token") {
properties["token"] = token
}
PaylisherSDK.shared.identify(userId, userProperties: properties)
}

To log out:

PaylisherSDK.shared.reset()

Reference

Notification types

type valuePlatformDescription
PUSHAndroid / iOSStandard push notification shown in the tray/banner.
IN-APPAndroid / iOSCustom in-app overlay rendered inside the app.
ACTION-BASEDAndroidPush with interactive action buttons.

Payload fields

FieldTypeDescription
typeStringPUSH, IN-APP, ACTION-BASED.
sourceStringAlways "Paylisher".
titleJSON StringLocalized title map, e.g. {"en": "Hello", "tr": "Merhaba"}.
messageJSON StringLocalized body map.
imageUrlStringURL of the notification image.
actionStringDeep link URL opened on tap.
silentBooleantrue = no sound, low priority.
buttonsJSON ArrayAction buttons (label, action, url).
defaultLangStringFallback language code, e.g. "en".
conditionJSON StringTargeting conditions (see below).

Condition object

{
"target": "HomeScreen,ProfileScreen", // screen name(s), empty = all screens
"displayTime": 1710000000000, // Unix ms, show after this time
"expireDate": 1710086400000, // Unix ms, do not show after this
"delay": 3 // seconds to delay display
}

In-app layout types

Layout typeDescription
bannerHorizontal bar at the top, center, or bottom of the screen.
modalCentered dialog with an overlay backdrop.
fullscreenFull-screen takeover.
modal-carouselCentered multi-slide carousel.
fullscreen-carouselFull-screen multi-slide carousel.

Layout blocks

Each layout is composed of ordered blocks:

BlockDescription
textLocalized text with font size, color, and alignment.
imageRemote image with an aspect ratio.
buttonGroup1–3 action buttons (open URL, dismiss, copy text).
spacerFixed or flexible vertical space.

Set style.verticalPosition in the layout: top, center (default), or bottom.

Analytics events

The SDK automatically captures these events — they appear in your Paylisher dashboard:

EventTrigger
notificationOpenUser taps a push notification.
notificationDismissUser dismisses a push notification.
inappMessageReadAn in-app message is displayed.
$fcm_token_registeredFCM token registered via registerFCMToken().

You can also capture custom events:

PaylisherSDK.shared.capture("button_clicked", properties: ["screen": "home"])

API reference

The key public methods used for notifications. Notification helpers live on NotificationManager.shared; SDK-level methods live on PaylisherSDK.shared. All handle… helpers are safe to call for every notification — they return false (and do nothing) for notifications that don't belong to Paylisher.

MethodWhat it does
NotificationManager.shared.handleNotificationResponse(_ response: UNNotificationResponse) -> BoolCall from userNotificationCenter(_:didReceive:withCompletionHandler:). Handles a notification tap or dismiss: captures the notificationOpen / notificationDismiss event, deduplicates it, and opens the deep link in the payload's action field. Returns true if the notification belonged to Paylisher.
NotificationManager.shared.handleForegroundPresentation(_ notification: UNNotification) -> BoolCall from userNotificationCenter(_:willPresent:withCompletionHandler:) before the completion handler. Captures the notificationReceived event for a notification shown while the app is in the foreground. Returns true if tracked.
NotificationManager.shared.handleLaunchOptions(_ launchOptions:) -> BoolCall from application(_:didFinishLaunchingWithOptions:) after setup. Captures a notificationOpen when the app is cold-launched by tapping a notification iOS displayed itself. Deduplicated against the didReceive delegate so the event fires exactly once.
NotificationManager.shared.customNotification(windowScene:userInfo:_:_:_:)Renders a Paylisher in-app overlay (banner / modal / fullscreen / carousel) for the given payload. Call from willPresent and/or didReceiveRemoteNotification.
PaylisherSDK.shared.registerFCMToken(_ fcmToken: String)Registers the device's FCM token with Paylisher so the backend can target push to this device. Emits a $fcm_token_registered event.
PaylisherSDK.shared.handleDeepLink(_ url: URL) -> BoolRoutes a deep link URL through the SDK (journey attribution + pending-link handling). Returns true if handled.

For complete analytics, you can additionally call handleForegroundPresentation in willPresent and handleLaunchOptions in didFinishLaunchingWithOptions — both are optional and only add the corresponding events.


Troubleshooting

Push notifications not received

  • GoogleService-Info.plist is added to your app target.
  • APNs key/certificate is uploaded to Firebase and the Paylisher dashboard.
  • FirebaseApp.configure() is called before PaylisherSDK.shared.setup(...).
  • The device has an internet connection.

In-app messages not appearing

  • Ensure NotificationManager.shared.customNotification(...) is called in both willPresent and didReceiveRemoteNotification.
  • Check that windowScene resolves correctly (the app must be in the foreground).
  • In-app messages are deduplicated with a 24-hour TTL — delete the app or wait 24h to re-test the same pushId.

FCM token not registering

// Must be called AFTER FirebaseApp.configure()
Messaging.messaging().token { token, error in
if let error = error {
print("Error fetching FCM token: \(error)")
} else if let token = token {
print("FCM token: \(token)")
}
}