AlgorithmX
Integrations17 min read

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:

swift
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.

  1. Push Notifications is turned on for your app (Xcode, target, Signing & Capabilities).
  2. Background Modes, Remote notifications is turned on for your app target. In-app campaigns need it.
  3. 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 .p8 file only once, so keep it safe. Never put it in the app. See Apple's guide.
  4. App Group (optional): only needed for delivery reports in step 9.

Send these to AlgorithmX

WhatExampleWhere to find it
Bundle IDcom.example.shopXcode, target settings.
Team IDABCDE12345Apple Developer account, Membership details.
APNs key file and its Key IDAuthKey_ABC123DEFG.p8Apple Developer account, Keys. Send it through a secure channel.
Which builds each AlgorithmX environment sends toTesting: debug builds. Production: TestFlight and App Store builds.Your release process. Debug builds and store builds use different Apple push servers.

The steps

StepWhat you doWhere
1Install the SDKXcode or your Podfile
2Start the SDKYour AppDelegate
3Tell AlgorithmX who the customer isLogin, app start, logout
4Send eventsWhere actions happen, such as checkout
5Connect push notificationsYour AppDelegate
6Open the right screenYour navigation code
7Check in-app campaignsNo code
8TestA real iPhone
9Images, buttons, and delivery reports (optional)A notification service extension

Step 1: Install the SDK

Choose one of these two ways.

Swift Package Manager

  1. In Xcode, choose File, then Add Package Dependencies.

  2. Paste this URL:

    Code
    https://github.com/algorithmx-cloud/algorithmx-ios-sdk
    
  3. Choose Up to Next Major Version from 1.0.0.

  4. 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:

swift
.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:

ruby
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.

swift
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 AppDelegate with an adaptor, if you do not have one yet:
swift
@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:

swift
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:

swift
_ = AlgorithmX.shared.clearWebViewQueue()
AlgorithmX.shared.resetIdentity()

When one account replaces another without a logout:

swift
_ = 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, close CampaignViewController too.
  • 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.

swift
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:

swift
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.

swift
// 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 completionHandler for 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 saysWhat happensYour code
Open a screen (deep link)The SDK gives the link to your handler.Step 6a
Custom actionThe SDK gives the action name and its details to your handler.Step 6b
Open a web pageThe SDK opens it in Safari.None
Open an in-app campaignThe SDK shows it over your app.None

Put all your handlers in one class. This is the AlgorithmXRouting object from step 2:

swift
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 true if your app accepts the link (also when it first waits for login), false if 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 onOpenURL or SceneDelegate code. Call the same router from both places.

6b. Custom actions

Map each action name you agreed on to a screen:

swift
// 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, so as? String does 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:

swift
// 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:

  1. AlgorithmX sends a hidden notification to the phone, and your AppDelegate passes it to the SDK.
  2. The SDK saves the campaign.
  3. 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:

bash
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 identifyUser attributes and trackEvent details, 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.

  1. 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.
  2. 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.swift from the SDK repository to the extension target only, and remove the import AlgorithmXSDK line below.
  3. Add the AlgorithmX branch to the extension:
swift
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.sharedWhat 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, campaignInteractionListenerYour 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?

ProblemWhat to check
No events in AlgorithmXinitialize runs in didFinishLaunchingWithOptions with the right HTTPS URL, and the phone is online.
The app crashes when sending an eventA value is not an allowed type, for example a Date. Convert it first (step 4).
No AlgorithmX notificationsThe 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 nothingThe AppDelegate keeps a reference to your handlers. The delegate is set in didFinishLaunchingWithOptions. The AlgorithmX check in didReceive response runs.
A deep link does nothingdeepLinkHandler 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 screenYour button handler returned false, so the main action ran. Check the action text.
No images or buttonsYour notification service extension calls processNotification and has the SDK (step 9).
In-app campaign never appearsBackground 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 closedThe campaign template needs a close button. Ask your campaign team.
A previous account's campaign appearsCall 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.