Skip to main content

Getting Started

The Paylisher iOS SDK uses an internal queue to make calls fast and non-blocking. It also batches requests and flushes them asynchronously, making it safe to use in any part of your mobile app.

Paylisher supports the following Apple platforms:

  • iOS: Version 13 or later
  • macOS: Catalina (10.15) or later
  • tvOS: Version 13 or later
  • watchOS: Version 6 or later

The current SDK version is 1.8.4.

Installation

Paylisher is available through CocoaPods or the Swift Package Manager.

CocoaPods

Add the following line to your Podfile:

pod 'Paylisher', '~> 1.8.4'

Then run:

pod install

Swift Package Manager

Add Paylisher as a dependency in your Xcode project under Package Dependencies, then select the target for your app.

For a Package.swift based project, add Paylisher to your dependencies:

dependencies: [
.package(url: "https://github.com/paylisher/PAYLISHER-SDK-IOS.git", from: "1.8.4")
],

and then as a dependency of the target that uses Paylisher:

.target(
name: "myApp",
dependencies: [.product(name: "Paylisher", package: "PAYLISHER-SDK-IOS")]
),

Push & in-app messaging: Paylisher's push notification and in-app messaging features build on top of Firebase. When you install via CocoaPods, the required Firebase pods (FirebaseCore, FirebaseAuth, FirebaseDatabase, FirebaseFirestore, FirebaseAnalytics) are pulled in automatically. Core analytics (event capture, identify, feature flags) work without any Firebase setup. The full APNs/FCM configuration is covered in the Push Notifications guide.

Configuration

Add Paylisher to your app's initializer. The minimum configuration requires your project API key; the host is the Paylisher ingestion host shown in your project settings.

import SwiftUI
import Paylisher

@main
struct YourGreatApp: App {

init() {
let PAYLISHER_API_KEY = "<ph_project_api_key>"
// Use the host from your Paylisher project settings.
// US: https://us.i.paylisher.com • EU: https://eu.i.paylisher.com
let PAYLISHER_HOST = "https://us.i.paylisher.com"

let config = PaylisherConfig(apiKey: PAYLISHER_API_KEY, host: PAYLISHER_HOST)
PaylisherSDK.shared.setup(config)
}

var body: some Scene {
WindowGroup {
ContentView()
}
}
}

host is optional — if you omit it, the SDK defaults to https://us.i.paylisher.com.

UIKit

If you use a UIApplicationDelegate, initialize Paylisher as early as possible in application(_:didFinishLaunchingWithOptions:):

import UIKit
import Paylisher

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let config = PaylisherConfig(apiKey: "<ph_project_api_key>", host: "https://us.i.paylisher.com")
PaylisherSDK.shared.setup(config)
return true
}
}

Capturing events

You can send custom events using capture:

PaylisherSDK.shared.capture("user_signed_up")

Tip: We recommend a [object] [verb] format for event names, where [object] is the entity the behavior relates to and [verb] is the behavior itself. For example, project created, user signed up, or invite sent.

Setting event properties

Optionally, you can include additional information in the event by setting properties. You can also attach person properties at capture time with userProperties and userPropertiesSetOnce:

PaylisherSDK.shared.capture(
"user_signed_up",
properties: ["login_type": "email"],
userProperties: ["is_free_trial": true]
)

Preventing sensitive data capture

To exclude specific UI elements from autocapture or session replay, add ph-no-capture as either an accessibilityLabel or accessibilityIdentifier. When Paylisher detects this label or identifier anywhere in the view hierarchy, the element will be ignored or masked:

// This view will be excluded from autocapture
let view = UIView()
view.accessibilityLabel = "ph-no-capture"

Important: By default, Paylisher makes a best effort to automatically exclude fields detected as sensitive, even without the ph-no-capture tag. These include password fields, credit card fields, OTP fields, and any other fields related to Personally Identifiable Information (PII).

Identifying users

We highly recommend reading our Identifying users guide to understand how to correctly use this method.

Using identify, you associate the events of a session with a specific user:

PaylisherSDK.shared.identify(
"distinct_id_of_your_user",
userProperties: ["name": "John Doe", "email": "[email protected]"],
userPropertiesSetOnce: ["signup_date": "2024-12-01"]
)

When you call identify, the SDK automatically attaches the previous anonymous ID as $anon_distinct_id, so previously tracked anonymous events can be linked to the user on the backend.

Get the current user's distinct ID

You may find it helpful to get the current user's distinct ID — for example, to check whether you've already called identify for a user.

