JustTrack
Search
⌃K

iOS SDK

Add the SDK to your app7

To integrate the justtrack SDK into your iOS app you first need an API token. Your API token should look like this: prod-57e...64 characters...e542 and is scoped to your bundle identifier.

CocoaPods

You can use CocoaPods to add the justtrack SDK to your app. Add the following line to your Podfile:
pod 'JustTrackSDK', '4.3.8'
Afterwards, run pod install in your project to actually download and install the justtrack SDK for your project.
We support Xcode version 13.2.1 and later.

Instantiate the SDK

The SDK consists of a handful of (public) classes and protocols you can interact with. The main protocol of these is JustTrackSdk which allows you to attribute the current user, send notifications to the backend or record user events. To create an instance of the SDK you have to invoke the JustTrackSdkBuilder class. Instantiating the SDK could look like this:
do {
let sdk = try JustTrackSdkBuilder(apiToken: "prod-...").build()
} catch {
// apiToken has invalid format...
}
You should call onDestroy on the SDK when your app terminates. This will unregister all listeners and tear down the session tracking. You have to create a new instance of the SDK before you can use it again.

Get an attribution

An attribution tells you from which source you got a user. Did the user click on an Ad and installed your app? Did they find it on their own in the App Store and simply downloaded it? Did another user invite the user so they can play your game together (or against each other)? An attribution contains these information by providing the campaign, channel, and network the user was acquired through. It also assigns a unique justtrack ID to that user which is later used to identify a user. After initializing the SDK you can call attributeUser to retrieve an attribution containing your justtrack user id as well as information about the source of the user.
sdk.attributeUser().observe(using: { result in
switch result {
case .failure(let error):
// handle error
case .success(let response):
log("My user id is " + response.getUuid().uuidString)
}
})
If you are only using the justtrack dashboard to keep track of your install sources, you can ignore the result of attributeUser in your app. To get the information about the user attribution in your app you need to call observe on the future returned by attributeUser. This will call the provided callback with the Result of the attribution of the user.
If the attribution can not be performed because the user is offline, it will eventually fail. The SDK will then wait for a new internet connection and retry the attribution. An attribution obtained after the returned Future resolved to a failure value will not update the future. You have to subscribe to updates of the attribution using registerAttributionListener(listener:) to receive the attribution.

Structure of an attribution

The AttributionResponse interface implements the access layer to an attribution on Swift side, but basically an attribution can look like this:
{
"userId": "9cf85b2b-71c9-491a-a2b4-72478b3eec11",
"installId": "23db1b68-6ab9-4287-8c5a-969c51053cfc",
"userType": "acquisition",
"campaign": {
"id": 42,
"name": "ExampleMcoinsCampaign",
"type": "acquisition"
},
"type": "mcoins",
"channel": {
"id": 100,
"name": "ExampleDirectChannel",
"incent": false
},
"network": {
"id": 200,
"name": "ExampleAdNetwork"
},
"sourceId": "example-source-id",
"sourceBundleId": "example-source-bundle-id",
"sourcePlacement": "example-source-placement",
"adsetId": "example-ad-set-id",
"attributedAt": "2020-07-10T09:45:43Z",
"recruiter": null
}
Such a response corresponds to a user who clicked on an ad from the ad set example-ad-set-id in ExampleAdNetwork which belongs to the ExampleDirectChannel. The user thus was not recruited by another user of your app, but instead was bought by a paid advertisement. The sourceId, sourceBundleId, and adsetId fields will not always be set if you paid for a user.
{
"userId": "9cf85b2b-71c9-491a-a2b4-72478b3eec11",
"installId": "23db1b68-6ab9-4287-8c5a-969c51053cfc",
"userType": "acquisition",
"campaign": {
"id": 31,
"name": "ExampleDefaultCampaign",
"type": "acquisition"
},
"type": "organic",
"channel": {
"id": 100,
"name": "ExampleDirectChannel",
"incent": false
},
"network": {
"id": 200,
"name": "Organic"
},
"sourceId": null,
"sourceBundleId": null,
"sourcePlacement": null,
"adsetId": null,
"attributedAt": "2020-07-10T09:45:43Z",
"recruiter": null
}
This user installed your app directly from the App Store without any advertisements we can track. A user who sees a TV ad for your app might for example end up with such an attribution.
{
"userId": "9cf85b2b-71c9-491a-a2b4-72478b3eec11",
"installId": "23db1b68-6ab9-4287-8c5a-969c51053cfc",
"userType": "acquisition",
"campaign": {
"id": 15,
"name": "ExampleAffiliateCampaign",
"type": "acquisition"
},
"type": "affiliate",
"channel": {
"id": 120,
"name": "ExampleSocialChannel",
"incent": false
},
"network": {
"id": 210,
"name": "Affiliate"
},
"sourceId": null,
"sourceBundleId": null,
"sourcePlacement": null,
"adsetId": null,
"attributedAt": "2020-07-10T09:45:43Z",
"recruiter": {
"advertiserId": "122ea0b9-544c-4b62-9029-05282279df93",
"userId": "944d6dd3-ac46-4184-a00a-e715a675c16c",
"packageId": "com.example.packageId",
"platform": "ios"
}
}
This user clicked on an affiliate link of another user of your app before installing your app.

