Adjust GmbH

Adjust GmbH

fb_anon_id property

In the fb_anon_id property, Adjust transmits the anon_id property from the Facebook SDK, which is a unique ID for the device that Facebook generates and persists on the device.

This can be verified by looking at the source code of the Adjust SDK for iOS.

Here, the fb_anon_id property is set to self.packageParams.fbAnonymousId (source):

1[ADJPackageBuilder parameters:parameters setString:self.packageParams.fbAnonymousId forKey:@"fb_anon_id"];

self.packageParams is an instance of the ADJPackageParams class (source):

1@property (nonatomic, weak) ADJPackageParams *packageParams;

In the constructor of the ADJPackageParams class, the fbAnonymousId property is set to the return value of a function call to [ADJUtil fbAnonymousId] (source):

1self.fbAnonymousId = [ADJUtil fbAnonymousId];

Finally, [ADJUtil fbAnonymousId] calls the Facebook SDK’s retrievePersistedAnonymousID function in the FBSDKBasicUtility (source):

 1// post FB SDK v6.0.0
 2// return [FBSDKBasicUtility retrievePersistedAnonymousID];
 3Class class = nil;
 4SEL selGetId = NSSelectorFromString(@"retrievePersistedAnonymousID");
 5class = NSClassFromString(@"FBSDKBasicUtility");
 6if (class != nil) {
 7    if ([class respondsToSelector:selGetId]) {
 8#pragma clang diagnostic push
 9#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
10        NSString *fbAnonymousId = (NSString *)[class performSelector:selGetId];
11        return fbAnonymousId;
12#pragma clang diagnostic pop
13    }

The retrievePersistedAnonymousID method returns Facebook’s anon_id. For more details on that, see our documentation for properties sent to Facebook’s tracking endpoints.

We have only observed this property on iOS, and found no references to it in the source code of the Adjust SDK for Android.

installed_at property

The installed_at property holds the date and time of when the app was installed. This can be verified by looking at the source code of Adjust’s SDKs.

In the Adjust SDK for Android, the property installed_at is set to the value of deviceInfo.appInstallTime (source):

1PackageBuilder.addString(parameters, "installed_at", deviceInfo.appInstallTime);

The deviceInfo object is an instance of the DeviceInfo class (source):

1private DeviceInfo deviceInfo;

In the constructor of the DeviceInfo class, appInstallTime is set to the return value of the getAppInstallTime() function (source):

1appInstallTime = getAppInstallTime(context);

The getAppInstallTime() function returns a formatted string of the firstInstallTime property of Android’s PackageInfo class (source):

 1private String getAppInstallTime(Context context) {
 2    try {
 3        PackageManager packageManager = context.getPackageManager();
 4        PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS);
 6        String appInstallTime = Util.dateFormatter.format(new Date(packageInfo.firstInstallTime));
 8        return appInstallTime;
 9    } catch (Exception ex) {
10        return null;
11    }

And, according to the Android Developers documentation for PackageInfo, firstInstallTime is:

The time at which the app was first installed.

In the Adjust SDK for iOS, the installed_at property is set to the value of self.packageParams.installedAt (source):

1[ADJPackageBuilder parameters:parameters setString:self.packageParams.installedAt forKey:@"installed_at"];

self.packageParams is an instance of the ADJPackageParams class (source):

1@property (nonatomic, weak) ADJPackageParams *packageParams;

In the constructor of the ADJPackageParams class, the installedAt property is set to the return value of a function call to [ADJUtil installedAt] (source):

1self.installedAt = [ADJUtil installedAt];

Finally, [ADJUtil installedAt] returns a formatted string of the date and time when the app was installed by looking at the creation date of the app’s files (source):

 1+ (NSString *)installedAt {
 2    // […]
 3    @try {
 4        NSArray *paths = NSSearchPathForDirectoriesInDomains(folderToCheck, NSUserDomainMask, YES);
 5        if (paths.count > 0) {
 6            pathToCheck = [paths objectAtIndex:0];
 7        } else {
 8            pathToCheck = [[NSBundle mainBundle] bundlePath];
 9        }
11        __autoreleasing NSError *error;
12        __autoreleasing NSError **errorPointer = &error;
13        Class class = NSClassFromString([NSString adjJoin:@"N", @"S", @"file", @"manager", nil]);
14        if (class != nil) {
15            NSString *keyDm = [NSString adjJoin:@"default", @"manager", nil];
16            SEL selDm = NSSelectorFromString(keyDm);
17            if ([class respondsToSelector:selDm]) {
18#pragma clang diagnostic push
19#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
20                id man = [class performSelector:selDm];
21#pragma clang diagnostic pop
22                NSString *keyChk = [NSString stringWithFormat:@"%@%@",
23                        [NSString adjJoin:@"attributes", @"of", @"item", @"at", @"path", @":", nil],
24                        [NSString adjJoin:@"error", @":", nil]];
25                SEL selChk = NSSelectorFromString(keyChk);
26                if ([man respondsToSelector:selChk]) {
27                    NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[man methodSignatureForSelector:selChk]];
28                    [inv setSelector:selChk];
29                    [inv setTarget:man];
30                    [inv setArgument:&pathToCheck atIndex:2];
31                    [inv setArgument:&errorPointer atIndex:3];
32                    [inv invoke];
33                    NSMutableDictionary * __unsafe_unretained tmpResult;
34                    [inv getReturnValue:&tmpResult];
35                    NSMutableDictionary *result = tmpResult;
36                    CFStringRef *indexRef = dlsym(RTLD_SELF, [[NSString adjJoin:@"N", @"S", @"file", @"creation", @"date", nil] UTF8String]);
37                    NSString *ref = (__bridge_transfer id) *indexRef;
38                    installTime = result[ref];
39                }
40            }
41        }
42    } @catch (NSException *exception) {
43        [logger error:@"Error while trying to check install date. Exception: %@", exception];
44        return nil;
45    }
47    return [ADJUtil formatDate:installTime];

Unique tracking IDs

The android_uuid, ios_uuid, persistent_ios_uuid, and web_uuid properties are unique IDs for the device that Adjust generates on install and correlates with other device IDs.

According to Adjust’s documentation for iOS (archived):

When users reset their advertising ID, uninstall and reinstall your app, or enable LAT, Adjust will not be able to retrieve their IDFA and/or IDFV. To continuously track users’ in-app sessions, Adjust relies on a permanent, locally generated UUID persisted to the device keychain. […] […] How does Adjust manage UUIDs? Adjust generates a UUID upon install. This is mapped to other device information on our backend.

Further, Adjust’s documentation for iOS (archived) says:

persistent_ios_uuidSame as ios_uuid, but saved in Keychain so that re-installed apps will have same value, iOS only3b35fcfb-6115-4cff-830f-e32a248c487d

And according to Adjust’s documentation for the web (archived):

To identify unique web users, the Adjust web SDK generates a unique web_uuid when it measures a session. The ID is created per subdomain and per browser. This means that if a user switches from Chrome to Safari, the SDK creates 2 web_uuid entries for the device. The identifier follows the Universally Unique Identifier (UUID) format.

  • Example: web_uuid=4d1615ab-ee78-49aa-a10f-d57035322a42

Adjust uses IndexedDB to store the web_uuid in the browser, or falls back to localStorage if IndexedDB is not available.

And while the property is not documented for Android, we can verify that the same applies here by looking at the source code for Adjust SDK for Android:

Here, the android_uuid property is set to activityStateCopy.uuid (source):

1// Device identifiers.
3PackageBuilder.addString(parameters, "android_uuid", activityStateCopy.uuid);

activityStateCopy is an instance of the ActivityStateCopy class that is passed an instance of the ActivityState class (source):

1PackageBuilder(AdjustConfig adjustConfig,
2               DeviceInfo deviceInfo,
3               ActivityState activityState,
4               SessionParameters sessionParameters,
5               long createdAt) {
6    // […]
7    this.activityStateCopy = new ActivityStateCopy(activityState);
8    // […]

An ActivityStateCopy object is just a wrapper around the ActivityState class, which exposes a few of its properties, including the uuid property (source):

1ActivityStateCopy(ActivityState activityState) {
2    // […]
3    this.uuid = activityState.uuid;
4    // […]

In an ActivityState object, the uuid property is set to the return value of a call to the Util.createUuid() function (source):

1// create UUID for new devices
2uuid = Util.createUuid();

And the Util.createUuid() function uses the randomUUID() of the java.util.UUID package (documentation) to generate a version 4 UUID (source):

1import java.util.UUID;
3// […]
5protected static String createUuid() {
6    return UUID.randomUUID().toString();