iOS Integration
Add AlgorithmX to your iOS app step by step: install the SDK, start it, identify customers, send events, connect push notifications, and open the right screen from campaigns.
This guide adds the AlgorithmX SDK to your iOS app in 9 steps. You keep your app's login, push setup, and navigation. You only add a few lines in places your app already has.
In the examples, names such as AppRouter and AlgorithmXRouting are your own code, not part of the SDK. Every SDK call goes through AlgorithmX.shared:
import AlgorithmXSDK
Before you start
Check that you have these:
- An app with iOS 15 or later, built with Xcode 15 or later.
- The API base URL from AlgorithmX, for example
https://api.example.com. - The list of customer ID, events, deep links, and custom actions you agreed on with AlgorithmX. See the overview.
Prepare your Apple Developer account
AlgorithmX sends notifications to your app through Apple's push service (APNs). If your app already receives push notifications, you probably have most of this already.
- Push Notifications is turned on for your app (Xcode, target, Signing & Capabilities).
- Background Modes, Remote notifications is turned on for your app target. In-app campaigns need it.
- An APNs key. Reuse your team's key, or create one in your Apple Developer account under Certificates, Identifiers & Profiles, then Keys, with Apple Push Notifications service turned on. Apple lets you download the
.p8file only once, so keep it safe. Never put it in the app. See Apple's guide. - App Group (optional): only needed for delivery reports in step 9.
Send these to AlgorithmX
| What | Example | Where to find it |
|---|---|---|
| Bundle ID | com.example.shop | Xcode, target settings. |
| Team ID | ABCDE12345 | Apple Developer account, Membership details. |
| APNs key file and its Key ID | AuthKey_ABC123DEFG.p8 | Apple Developer account, Keys. Send it through a secure channel. |
| Which builds each AlgorithmX environment sends to | Testing: debug builds. Production: TestFlight and App Store builds. | Your release process. Debug builds and store builds use different Apple push servers. |
The steps
| Step | What you do | Where |
|---|---|---|
| 1 | Install the SDK | Xcode or your Podfile |
| 2 | Start the SDK | Your AppDelegate |
| 3 | Tell AlgorithmX who the customer is | Login, app start, logout |
| 4 | Send events | Where actions happen, such as checkout |
| 5 | Connect push notifications | Your AppDelegate |
| 6 | Open the right screen | Your navigation code |
| 7 | Check in-app campaigns | No code |
| 8 | Test | A real iPhone |
| 9 | Images, buttons, and delivery reports (optional) | A notification service extension |
Step 1: Install the SDK
Choose one of these two ways.
Swift Package Manager
-
In Xcode, choose File, then Add Package Dependencies.
-
Paste this URL:
Codehttps://github.com/algorithmx-cloud/algorithmx-ios-sdk -
Choose Up to Next Major Version from
1.0.0. -
Add the AlgorithmXSDK product to your app target. If you plan to do step 9, add it to your notification service extension too.
If your project uses a Package.swift file, add this instead:
.package(url: "https://github.com/algorithmx-cloud/algorithmx-ios-sdk", from: "1.0.0")
CocoaPods
Add this line to your app target in the Podfile, then run pod install:
target 'YourApp' do
pod 'AlgorithmXSDK', :git => 'https://github.com/algorithmx-cloud/algorithmx-ios-sdk.git', :tag => '1.0.0'
end
The pod is installed from GitHub, because the public CocoaPods registry stops accepting new pods on December 2, 2026. With CocoaPods, do not add the pod to your notification service extension; step 9 explains what to do instead.
Check: import AlgorithmXSDK builds without errors.
The SDK has no other dependencies. To update later, change the version.
Step 2: Start the SDK
Add these lines to application(_:didFinishLaunchingWithOptions:) in your AppDelegate. Keep your existing code.
import AlgorithmXSDK
import UIKit
import UserNotifications
final class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
// Your handlers from step 6. Keep this property: the SDK does not keep them alive.
private let algorithmXRouting = AlgorithmXRouting()
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
// Keep your existing startup code.
AlgorithmX.shared.initialize(apiBaseUrl: "https://api.example.com")
#if DEBUG
AlgorithmX.shared.setLoggingEnabled(true)
#endif
AlgorithmX.shared.notificationClickListener = algorithmXRouting
AlgorithmX.shared.deepLinkHandler = algorithmXRouting
AlgorithmX.shared.actionButtonHandler = algorithmXRouting
UNUserNotificationCenter.current().delegate = self // Keep yours if you already set it.
application.registerForRemoteNotifications() // On every launch.
return true
}
}
Why in the AppDelegate and not on the first screen? iOS can start your app in the background to deliver a campaign. No screen appears then, but this method still runs. SDK calls made before initialize are ignored.
Important: keep your handlers alive. The SDK holds your handlers weakly. Store them in a property of the AppDelegate, as above. If you write AlgorithmX.shared.deepLinkHandler = MyHandler(), the object disappears at once and taps do nothing.
- App Group (step 9 only): use
AlgorithmX.shared.initialize(apiBaseUrl: "https://api.example.com", appGroup: "group.com.example.shop"). - SwiftUI apps: connect the
AppDelegatewith an adaptor, if you do not have one yet:
@main
struct ShopApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene { WindowGroup { RootView() } }
}
Check: run a debug build. The Xcode console shows the SDK's requests.
Step 3: Tell AlgorithmX who the customer is
Until you do this, the SDK uses an anonymous ID for the phone.
At login, and when your app starts with a customer already logged in, call identifyUser with your customer ID:
AlgorithmX.shared.identifyUser(
userId: "customer_123",
attributes: ["first_name": "Amina", "language": "ar", "loyalty_tier": "gold"]
)
Do both places. The second one covers customers who are already logged in when they update to your first app version with AlgorithmX.
At logout, clear waiting campaigns first, so the next person does not see them:
_ = AlgorithmX.shared.clearWebViewQueue()
AlgorithmX.shared.resetIdentity()
When one account replaces another without a logout:
_ = AlgorithmX.shared.clearWebViewQueue()
AlgorithmX.shared.identifyUser(userId: "customer_456", attributes: ["language": "en"])
Good to know:
- The SDK remembers the customer after the app restarts. It also links what the customer did before logging in.
resetIdentity()does not close a campaign that is already on screen. If your logout closes open screens, closeCampaignViewControllertoo.- Attribute values follow the same rules as event values in step 4.
- This does not replace your own login. It only tells AlgorithmX who is using the app.
Check: after login, the customer appears in AlgorithmX with the ID you sent.
Step 4: Send events
Send an event right after an action succeeds, for example after an order is confirmed. Put it next to your existing analytics calls.
AlgorithmX.shared.trackEvent("product_view", properties: ["product_id": "SKU-123"])
AlgorithmX.shared.trackEvent("add_to_cart", properties: ["product_id": "SKU-123", "quantity": 2])
AlgorithmX.shared.trackEvent("purchase", properties: [
"order_id": "ORDER-1001",
"total": 149.90,
"currency": "SAR",
"items": [["product_id": "SKU-123", "quantity": 2]]
])
Important: only use these value types: String, Int, Double, Bool, and arrays or dictionaries of them. Any other value, such as a Date, a URL, your own type, or Double.nan, crashes the app. Turn dates into a timestamp or an ISO 8601 string first, and leave out empty optional values.
Other rules:
- Names: use simple names with letters and underscores, such as
add_to_cart. - Offline: an event sent without internet is lost. It is not sent again later. Keep your own backend as the record of orders and payments.
- Past data: only new events are sent. To bring in older data, ask your AlgorithmX contact about a data import.
Check: the event appears for the customer in AlgorithmX.
Step 5: Connect push notifications
You keep your push setup. You add two things to your AppDelegate.
5a. Send the push token
Add one line to your token callback:
func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
let apnsToken = deviceToken.map { String(format: "%02x", $0) }.joined()
AlgorithmX.shared.registerDeviceToken(apnsToken)
// Keep your own registrations here, for example Firebase.
}
- Send the APNs token, even if your app also uses Firebase. The Firebase token does not work with AlgorithmX.
- Send it on every launch. That is why step 2 calls
registerForRemoteNotifications()every time. Customers who update your app have a token AlgorithmX has not seen yet. - This does not need the customer's permission. Keep your own permission prompt; the SDK never asks.
5b. Pass AlgorithmX notifications to the SDK
Add an AlgorithmX check at the top of these three methods. Other notifications continue through your existing code.
// 1. Hidden (background) notifications. In-app campaigns arrive here.
func application(
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
if AlgorithmX.shared.isAlgorithmXPush(userInfo: userInfo) {
AlgorithmX.shared.handleNotification(userInfo: userInfo) {
completionHandler(.newData)
}
return
}
// Your existing code. It must call completionHandler once.
}
// 2. A notification arrives while the app is open.
func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
) {
let userInfo = notification.request.content.userInfo
if AlgorithmX.shared.isAlgorithmXPush(userInfo: userInfo) {
AlgorithmX.shared.handleNotification(userInfo: userInfo) {
completionHandler([.banner, .list, .sound]) // Show it as a banner.
}
return
}
// Your existing code.
}
// 3. The customer taps a notification or one of its buttons.
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
) {
let userInfo = response.notification.request.content.userInfo
if AlgorithmX.shared.isAlgorithmXPush(userInfo: userInfo) {
AlgorithmX.shared.handleNotificationResponse(
actionIdentifier: response.actionIdentifier,
userInfo: userInfo,
completionHandler: completionHandler
)
return
}
// Your existing code.
}
Good to know:
- The SDK calls each
completionHandlerfor you, once. - Set the notification delegate inside
didFinishLaunchingWithOptions, as in step 2. If you set it later, a tap that starts the app is lost. - If another library, such as Firebase, takes over your notification delegate, send a real AlgorithmX notification while testing to check that these lines still run.
- Taps that start the app: the SDK calls your handlers right after launch, maybe before your first screen is ready. Your router should remember the destination and open it when the app is ready.
Check: ask your campaign team to send a test notification. It appears, and a tap reaches your code.
Step 6: Open the right screen when a customer taps
A campaign can do one of four things when the customer taps it. Two of them need your code:
| The campaign says | What happens | Your code |
|---|---|---|
| Open a screen (deep link) | The SDK gives the link to your handler. | Step 6a |
| Custom action | The SDK gives the action name and its details to your handler. | Step 6b |
| Open a web page | The SDK opens it in Safari. | None |
| Open an in-app campaign | The SDK shows it over your app. | None |
Put all your handlers in one class. This is the AlgorithmXRouting object from step 2:
6a. Deep links
import AlgorithmXSDK
final class AlgorithmXRouting: NSObject, NotificationClickListener, DeepLinkHandler, ActionButtonHandler {
func onDeepLinkReceived(url: URL) -> Bool {
AppRouter.shared.open(url) // Your existing router.
}
// The methods from 6b and 6c go here too.
}
- Return
trueif your app accepts the link (also when it first waits for login),falseif not. - The SDK never opens the link by itself. If you set no handler, nothing happens.
- You do not need a new URL scheme, universal links, or new screens. Give your campaign team the links your app already supports, for example
myapp://open/product?id=SKU-123. - Links opened from outside the app still go through your
onOpenURLor SceneDelegate code. Call the same router from both places.
6b. Custom actions
Map each action name you agreed on to a screen:
// Inside AlgorithmXRouting.
// Runs first on every tap. Return false so the SDK runs the campaign's action.
func onNotificationClick(data: [String: Any]) -> Bool {
false
}
func onCustomAction(action: String?, data: [String: Any]) -> Bool {
let details = data["parsedActionData"] as? [String: Any] ?? [:]
switch action {
case "view_product":
guard let productId = details["product_id"] as? String, !productId.isEmpty else { return false }
return AppRouter.shared.openProduct(id: productId)
case "view_cart":
return AppRouter.shared.openCart()
default:
return false
}
}
- The action's details are in
parsedActionData. A number arrives as a number, soas? Stringdoes not work for it. Agree on the types with your campaign team. - An action name your app does not know does nothing. Only offer actions that your released app supports.
6c. Notification buttons
Match on the button's action text, not on its title, because the title can be translated:
// Inside AlgorithmXRouting.
func onActionButtonClicked(
buttonId: String,
actionText: String,
title: String,
notificationData: [String: Any]
) -> Bool {
switch actionText {
case "view_cart":
return AppRouter.shared.openCart()
case "dismiss":
return true // Handled: do nothing more.
default:
return false
}
}
Every button opens the app. Return true when you handled the button. If you return false, the SDK runs the notification's main action instead. Buttons only appear after step 9.
Keep your normal login and permission checks before sensitive screens: a campaign only suggests where to go.
Check: send a test notification for each link and action your app supports. The right screen opens once.
Step 7: Check in-app campaigns
In-app campaigns need no code. After steps 2 to 5:
- AlgorithmX sends a hidden notification to the phone, and your
AppDelegatepasses it to the SDK. - The SDK saves the campaign.
- The next time the customer opens the app, the SDK shows the campaign over the current screen. It shows at most one per visit, and closes with the campaign's close button.
Check these in your app:
- Background Modes, Remote notifications is on. Without it, campaigns only arrive while the app is open.
- Logout: clear waiting campaigns, as in step 3.
- Your own pop-ups at start: a campaign can appear when the app opens. Before showing your own pop-up, you can check
AlgorithmX.shared.isWebViewCurrentlyShowing(). - Close button: iPhones have no Back button, so every campaign template needs a close button. Tell your campaign team.
- Delivery is not guaranteed. Apple can delay hidden notifications. If the customer swipes the app away in the app switcher, campaigns arrive only after they open it again.
To see impressions, clicks, and closes while testing, set AlgorithmX.shared.campaignInteractionListener to an object you keep a reference to.
Step 8: Test before you release
Use a physical iPhone and a test customer. Then repeat the main checks with a TestFlight build, because store builds use a different Apple push server than debug builds.
- Update while logged in: install your current App Store version, log in, then update to the new build. The customer is recognized without logging in again.
- Login, restart, logout, second account: events use the right customer each time. No campaign from the first account appears for the second.
- Your existing notifications still work exactly as before.
- AlgorithmX notification with the app open, in the background, and closed: a tap opens the right screen once, also when the tap starts the app.
- Each deep link, custom action, and button opens the right screen.
- In-app campaign: it appears the next time the app opens and closes with its close button.
- Notifications turned off: in-app campaigns still arrive.
- Events appear for the customer in AlgorithmX.
While testing, setLoggingEnabled(true) shows each SDK request in the Xcode console. To see it on a phone without the debugger, run:
xcrun devicectl device process launch --console --terminate-existing --device <device-id> <bundle-id>
App Store privacy
- The SDK includes a privacy manifest (no tracking; device ID, user ID, and product interactions). Xcode adds it to your app's privacy report.
- Your App Store privacy answers must also cover what you send yourself in
identifyUserattributes andtrackEventdetails, such as names, emails, or purchases. - The SDK uses the vendor identifier, not the advertising identifier, and does not show the tracking permission prompt.
Step 9 (optional): Images, buttons, and delivery reports
iOS shows notifications itself. To show AlgorithmX images and buttons, and to report that a notification arrived, add one call to a notification service extension. Without it, notifications still appear and taps still work, just without images and buttons.
- If your app already has a notification service extension, use it; an app has only one. Otherwise, in Xcode choose File, New, Target, Notification Service Extension, and set its iOS version to match your app.
- Give the extension the SDK:
- Swift Package Manager: add the AlgorithmXSDK product to the extension target.
- CocoaPods: do not add the pod. Instead, add the file
Sources/AlgorithmXSDK/Notifications/EngageNotificationService.swiftfrom the SDK repository to the extension target only, and remove theimport AlgorithmXSDKline below.
- Add the AlgorithmX branch to the extension:
import AlgorithmXSDK // Remove this line with CocoaPods (step 2).
import UserNotifications
final class NotificationService: UNNotificationServiceExtension {
private var contentHandler: ((UNNotificationContent) -> Void)?
private var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(
_ request: UNNotificationRequest,
withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
) {
self.contentHandler = contentHandler
guard let content = request.content.mutableCopy() as? UNMutableNotificationContent else {
contentHandler(request.content)
return
}
bestAttemptContent = content
if EngageNotificationService.isAlgorithmXPush(request) {
EngageNotificationService.processNotification(
request: request,
bestAttemptContent: content,
appGroup: "group.com.example.shop", // The same group as in step 2.
contentHandler: contentHandler
)
return
}
// Your existing code for other notifications.
contentHandler(content)
}
override func serviceExtensionTimeWillExpire() {
if let contentHandler, let bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
}
Delivery reports need an App Group shared by the app and the extension, with the same name here and in step 2. Without an App Group, call processNotification(request:bestAttemptContent:contentHandler:) instead; images and buttons still work.
Check: a test notification shows its image and buttons.
Quick reference
| Call on AlgorithmX.shared | What it does |
|---|---|
initialize(apiBaseUrl:) or initialize(apiBaseUrl:appGroup:) | Starts the SDK. Call it in didFinishLaunchingWithOptions. |
setLoggingEnabled(_:) | Shows SDK requests in the console. Off by default. |
identifyUser(userId:attributes:) | Sets the customer. Call at login and app start. |
resetIdentity() | Goes back to an anonymous customer. Call at logout. |
trackEvent(_:properties:) | Sends an event. |
registerDeviceToken(_:) | Sends the APNs token. Call on every launch. |
isAlgorithmXPush(userInfo:) | Tells you if a notification is from AlgorithmX. |
handleNotification(userInfo:completionHandler:) | Gives a notification to the SDK, from didReceiveRemoteNotification and willPresent. |
handleNotificationResponse(actionIdentifier:userInfo:completionHandler:) | Gives a tap to the SDK, from didReceive response. |
deepLinkHandler, notificationClickListener, actionButtonHandler, campaignInteractionListener | Your handlers. Keep a strong reference to each. Set nil to remove. |
clearWebViewQueue() | Removes waiting in-app campaigns. Call at logout. |
isWebViewCurrentlyShowing() | Tells you if a campaign is on screen. |
getDeviceFingerprint(), getQueueSize() | Show the current customer ID and waiting campaigns, for testing. |
EngageNotificationService.processNotification(...) | Call from your notification service extension (step 9). |
Something not working?
| Problem | What to check |
|---|---|
| No events in AlgorithmX | initialize runs in didFinishLaunchingWithOptions with the right HTTPS URL, and the phone is online. |
| The app crashes when sending an event | A value is not an allowed type, for example a Date. Convert it first (step 4). |
| No AlgorithmX notifications | The token is sent on every launch. AlgorithmX has your APNs key, Team ID, and bundle ID. The AlgorithmX environment matches the build type (debug, or TestFlight and App Store). |
| Tapping a notification does nothing | The AppDelegate keeps a reference to your handlers. The delegate is set in didFinishLaunchingWithOptions. The AlgorithmX check in didReceive response runs. |
| A deep link does nothing | deepLinkHandler is set and calls your router. Your router accepts the link and waits for the app to be ready after a launch tap. |
| A button opens the wrong screen | Your button handler returned false, so the main action ran. Check the action text. |
| No images or buttons | Your notification service extension calls processNotification and has the SDK (step 9). |
| In-app campaign never appears | Background Modes, Remote notifications is on. didReceiveRemoteNotification passes the notification to the SDK. The app was not swiped away, and you opened it again after the campaign was sent. |
| A campaign cannot be closed | The campaign template needs a close button. Ask your campaign team. |
| A previous account's campaign appears | Call clearWebViewQueue() at logout and when switching accounts. |
Next steps
Create your first campaigns with In-App Push Campaigns and In-App View Campaigns. Go back to the overview for other platforms.