Getting the Advertiser and Test Group Id

The justtrack SDK can provide you with the IDFA of the user if the user didn't limit ad tracking. Additionally, users are divided into three test groups (1, 2, and 3) based on their IDFV. You can retrieve that test group id from the SDK, implement a different logic for one of the test groups, and then compare the (different) performance of that group on the justtrack dashboard.
let info = sdk.getAdvertiserIdInfo()
let advertiserId: String? = info.getAdvertiserId()
let isLimitedAdTracking = info.isLimitedAdTracking()
log("My advertiser id is " + advertiserId)
log("Ad tracking is limited = " + isLimitedAdTracking)
let testGroupId: Int? = sdk.getTestGroupId()
log("My test group id is " + testGroupId)

Providing your own user id

If you already have a mechanism to assign a unique id to each user, you can share this information with the justtrack SDK. This then allows the backend of the justtrack SDK to associate events received from third parties via that user id with the correct user on justtrack side. You can provide as an id multiple times, the justtrack SDK takes care about sending it to the justtrack backend as needed.
You can supply the own user id when constructing the SDK as well as sending it later once your app is already running for some time. Should your own user id change for some reason, you have to supply the new value to the justtrack SDK again:
var ownUserId = /* get your own user id from your app */
let builder = /* create the builder somehow */ JustTrackSdkBuilder(apiToken: "prod-...")
let sdk = builder
.set(customUserId: ownUserId)
// other options...
.build()
ownUserId = /* new user id for some reason */
sdk.set(customUserId: ownUserId)
A custom user id must be shorter than 4096 characters and only consist of printable ASCII characters (U+0020 to U+007E).

User events

Publishing an event requires requires you to create an event object which you then can pass to publishEvent:
let event = UserEvent("namespace_module_action").build()
sdk.publishEvent(event: event)
By calling build() on a UserEvent instance you are creating an immutable PublishableUserEvent object which you can pass to publishEvent() as often as you need. Calling build() does not modify the UserEvent object if you need to modify it again.
The SDK defines a broad range of event names with defined semantics for you to make use of. For each event the SDK also documents which values should be supplied as a dimension. For example, to track the navigation from your menu screen to a game screen, you could record the following event:
sdk.publishEvent(UserScreenShowEvent(elementName: "Game Main", elementId: "SCREEN_GAME_MAIN").build());

Building a user event

A UserEvent instance is actually a builder for the event we later send to the backend. You can either provide all parameters via the constructor if you want or you can initialize an empty event and later call setters for the different properties. The only thing you need to create a new event is the name of the event, which may also not be empty (and can later no longer be changed). The following constructors are provided:
public class UserEvent {
public init(name: EventDetails)
public init(name: EventDetails, value: Double, unit: Unit)
public init(name: EventDetails, money: Money)
public init(name: EventDetails, dimension1: String)
public init(name: EventDetails, dimension1: String, dimension2: String)
public init(name: EventDetails, dimension1: String, dimension2: String, dimension3: String)
public init(name: EventDetails, dimension1: String, dimension2: String, dimension3: String, value: Double, unit: Unit)
public init(name: EventDetails, dimension1: String, dimension2: String, dimension3: String, money: Money)
}
The EventDetails class captures the name of a user event with some additional details. Those are the category, element, and action of the event and can be used to group events together. For example, all events generated by the SDK regarding the session tracking share the category session.
value and unit can only be provided together and default to 1 and .count. Alternatively, an event can carry a monetary value instead of a value with a unit. The three dimensions default to the empty string if not specified. You can also set the properties after initialization:
public protocol UserEventBuilder {
func with(dimension1: String) -> UserEventBuilder
func with(dimension2: String) -> UserEventBuilder
func with(dimension3: String) -> UserEventBuilder
func with(value: Double, unit: Unit) -> UserEventBuilder
func with(money: Money) -> UserEventBuilder
func with(count: Double) -> UserEventBuilder
func with(seconds: Double) -> UserEventBuilder
func with(milliseconds: Double) -> UserEventBuilder
func build() -> PublishableUserEvent
}
with(count:), with(seconds:), and with(milliseconds:) are provided for your convenience if you know an event will always use a specific unit.