To do this, call getDistinctId(). This returns either the ID automatically generated by Paylisher or the ID passed to identify():

let distinctId = PaylisherSDK.shared.getDistinctId()

Alias

Sometimes you want to assign multiple distinct IDs to a single user. This is helpful when your primary distinct ID is inaccessible — for example, when a distinct ID used on the frontend is not available in your backend.

In this case, you can use alias to assign another distinct ID to the same user:

PaylisherSDK.shared.alias("alias_id")

Setting person properties

To set properties on your users via an event, use the userProperties and userPropertiesSetOnce parameters:

PaylisherSDK.shared.capture(
"signed_up",
properties: ["plan": "Pro++"],
userProperties: ["user_property_name": "your_value"]
)

userPropertiesSetOnce works just like userProperties, except that it will only set the property if the user doesn't already have that property set:

PaylisherSDK.shared.capture(
"signed_up",
properties: ["plan": "Pro++"],
userPropertiesSetOnce: ["user_property_name": "your_value"]
)

Super Properties

Super Properties are properties associated with events that are set once and then sent with every capture call, be it a $screen or anything else.

They are set using PaylisherSDK.shared.register, which takes a properties object as a parameter, and they persist across sessions:

PaylisherSDK.shared.register(["team_id": 22])

The call above ensures that every event sent by the user will include "team_id": 22.

Note that this does not store properties against the user, only against their events. To store properties against the user, use PaylisherSDK.shared.identify.

Removing stored Super Properties

Super Properties are persisted across sessions, so you have to explicitly remove them if they are no longer relevant. To stop sending a Super Property with events, use unregister:

PaylisherSDK.shared.unregister("team_id")

This removes the Super Property, and subsequent events will not include it.

If you are doing this as part of a user logging out, you can instead use PaylisherSDK.shared.reset, which clears all stored Super Properties and more.

Amending event properties before they are captured

If you need to modify or process event properties before they are captured, cached, and sent to Paylisher, you can use the config.propertiesSanitizer option.

To implement this, create a class or struct that conforms to the PaylisherPropertiesSanitizer protocol and implement the sanitize() method. The method receives a dictionary of event properties and should return a modified version of the dictionary. Then pass your custom sanitizer to config.propertiesSanitizer:

class ExampleSanitizer: PaylisherPropertiesSanitizer {
public func sanitize(_ properties: [String: Any]) -> [String: Any] {
var sanitizedProperties = properties
// Example: remove keys with empty string values
for (key, value) in properties {
if let stringValue = value as? String, stringValue.isEmpty {
sanitizedProperties.removeValue(forKey: key)
}
}
return sanitizedProperties
}
}

config.propertiesSanitizer = ExampleSanitizer()

Flush

You can set the number of events that should queue before flushing. Setting this to 1 sends events immediately and uses more battery. The default is 20:

config.flushAt = 1

You can also manually flush the queue:

PaylisherSDK.shared.capture("logged_out")
PaylisherSDK.shared.flush()

Reset after logout

To reset the user's ID and anonymous ID, call reset. Usually you do this right after the user logs out. This also clears local data such as the session, super properties, feature flags, and journey ID:

PaylisherSDK.shared.reset()

Opt out of data capture

You can completely opt users out of data capture. There are two options:

  1. Opt users out by default by setting optOut to true in your config:
let config = PaylisherConfig(apiKey: "<ph_project_api_key>", host: "<ph_client_api_host>")
config.optOut = true
PaylisherSDK.shared.setup(config)
  1. Opt users out on a per-person basis by calling optOut():
PaylisherSDK.shared.optOut()

Similarly, you can opt users back in:

PaylisherSDK.shared.optIn()

To check if a user is opted out:

PaylisherSDK.shared.isOptOut()

Group analytics

Group analytics lets you associate a user's session events with a group (e.g. teams, organizations, etc.).

Note: This is a paid feature and is not available on the open-source or free cloud plan.

  • Associate the events for this session with a group:
PaylisherSDK.shared.group(type: "company", key: "company_id_in_your_db")
  • Associate the events for this session with a group and update the properties of that group:
PaylisherSDK.shared.group(type: "company", key: "company_id_in_your_db", groupProperties: [
"name": "ACME Corp"
])

The name is a special property used in the Paylisher UI for the name of the group. If you don't specify a name property, the group ID is used instead.

Feature flags

Paylisher feature flags let you roll out features gradually and run experiments. Flags are preloaded automatically on startup (see preloadFeatureFlags).

