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:
| Type | Description |
|---|---|
| PUSH | Standard OS-level push notification shown in the notification tray/banner. |
| IN-APP | A 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
- Go to the Firebase Console → Add an iOS app with your Bundle ID.
- Download
GoogleService-Info.plistand add it to your Xcode project target. - In Firebase Console → Project Settings → Cloud Messaging → Apple app configuration, upload your APNs Authentication Key (
.p8) or an APNs Certificate. - 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:
| Capability | Setting |
|---|---|
| Push Notifications | Just enable it. |
| Background Modes | Check Remote notifications. |
| App Groups | Add 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_registeredevent 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)
}
Notification tap (deep link)
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 value | Platform | Description |
|---|---|---|
PUSH | Android / iOS | Standard push notification shown in the tray/banner. |
IN-APP | Android / iOS | Custom in-app overlay rendered inside the app. |
ACTION-BASED | Android | Push with interactive action buttons. |
Payload fields
| Field | Type | Description |
|---|---|---|
type | String | PUSH, IN-APP, ACTION-BASED. |
source | String | Always "Paylisher". |
title | JSON String | Localized title map, e.g. {"en": "Hello", "tr": "Merhaba"}. |
message | JSON String | Localized body map. |
imageUrl | String | URL of the notification image. |
action | String | Deep link URL opened on tap. |
silent | Boolean | true = no sound, low priority. |
buttons | JSON Array | Action buttons (label, action, url). |
defaultLang | String | Fallback language code, e.g. "en". |
condition | JSON String | Targeting 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 type | Description |
|---|---|
banner | Horizontal bar at the top, center, or bottom of the screen. |
modal | Centered dialog with an overlay backdrop. |
fullscreen | Full-screen takeover. |
modal-carousel | Centered multi-slide carousel. |
fullscreen-carousel | Full-screen multi-slide carousel. |
Layout blocks
Each layout is composed of ordered blocks:
| Block | Description |
|---|---|
text | Localized text with font size, color, and alignment. |
image | Remote image with an aspect ratio. |
buttonGroup | 1–3 action buttons (open URL, dismiss, copy text). |
spacer | Fixed or flexible vertical space. |
Banner positioning
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:
| Event | Trigger |
|---|---|
notificationOpen | User taps a push notification. |
notificationDismiss | User dismisses a push notification. |
inappMessageRead | An in-app message is displayed. |
$fcm_token_registered | FCM 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.
| Method | What it does |
|---|---|
NotificationManager.shared.handleNotificationResponse(_ response: UNNotificationResponse) -> Bool | Call 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) -> Bool | Call 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:) -> Bool | Call 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) -> Bool | Routes a deep link URL through the SDK (journey attribution + pending-link handling). Returns true if handled. |
For complete analytics, you can additionally call
handleForegroundPresentationinwillPresentandhandleLaunchOptionsindidFinishLaunchingWithOptions— both are optional and only add the corresponding events.
Troubleshooting
Push notifications not received
GoogleService-Info.plistis added to your app target.- APNs key/certificate is uploaded to Firebase and the Paylisher dashboard.
FirebaseApp.configure()is called beforePaylisherSDK.shared.setup(...).- The device has an internet connection.
In-app messages not appearing
- Ensure
NotificationManager.shared.customNotification(...)is called in bothwillPresentanddidReceiveRemoteNotification. - Check that
windowSceneresolves 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)")
}
}