Tracking for iOS SDK
You can track events in Engagement to learn more about your app’s usage patterns and to segment your customers by their interactions.
By default, the SDK tracks certain events automatically, including:
- Installation (after app installation and after invoking anonymize)
- User session start and end
- Banner event for showing an in-app message or content block
notification_stateevent for push notification token tracking (SDK versions 3.8.0 and higher). Learn more.
Additionally, you can track any custom event relevant to your business.
Also see Mobile SDK tracking FAQ at Bloomreach Support Help Center.
Protect the privacy of your customers
Make sure you have obtained and stored tracking consent from your customer before initializing Exponea iOS SDK.
To ensure you're not tracking events without the customer's consent, you can use
Exponea.shared.clearLocalCustomerData(appGroup: String)when a customer opts out from tracking (this applies to new users or returning customers who have previously opted out). This will bring the SDK to a state as if it was never initialized. This option also prevents reusing existing cookies for returning customers.Refer to Clear local customer data for details.
If customer denied tracking consent after Exponea iOS SDK is initialized, you can use
Exponea.shared.stopIntegration()to stop SDK integration and remove all locally stored data.Refer to Stop SDK integration for details.
Events
Track event
Use the trackEvent() method to track any custom event type relevant to your business.
You can use any name for a custom event type. We recommended using a descriptive and human-readable name.
Refer to the Custom events documentation for an overview of commonly used custom events.
Arguments
| Name | Type | Description |
|---|---|---|
| properties | [String: JSONConvertible] | Dictionary of event properties. |
| timestamp | Double | Unix timestamp specifying when the event was tracked. Specify nil value to use the current time. |
| eventType (required) | String | Name of the event type, for example screen_view. |
Examples
Imagine you want to track which screens a customer views. You can create a custom event screen_view for this.
First, create a dictionary with properties you want to track with this event. In our example, you want to track the name of the screen, so you include a property screen_name along with any other relevant properties:
let properties: [String: JSONConvertible] = [
"screen_name": "dashboard",
"other_property": 123.45
]Pass the dictionary to trackEvent() along with the eventType (screen_view) as follows:
Exponea.shared.trackEvent(properties: properties,
timestamp: nil,
eventType: "screen_view")The second example below shows how you can use a nested JSON structure for complex properties if needed:
let properties: [String: JSONConvertible] = [
"purchase_status": "success",
"product_list": [
["product_id": "abc123", "quantity": 2],
["product_id": "abc456", "quantity": 1]
],
"total_price": 7.99,
]
Exponea.shared.trackEvent(properties: properties,
timestamp: nil,
eventType: "purchase")Optionally, you can provide a custom
timestampif the event happened at a different time. By default, the current time will be used.
Customers
Identifying your customers allows you to track them across devices and platforms, improving the quality of your customer data.
Without identification, events are tracked for an anonymous customer, only identified by a cookie. Once the customer is identified by a hard ID, these events will be transferred to a newly identified customer.
Keep in mind that, while an app user and a customer record can be related by a soft or hard ID, they are separate entities, each with their own lifecycle. Take a moment to consider how their lifecycles relate and when to use identify and anonymize.
Identify
Use the identifyCustomer() method to identify a customer using their unique hard ID.
The default hard ID is registered and its value is typically the customer's email address. However, your Engagement project may define a different hard ID.
Optionally, you can track additional customer properties such as first and last names, age, etc.
Although it's possible to use
identifyCustomerwith a soft ID, developers should use caution when doing this. In some cases (for example, after usinganonymize), this can unintentionally associate the current user with an incorrect customer profile.
The SDK stores data, including customer hard ID, in a local cache on the device. Removing the hard ID from the local cache requires calling anonymize in the app.
If the customer profile is anonymized or deleted in the Bloomreach Engagement webapp, subsequent initialization of the SDK in the app can cause the customer profile to be reidentified or recreated from the locally cached data.
Always use a hard ID to identify a customer. Using a soft ID with
identifyCustomercould unintentionally cause the customer to be associated with an incorrect profile.
Arguments
| Name | Type | Description |
|---|---|---|
| customerIds (required) | [String: String] | Dictionary of customer unique identifiers. Only identifiers defined in the Engagement project are accepted. |
| properties | [String: JSONConvertible] | Dictionary of customer properties. |
| timestamp | Double | Unix timestamp specifying when the customer properties were updated. Specify the nil value to use the current time. |
Examples
First, create a dictionary containing at least the customer's hard ID:
let customerIds: [String: JSONConvertible] = [
"registered": "[email protected]"
]Optionally, create a dictionary with additional customer properties:
let properties: [String: JSONConvertible] = [
"first_name": "Jane",
"last_name": "Doe",
"age": 32
]Pass the customerIds and properties dictionaries to identifyCustomer():
Exponea.identifyCustomer(customerIds: customerIds,
properties: properties,
timestamp: nil)If you only want to update the customer ID without any additional properties, you can pass an empty dictionary literal for properties:
Exponea.identifyCustomer(customerIds: customerIds,
properties: [:],
timestamp: nil)Optionally, you can provide a custom
timestampif the identification happened at a different time. By default the current time will be used.
Identify with auth context (Stream/Data hub)
When using Stream JWT integration, you can provide customer IDs and the JWT token together using identifyCustomer(context:properties:timestamp:). This is the preferred method for Stream mode as it atomically associates the customer identity with the JWT.
Arguments
| Name | Type | Description |
|---|---|---|
| context (required) | CustomerIdentity | Contains customerIds (dictionary of customer IDs) and optional jwtToken (Stream JWT). |
| properties | [String: JSONConvertible] | Dictionary of customer properties. |
| timestamp | Double | Unix timestamp specifying when the identification happened. Specify nil to use the current time. |
Example
let context = CustomerIdentity(
customerIds: ["registered": "[email protected]"],
jwtToken: "YOUR_STREAM_JWT_TOKEN"
)
Exponea.shared.identifyCustomer(context: context, properties: [:], timestamp: nil)The JWT is stored in the Keychain and used for subsequent Stream requests. You can omit jwtToken if the token was already set via setSdkAuthToken.
Anonymize
Use the anonymize() method to delete all information stored locally and reset the current SDK state. A typical use case for this is when the user signs out of the app.
Invoking this method will cause the SDK to:
- Track a
session_endevent ifautomaticSessionTrackingis enabled. - Invalidate the push notification token by tracking
notification_statewithvalid: false. - Flush all pending events to the server (in Stream mode, this uses the current JWT while it is still available).
- Remove the push notification token for the current customer from local device storage and the customer profile in Engagement.
- Clear local repositories and caches, excluding tracked events.
- Clear the JWT from the Keychain (Stream mode).
- Create a new customer record in Engagement (a new
cookiesoft ID is generated). - Assign the previous push notification token to the new customer record.
- Preload in-app messages, in-app content blocks, and app inbox for the new customer.
- Track a new
installationevent for the new customer. - Track a new session start if
automaticSessionTrackingis enabled.
You can also use the anonymize method to switch to a different integration. The SDK will then track events to a new customer record, similar to the first app session after installation on a new device.
Stream/JWT integrators
anonymize()creates a new anonymous customer profile. If your integration requires that every event is associated with an authenticated customer and valid JWT, usestopIntegration(completion:)on logout instead.stopIntegrationdoes not generate anonymous events.
Overloads
| Method | Description |
|---|---|
anonymize() | Anonymize with current integration settings. |
anonymize(completion:) | Same as above, but calls completion on the main thread after flush + teardown are complete. Recommended for Stream mode so the host app knows when it is safe to proceed. |
anonymize(exponeaIntegrationType:exponeaProjectMapping:) | Anonymize and switch to a different integration (accepts both ExponeaProject and ExponeaIntegration). |
anonymize(exponeaProject:projectMapping:)is deprecated. Useanonymize(exponeaIntegrationType:exponeaProjectMapping:)instead, which accepts anyExponeaIntegrationType(both Project and Stream).
Examples
Exponea.shared.anonymize()Anonymize with completion (recommended for Stream mode):
Exponea.shared.anonymize {
// Flush and teardown are complete, safe to re-configure or navigate
}Switch to a different Project:
Exponea.shared.anonymize(
exponeaIntegrationType: ExponeaProject(
baseUrl: "https://api.exponea.com",
projectToken: "YOUR PROJECT TOKEN",
authorization: .token("YOUR API KEY")
),
exponeaProjectMapping: nil
)Switch to a Stream integration:
Exponea.shared.anonymize(
exponeaIntegrationType: ExponeaIntegration(
baseUrl: "https://api.exponea.com",
streamId: "YOUR_STREAM_ID"
),
exponeaProjectMapping: nil
)Sessions
The SDK tracks sessions automatically by default, producing two events: session_start and session_end.
The session represents the actual time spent in the app. It starts when the application is launched and ends when it goes into the background. If the user returns to the app before the session times out, the application will continue the current session.
The default session timeout is 60 seconds. Set sessionTimeout in the Configuration for iOS SDK to specify a different timeout.
Track session manually
To disable automatic session tracking, set automaticSessionTracking to false in the Configuration for iOS SDK.
Use the trackSessionStart() and trackSessionEnd() methods to track sessions manually.
Examples
Exponea.shared.trackSessionStart()The default behavior for manually calling
Exponea.shared.trackSessionStart()multiple times can be controlled by themanualSessionAutoCloseflag in theConfiguration, which is set totrueby default. If a previous session is still open (i.e., it hasn’t been manually closed withExponea.shared.trackSessionEnd()) beforeExponea.shared.trackSessionStart()is called again, the SDK will automatically track asessionEndfor the previous session and then trigger a newsessionStartevent. To prevent this behavior, set themanualSessionAutoCloseflag in theConfigurationtofalse.
Exponea.shared.trackSessionEnd()Push notifications
If developers integrate push notification functionality in their app, the SDK automatically tracks the push notification token by default.
In the Configuration for iOS SDK, you can disable automatic push notification tracking by setting the Boolean value of the pushNotificationTracking property to false. It is then up to the developer to manually track push notifications.
The behavior of push notification tracking may be affected by the tracking consent feature, which in enabled mode requires explicit consent for tracking. Refer to the Tracking consent for iOS SDK documentation for details.
Track token manually
Use the trackPushToken() method to manually track the token for receiving push notifications. The token is assigned to the currently logged-in customer (with the identifyCustomer method).
Invoking this method will track a push token immediately regardless of the value of tokenTrackFrequency (refer to the Configuration for iOS SDK documentation for details).
Each time the app becomes active, the SDK calls verifyPushStatusAndTrackPushToken and tracks the token.
Arguments
| Name | Type | Description |
|---|---|---|
| token (required) | String | String containing the push notification token. |
Example
Exponea.shared.trackPushToken("value-of-push-token")Remember to invoke anonymize whenever the user signs out to ensure the push notification token is removed from the user's customer profile. Failing to do this may cause multiple customer profiles share the same token, resulting in duplicate push notifications.
Track push notification delivery manually
Use the trackPushReceived() method to manually track push notification delivery.
You can pass either the notification data or the user info as argument.
Arguments
| Name | Type | Description |
|---|---|---|
| content (required) | UNNotificationContent | Notification data. |
or:
| Name | Type | Description |
|---|---|---|
| userInfo (required) | [AnyHashable: Any] | User info object from the notification data. |
Example
Passing notification data as argument:
func trackPushNotifReceived() {
let notifContent = UNMutableNotificationContent()
notifContent.title = "Example title"
// ... and anything you need, but only `userInfo` is required for tracking
notifContent.userInfo = [
"url": "https://example.com/ios",
"title": "iOS Title",
"action": "app",
"message": "iOS Message",
"image": "https://example.com/image.jpg",
"actions": [
["title": "Action 1", "action": "app", "url": "https://example.com/action1/ios"],
["title": "Action 2", "action": "browser", "url": "https://example.com/action2/ios"]
],
"sound": "default",
"aps": [
"alert": ["title": "iOS Alert Title", "body": "iOS Alert Body"],
"mutable-content": 1
],
"attributes": [
"event_type": "campaign",
"campaign_id": "123456",
"campaign_name": "iOS Campaign",
"action_id": 1,
"action_type": "mobile notification",
"action_name": "iOS Action",
"campaign_policy": "policy",
"consent_category": "General consent",
"subject": "iOS Subject",
"language": "en",
"platform": "ios",
"sent_timestamp": 1631234567.89,
"recipient": "[email protected]"
],
"url_params": ["param1": "value1", "param2": "value2"],
"source": "xnpe_platform",
"silent": false,
"has_tracking_consent": true,
"consent_category_tracking": "iOS Consent"
]
Exponea.shared.trackPushReceived(content: notifContent)
}Passing user info as argument:
func trackPushNotifReceived() {
let userInfo: [AnyHashable: Any] = [
"url": "https://example.com/ios",
"title": "iOS Title",
"action": "app",
"message": "iOS Message",
"image": "https://example.com/image.jpg",
"actions": [
["title": "Action 1", "action": "app", "url": "https://example.com/action1/ios"],
["title": "Action 2", "action": "browser", "url": "https://example.com/action2/ios"]
],
"sound": "default",
"aps": [
"alert": ["title": "iOS Alert Title", "body": "iOS Alert Body"],
"mutable-content": 1
],
"attributes": [
"event_type": "campaign",
"campaign_id": "123456",
"campaign_name": "iOS Campaign",
"action_id": 1,
"action_type": "mobile notification",
"action_name": "iOS Action",
"campaign_policy": "policy",
"consent_category": "General consent",
"subject": "iOS Subject",
"language": "en",
"platform": "ios",
"sent_timestamp": 1631234567.89,
"recipient": "[email protected]"
],
"url_params": ["param1": "value1", "param2": "value2"],
"source": "xnpe_platform",
"silent": false,
"has_tracking_consent": true,
"consent_category_tracking": "iOS Consent"
]
Exponea.shared.trackPushReceived(userInfo: userInfo)
}Track push notification click manually
Use the trackPushOpened() method to manually track push notification clicks.
Arguments
| Name | Type | Description |
|---|---|---|
| userInfo (required) | [AnyHashable: Any] | User info object from the notification data. |
Example
func trackPushNotifClick() {
let userInfo: [AnyHashable: Any] = [
"url": "https://example.com/ios",
"title": "iOS Title",
"action": "app",
"message": "iOS Message",
"image": "https://example.com/image.jpg",
"actions": [
["title": "Action 1", "action": "app", "url": "https://example.com/action1/ios"],
["title": "Action 2", "action": "browser", "url": "https://example.com/action2/ios"]
],
"sound": "default",
"aps": [
"alert": ["title": "iOS Alert Title", "body": "iOS Alert Body"],
"mutable-content": 1
],
"attributes": [
"event_type": "campaign",
"campaign_id": "123456",
"campaign_name": "iOS Campaign",
"action_id": 1,
"action_type": "mobile notification",
"action_name": "iOS Action",
"campaign_policy": "policy",
"consent_category": "General consent",
"subject": "iOS Subject",
"language": "en",
"platform": "ios",
"sent_timestamp": 1631234567.89,
"recipient": "[email protected]"
],
"url_params": ["param1": "value1", "param2": "value2"],
"source": "xnpe_platform",
"silent": false,
"has_tracking_consent": true,
"consent_category_tracking": "iOS Consent"
]
Exponea.shared.trackPushOpened(with: userInfo)
}Clear local customer data
Your application should always ask customers for consent to track their app usage. If the customer consents to tracking events at the application level but not at the personal data level, using the anonymize() method is usually sufficient.
If the customer doesn't consent to any tracking, it's recommended not to initialize the SDK at all.
If the customer asks to delete personalized data, use the clearLocalCustomerData(appGroup: String) method to delete all information stored locally before SDK is initialized.
The customer may also revoke all tracking consent after the SDK is fully initialized and tracking is enabled. In this case, you can stop SDK integration and remove all locally stored data using the stopIntegration method.
Invoking this method will cause the SDK to:
- Remove the push notification token for the current customer from local device storage.
- Clear local repositories and caches, including all previously tracked events that haven't been flushed yet.
- Clear all session start and end information.
- Remove the customer record stored locally.
- Clear any previously loaded in-app messages, in-app content blocks, and app inbox messages.
- Clear the SDK configuration from the last invoked initialization.
- Clear the Stream JWT from the Keychain (if Stream integration was used).
- Stop handling of received push notifications.
- Stop tracking of deep links and universal links (your app's handling of them isn't affected).
Stop SDK integration
❗️ App group must be same for configuration and NotificationServices ❗️
- otherwise received push could be tracked
Your application should always ask the customer for consent to track their app usage. If the customer consents to tracking of events at the application level but not at the personal data level, using the anonymize() method is normally sufficient.
If the customer doesn't consent to any tracking before the SDK is initialized, it's recommended that the SDK isn't initialized at all. For the case of deleting personalized data before SDK initialization, see more info in the usage of the clearLocalCustomerData method.
The customer may also revoke all tracking consent later, after the SDK is fully initialized and tracking is enabled. In this case, you can stop SDK integration and remove all locally stored data by using the Exponea.shared.stopIntegration() method.
Use the stopIntegration() method to stop the SDK, flush any pending data, and delete all information stored locally.
Preferred for Stream/JWT modeUse
stopIntegrationinstead ofanonymize()when non-anonymous traffic is required (e.g. Stream JWT mode). Unlikeanonymize,stopIntegrationdoes not create a new anonymous customer profile.
When the SDK is running, invoking this method will cause the SDK to:
- Track a
session_endevent if automatic session tracking is enabled. (If you use manual session tracking, calltrackSessionEnd()beforestopIntegration.) - Invalidate the push notification token by tracking
notification_statewithvalid: false. - Flush all pending events to the server (using the current JWT in Stream mode).
- Clear the Stream JWT from the Keychain (Stream mode).
- Remove the push notification token for the current customer from local device storage.
- Clear local repositories and caches.
- Clear all session start and end information.
- Remove the customer record stored locally.
- Clear any in-app messages, in-app content blocks, and App Inbox messages previously loaded.
- Clear the SDK configuration from the last invoked initialization.
- Stop handling of received push notifications.
- Stop tracking of deep links and universal links (your app's handling of them is not affected).
- Stop and disable session tracking, event tracking, and flushing.
- Stop displaying in-app messages, in-app content blocks, and App Inbox messages. Already displayed messages are dismissed.
After invoking stopIntegration(), the SDK will drop any API method invocation until you initialize the SDK again.
stopIntegration(completion:)
stopIntegration(completion:)Use the completion variant to be notified when flush and teardown are complete. The completion block is called on the main thread. You may safely call Exponea.shared.configure(...) again inside the completion handler (e.g. for re-login flows).
Exponea.shared.stopIntegration {
// All data flushed, JWT cleared, SDK fully torn down.
// Safe to re-configure for a different user:
Exponea.shared.configure(
Exponea.StreamSettings(streamId: "NEW_STREAM_ID"),
pushNotificationTracking: .enabled(appGroup: "YOUR_APP_GROUP")
)
Exponea.shared.setSdkAuthToken("NEW_USER_JWT")
}Please validate dismiss behaviour if you customized the App Inbox UI layout.
Use cases
Correct usage of the stopIntegration() method depends on the use case, so consider all scenarios.
Stop the SDK but upload tracked data
stopIntegration() now automatically flushes all pending events before clearing local data. You no longer need to call flushData() manually before stopping the SDK.
If you need to know when the flush completes, use the completion variant:
Exponea.shared.stopIntegration {
// All pending data has been flushed and the SDK is fully stopped.
}Stop the SDK and wipe all tracked data
The SDK caches data (such as sessions, events, and customer properties) in an internal local database and periodically sends them to the Bloomreach Engagement app. If the device has no network or if you configured the SDK to upload them less frequently, these data are kept locally.
You may face the use case where the customer gets removed from the Bloomreach Engagement platform, and subsequently, you want to remove them from local storage too.
Please do not initialize the SDK in this case. Depending on your configuration, the SDK may upload the stored tracked events. This may lead to the customer's profile being recreated in Bloomreach Engagement. Stored events may have been tracked for this customer, and uploading them will result in the recreation of the customer profile based on the assigned customer IDs.
To prevent this from happening, invoke stopIntegration() immediately without initializing the SDK:
Exponea.shared.stopIntegration()This results in all previously stored data being removed from the device. The next SDK initialization will be considered a fresh new start.
Stop the already running SDK
The method stopIntegration() can be invoked anytime on a configured and running SDK.
This can be used in case the customer previously consented to tracking but revoked their consent later. You may freely invoke stopIntegration() with immediate effect.
// User gave you permission to track
Exponea.shared.configure(...)
// Later, user decides to stop tracking
Exponea.shared.stopIntegration()This results in the SDK flushing all pending events, then stopping all internal processes (such as session tracking and push notifications handling) and removing all locally stored data.
Customer denies tracking consent
It is recommended to ask the customer for tracking consent as soon as possible in your application. If the customer denies consent, please do not initialize the SDK at all.
❗️ AppInbox remove after stopIntegration()
Add a callback to your viewController with AppInboxButton
IntegrationManager.shared.onIntegrationStoppedCallbacks.append { [weak self] in
self?.appInboxButton.removeFromSuperview()
self?.view.layoutIfNeeded()
}❗️ Stop receiving push after stopIntegration()
You have to override the method in ExponeaAppDelegate
func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
) {
super.userNotificationCenter(.....)
// your code if needed
}
}If you can't override this method and call super, make sure you add if to your method
if IntegrationManager.shared.isStopped && Exponea.isExponeaNotification(userInfo: notification.request.content.userInfo) {
Exponea.logger.log(.error, message: "Will present wont finish, SDK is stopping")
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [notification.request.identifier])
completionHandler([])
}Payments
The SDK tracks in-app purchases automatically.
Track payment
Use the trackPayment() method to track payments manually.
Arguments
| Name | Type | Description |
|---|---|---|
| properties | [String: JSONConvertible] | Dictionary of payment properties. |
| timestamp | Double | Unix timestamp specifying when the event was tracked. Specify the nil value to use the current time. |
Example
Exponea.shared.trackPayment(
properties: [
"productId": "123",
"currency": "USD",
"price": 123.45,
"quantity": 2
]
timestamp: nil
)Default properties
You can configure default properties to be tracked with every event. Note that the value of a default property will be overwritten if the tracking event has a property with the same key.
Refer to defaultProperties in the Configuration for iOS SDK documentation for details.
After initializing the SDK, you can change the default properties using the Exponea.shared.defaultProperties() method.
Updated 14 days ago