Sending a user event to the backend

If you are interested in whether an event could be successfully send to the backend, you can await the future returned by publishEvent:
sdk.publishEvent(event: event).observe(using: { result in
switch result {
case .failure(let error):
// event failed to reach our servers
case .success(_):
// the event was successfully published
}
})

User event restrictions

The following restrictions apply to user events:
  • The name must not be empty.
  • Name, action, category, and element must be shorter than 256 characters and can only consist of printable ISO 8859-1 characters (U+0020 to U+007E as well as U+00A0 to U+00FF).
  • Dimension values must be shorter than 4096 characters and can only consist of printable ISO 8859-1 characters (U+0020 to U+007E as well as U+00A0 to U+00FF).
  • The value of a user event must be finite (i.e., not NaN or ±Infinity).
  • If you provide a monetary value, the currency needs to be a 3 letter uppercase ISO 4217 string.

Session tracking

The SDK provides automatic session tracking. This works by wrapping the UIApplicationDelegate set on UIApplication.shared and handling the events relevant for the SDK. If you did set a delegate there already your delegate will still get called as before. If you set another delegate after initializing the SDK you will overwrite the delegate set by the SDK and session tracking will no longer work.

Progression time tracking

The justtrack SDK automatically tracks the time a user spends in each level and quest you are tracking via the PROGRESSION_LEVEL_START and PROGRESSION_QUEST_START events. The tracking ends once you trigger the PROGRESSION_LEVEL_FINISH or PROGRESSION_LEVEL_FAIL events for levels and PROGRESSION_QUEST_FINISH or PROGRESSION_QUEST_FAIL events for quests. These events are then automatically modified to also carry the total time the user spend with the game open on his device.
Example: A user starts a level at 1pm and plays for 5 minutes. He then is interrupted by a phone call and your game is running in the background for 10 minutes. Afterwards he continues to play and finishes the level after another 3 minutes. Once you trigger the corresponding finish event the SDK computes that the user took 8 minutes to finish the level and sends this value to the backend. You can then later see on the justtrack dashboard how long users take in general to complete a single level and identify levels which are unreasonably hard or too easy compared to your expectation.
There are two important aspects to this automatic tracking. First, each time the user finishes or fails a level you have to submit another start event for that level again to restart the tracking. If a user fails to complete a level first, we would add the time between the start and the fail event and attach it to the fail event. If the user now retries the level without another start event getting triggered, the next fail or finish event will not have any duration attached. Thus, there should be one start event for every fail or finish event. The flow could look something like this:
As you can see, each event is carrying some string in the above example. They represent the element name dimension of the events. If two progression events carry different element names or IDs, we will treat them like separate levels and not match them. Thus, if you send a finish event for level 2 two seconds after starting level 1 we will not add a duration of two seconds to that event, but instead look for some other start event in the past for level 2. Similarly, quests and levels are of course different namespaces and will not be mixed, either.

IronSource integration

The SDK can automatically integrate with the IronSource SDK and publish UserEvents for each ad impression. Additionally, the SDK can set the user id in the IronSource SDK to the justtrack user id or an id of your choice to enable the justtrack backend to match the ad impressions with the users during a revenue import. You can either configure the SDK to integrate with the IronSource SDK upon instantiation of your instance of the justtrack SDK or later perform an explicit call.
If you tell the SDK builder to integrate with the IronSource SDK you will not get notified about whether the integration failed or was successful. As the integration depends on the attribution, it is done asynchronously in the background, so it might not be done after the call to build() returns. You can enable the integration like this:
let builder = /* create the builder somehow */ JustTrackSdkBuilder(apiToken: "prod-...")
let sdk = builder
.set(enableIronSourceIntegration: true, userIdSource: .justtrack)
// or, if you want to provide your own user id:
.set(enableIronSourceIntegration: true, userIdSource: .customUserId("...your user id..."))
// other options...
.build()
Each call to set(enableIronSourceIntegration:userIdSource:) overrides the previous settings, only the last call before instantiating the justtrack SDK applies.
Alternatively, you can trigger the integration yourself after creating an instance of the justtrack SDK. Only one method (configuring the builder or setting up the integration manually) is needed. To set up the integration manually, take a look at the following code snippet:
// kick off the integration, will eventually complete as long as attribution succeeds
sdk.integrateWithIronSource(userIdSource: .justtrack)
// or, if you want to provide your own user id:
sdk.integrateWithIronSource(customUserId: "...your user id...")
If you supply your own user id to IronSource (either by letting the justtrack SDK forward it or by specifying .noUserId), you need to provide it as a custom user id to the justtrack SDK. Otherwise, we will not be able to import the IronSource revenue later on and match ad impressions to the correct user in every case.