// Boolean flags
if PaylisherSDK.shared.isFeatureEnabled("my-flag") {
// do something
}

// Multivariate flags return the variant key as a String
if let variant = PaylisherSDK.shared.getFeatureFlag("my-flag") as? String, variant == "variant-name" {
// do something
}

// Flags can carry a JSON payload for dynamic, per-user content
let payload = PaylisherSDK.shared.getFeatureFlagPayload("my-flag")

If you load flags yourself (e.g. after identify), you can refresh them on demand:

PaylisherSDK.shared.reloadFeatureFlags()

When sendFeatureFlagEvent is enabled (default), a $feature_flag_called event is sent automatically the first time a flag is used.

Experiments (A/B tests)

Because experiments use feature flags, running an experiment looks just like reading a flag:

if PaylisherSDK.shared.getFeatureFlag("experiment-feature-flag-key") as? String == "variant-name" {
// do something
}

A note about IDFA (identifier for advertisers)

Starting with iOS 14, Apple restricts apps that track users, and any reference to Apple's AdSupport framework — even in strings — can trip the App Store's static analysis. Paylisher does not reference Apple's AdSupport framework, so the SDK does not collect the IDFA.

Session replay

To set up session replay, install the iOS SDK, enable Record user sessions in your project settings, and enable the sessionReplay option. Session replay is currently experimental.

config.sessionReplay = true

All configuration options

The PaylisherConfig object contains several settings you can toggle:

let config = PaylisherConfig(apiKey: PAYLISHER_API_KEY, host: PAYLISHER_HOST)

/// Number of queued events the client should flush at. Setting this to `1` will not
/// queue any events and will use more battery. `20` by default.
config.flushAt = 20

/// Interval, in seconds, between ticks of the flush timer. Smaller values deliver
/// events in a more real-time manner but use more battery. `30` by default.
config.flushIntervalSeconds = 30

/// Maximum number of items to queue before old ones start being dropped. `1000` by default.
config.maxQueueSize = 1000

/// Maximum number of events in a single batch call. `50` by default.
config.maxBatchSize = 50

/// Automatically capture application lifecycle events such as "Application Installed",
/// "Application Updated" and "Application Opened". `true` by default.
config.captureApplicationLifecycleEvents = true

/// Automatically capture a screen event when a view controller is added to the view
/// hierarchy. Because the implementation uses method swizzling, initialize the SDK as
/// early as possible. `true` by default.
config.captureScreenViews = true

/// Send a `$feature_flag_called` event when a feature flag is used. `true` by default.
config.sendFeatureFlagEvent = true

/// Preload feature flags automatically on startup. `true` by default.
config.preloadFeatureFlags = true

/// Log the SDK messages to the console. `false` by default.
config.debug = false

/// Prevent capturing any data while enabled. `false` by default.
config.optOut = false

/// Callback that allows you to sanitize event properties before they are cached/sent.
config.propertiesSanitizer = ExampleSanitizer()

/// Hook to customize the generation of the anonymous id (a random UUID v7 by default).
config.getAnonymousId = { uuid in uuid }

/// Restrict sending data to a given network mode: `.wifi`, `.cellular` or `.any`.
/// `.any` by default.
config.dataMode = .any

/// Determines how user profiles are processed: `.never`, `.always` or `.identifiedOnly`.
/// `.identifiedOnly` by default.
config.personProfiles = .identifiedOnly

/// Controls what happens when `identify()` is called again with the same distinctId after
/// the user is already identified on this device:
/// - `.ignore` (default) suppress duplicate identify calls (legacy behavior)
/// - `.capture` emit a new `$identify` event so person/device properties can be refreshed
config.repeatedIdentifyBehavior = .ignore

/// Identifier of the App Group used to store shared analytics data (e.g. for the
/// notification service extension). `nil` by default.
config.appGroupIdentifier = nil

/// Deferred deep link configuration (install attribution). Disabled (`nil`) by default.
/// See the Deep Linking guide for details.
config.deferredDeepLinkConfig = nil

/// Engage-served in-app message pull configuration. When set, the SDK can fetch in-app
/// campaigns directly from the Engage service without FCM delivery. `nil` by default.
/// See the In-App Messaging guide for details.
config.engageInAppConfig = nil

/// Enable Recording of Session Replays (experimental). `false` by default.
config.sessionReplay = false
/// Session Replay configuration (experimental).
config.sessionReplayConfig = .init()