Forwarding Ad Impressions

The justtrack SDK can forward data about ad impressions to the justtrack backend to track the ad revenue of your app. If you are using the IronSource integration, this will already happen automatically for IronSource ad impressions. For other ad network integrations (AdMob for example), you have to provide the ad impression events yourself by calling forwardAdImpression:
sdk.forwardAdImpression(adFormat: adFormat, adSdkName: adSdkName, adNetwork: adNetwork, placement: placement, abTesting: abTesting, segmentName: segmentName, instanceName: instanceName, revenue: revenue)
You need to provide the following parameters to such a call:
  • adFormat: The format of the ad.
  • adSdkName: The name of the SDK which provided the event, for example "ironsource" or "admob".
  • adNetwork: The name of the ad network which provided the ad. May be null.
  • placement: The placement of the ad. May be null.
  • abTesting: The test group of the user if we are A/B testing. May be null.
  • segmentName: The name of the segment of the ad. May be null.
  • instanceName: The name of the instance of the ad. May be null.
  • revenue: The amount of revenue this ad generated. May be zero but must not be negative.
Capitalization of the parameter values is important (i.e., "Ironsource" would not work as ad SDK name). Upon success, forwardAdImpression returns true, if a parameter was invalid, false is returned.

Forwarding in-app purchases

If the user performs an in-app product or subscription purchase, you can forward the purchase via the justtrack SDK to our backend. By default, this is already enabled and every purchase of the user will be forwarded automatically. You can enable or disable the integration by calling set(automaticInAppPurchaseTracking:) when creating the SDK instance or on the SDK:
let builder = /* create the builder somehow */ JustTrackSdkBuilder(apiToken: "prod-...")
let sdk = builder
// configure the SDK when creating an instance:
.set(automaticInAppPurchaseTracking: forwardPurchasesAutomatically)
// other options...
.build()
// configure an already existing SDK instance:
sdk.set(automaticInAppPurchaseTracking: forwardPurchasesAutomatically)
When enabled, all purchases will be reported automatically to the justtrack backend and, if you have correctly configured purchase validation, show up as revenue for your app.
You have to configure a shared secret to correctly validate purchases.

Firebase Integration

If you are using the Firebase SDK next to the justtrack SDK, you can use the justtrack SDK to send the Firebase app instance id of a user to the justtrack backend. You can then later send events from the justtrack backend to Firebase to measure events happening outside of your app. To send these events, the justtrack backend needs the Firebase app instance id of the user. You can send this by calling sdk.publishFirebaseAppInstanceId(firebaseAppInstanceId), but of course this requires you to add additional boilerplate code to your app.
To help you here, the justtrack SDK offers the sdk.integrateWithFirebase() method.

App Tracking Transparency

Starting with iOS 14, the justtrack SDK can only access the IDFA if the user allowed the app to track them. By default, the justtrack SDK does not request this permission and can (albeit somewhat limited) still attribute the user to the correct campaign. To improve this precision, you can ask the user for permission to track them.
You can use JustTrack.requestTrackingAuthorization to prompt the user for permission on iOS 14+. On earlier versions it will fall back to a simple check whether the user allowed tracking at all (there is nothing to prompt the user for). It will also track the decision of the user (only for the first time a user got a prompt), so you can check, how many of your users actually grant you permission to track them. Initialization of the justtrack SDK could then look like this:
JustTrack.requestTrackingAuthorization { success in
// success is true if we got permission, false otherwise. You can use this for your own logic,
// we will ignore this here
do {
// create the instance of the JustTrack SDK only after trying to get permission to read the IDFA
let sdk = try JustTrackSdkBuilder(apiToken: apiToken).build()
// use the SDK for your app
} catch {
// your API token had an invalid format
}
}
You have to set the NSUserTrackingUsageDescription [1] key in your Info.plist file when requesting the tracking permission from the user.

References