Identity & Device IDs
The Datafly mobile SDKs use a multi-layered identity model to track users across sessions, app reinstalls, and the transition from anonymous to known user.
Bridging identities
Two envelope fields let you tell the server that two identities are the same person, or that an event has already been reported elsewhere.
setPreviousId — app-to-web handoff and logout
reset() mints a fresh anonymous ID. The SDK automatically carries the previous
one on the next event, so the server merges the two identities instead of
treating a logout/login cycle as two different people.
Call it explicitly when you know the other surface’s ID — for example, when your
app opens a web view that already has a _dfid:
Datafly.shared.setPreviousId("the-web-anonymous-id")Datafly.setPreviousId("the-web-anonymous-id")Datafly.setPreviousId('the-web-anonymous-id');The ID is sent once, on the next event. Repeating it every event would force a redundant identity lookup server-side.
event_id — deduplicating against an existing pixel
If you run a vendor SDK alongside Signal (a Meta SDK reporting the same purchase,
say), pass a shared event_id so the vendor deduplicates rather than counting the
conversion twice. Use the same value you pass to the vendor’s own SDK — typically
your order ID.
Datafly.shared.track("purchase", properties: [
"event_id": orderId,
"revenue": 99.99,
])Datafly.track("purchase", mapOf(
"event_id" to orderId,
"revenue" to 99.99,
))The SDK lifts event_id (or eventId) out of your properties into the event
envelope, so it never reaches a vendor as an ordinary property. Meta CAPI and
TikTok Events API both receive it as their native event_id. If you omit it,
the delivery layer falls back to the SDK’s own message ID, which deduplicates
Signal’s own retries but cannot match a vendor SDK’s events.
Collected IDs — identifiers another SDK owns
Some identifiers cannot be generated on-device because a different SDK owns
them. The Firebase app instance ID is the case that matters: the GA4
Measurement Protocol requires app_instance_id for app properties, and only
the Firebase SDK can produce it.
Read it from Firebase and hand it over:
Analytics.appInstanceID { id in
if let id { Datafly.shared.setCollectedId("firebase_app_instance_id", value: id) }
}FirebaseAnalytics.getInstance(context).appInstanceId
.addOnSuccessListener { Datafly.setCollectedId("firebase_app_instance_id", it) }Datafly.setCollectedId('firebase_app_instance_id', appInstanceId);Blueprints read these as collected_ids.<key>. Values are persisted, survive a
reset() (they describe the app install, not the person) and are sent once per
change — the server stores them against the canonical identity, so resending on
every event would be waste.
Without this, no mobile install or in-app conversion can reach a GA4 app property or Firebase. Datafly cannot generate the value for you.
Identity layers
| Layer | What it is | Who sets it | Persistence |
|---|---|---|---|
| Anonymous ID | UUID v4 generated on first launch | SDK (client) | iOS: Keychain / Android: SharedPreferences |
| Device ID | IDFV (iOS) or ANDROID_ID (Android) | OS (automatic) | System-managed |
| IDFA / AAID | Advertising identifier | App (after consent) | System-managed |
| User ID | Your user’s database ID | App (via identify()) | Memory (cleared on reset()) |
| Canonical ID | Unified server-side identity | Server (automatic) | Server-side |
How it works
1. App install (new user)
On first launch, the SDK:
- Generates a new anonymous ID (UUID v4)
- Collects the device ID (IDFV on iOS, ANDROID_ID on Android)
- Self-generates vendor tracking IDs (GA4
client_id, Metafbp, TikTokttp, Pinterest_pin_unauth) - Starts session 1
The first event sent to the server creates a canonical identity that links all these signals:
canonical:org123:uuid-canonical-1
├── link:dfid:uuid-anonymous-1 (anonymous ID)
├── link:idfv:uuid-vendor-1 (device ID)
└── vendor_ids: { ga_client_id, fbp, ttp, pin_unauth }2. User logs in
When the app calls identify("user-123"):
canonical:org123:uuid-canonical-1
├── link:dfid:uuid-anonymous-1
├── link:idfv:uuid-vendor-1
├── link:user_id:user-123 (now linked)
└── vendor_ids: { ga_client_id, fbp, ttp, pin_unauth }All pre-login events are now attributed to the identified user.
3. App reinstall
When the app is reinstalled, a new anonymous ID is generated. The server recovers the identity through device recognition:
New anonymous ID: uuid-anonymous-2
Device recognition hash matches → same device
Server links: dfid:uuid-anonymous-2 → canonical:uuid-canonical-1
Result: same canonical identity, continuous user historyDevice recognition
The server computes a SHA-256 hash from multiple device signals to identify returning devices:
| Signal | Weight | Source |
|---|---|---|
| IP address | 25 | Request headers |
| User agent | 25 | SDK-generated (includes device model, OS version) |
| Screen resolution | 15 | Device info |
| Accept-Language | 15 | Request headers |
| Timezone | 10 | Device locale |
| Locale | 10 | Device locale |
If the hash matches a previously seen device (confidence threshold met), the server links the new anonymous ID to the existing canonical identity.
Device recognition includes a collision cap (default 15 distinct anonymous IDs per hash). If exceeded — for example on a corporate network where many devices share the same IP and user agent — the hash is marked unreliable and device recognition is disabled for that fingerprint.
Identity correction
After processing device recognition, the server may return a corrected_anonymous_id in the batch response. The SDK automatically updates its stored anonymous ID to match:
SDK sends event with anonymousId: "uuid-anonymous-2"
Server recognises device → returns corrected_anonymous_id: "uuid-anonymous-1"
SDK updates Keychain/SharedPreferences with corrected ID
Subsequent events use the corrected IDPlatform-specific persistence
iOS
| Data | Storage | Survives reinstall | Survives factory reset |
|---|---|---|---|
| Anonymous ID | Keychain (kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly) | Yes | No |
| Anonymous ID backup | UserDefaults | No | No |
| IDFV | System | Yes (same vendor) | No |
| Vendor IDs | UserDefaults | No | No |
| Session number | UserDefaults | No | No |
The Keychain recovery chain: Keychain → UserDefaults backup → generate new.
Android
| Data | Storage | Survives reinstall | Survives factory reset |
|---|---|---|---|
| Anonymous ID | SharedPreferences | No | No |
| ANDROID_ID | System (per-signing-key since Android 8.0) | Yes | No |
| Vendor IDs | SharedPreferences | No | No |
| Session number | SharedPreferences | No | No |
Android has weaker client-side persistence than iOS. On app reinstall, a new anonymous ID is generated and vendor IDs are regenerated. The server-side device recognition system compensates by linking the new anonymous ID back to the existing canonical identity.
Vendor ID generation
On first launch, the SDK self-generates tracking IDs that match the format each vendor expects:
| Vendor | ID name | Format | Example |
|---|---|---|---|
| GA4 | ga_client_id | {random}.{timestamp} | 7234856102.1712498234 |
| Meta | fbp | fb.1.{timestamp_ms}.{random} | fb.1.1712498234567.8765432109 |
| TikTok | ttp | 27-char base36 | abcdefghijklmnopqrstuvwxyz1 |
pin_unauth | UUID v4 | 6f4f9e2a-1b3c-4d5e-8f9a-0b1c2d3e4f5a |
These are sent once on the first event, then cached server-side. This means you don’t need to embed Meta SDK, GA4 SDK, etc. in your app — Datafly generates compatible IDs natively.
Advertising IDs (IDFA / AAID)
Advertising IDs require explicit user consent and must be set by the app after permission is granted:
iOS (IDFA)
import AppTrackingTransparency
import AdSupport
ATTrackingManager.requestTrackingAuthorization { status in
if status == .authorized {
let idfa = ASIdentifierManager.shared().advertisingIdentifier.uuidString
Datafly.shared.setAdvertisingId(idfa, enabled: true)
}
}Android (AAID)
// Using Google Play Services (separate dependency)
val adInfo = AdvertisingIdClient.getAdvertisingIdInfo(context)
if (!adInfo.isLimitAdTrackingEnabled) {
Datafly.setAdvertisingId(adInfo.id, enabled = true)
}Never call setAdvertisingId() without first obtaining user consent. On iOS, this requires the App Tracking Transparency (ATT) prompt. On Android, respect the user’s “Opt out of Ads Personalization” setting.
Logout / reset
Call reset() when the user logs out:
Datafly.shared.reset()This:
- Generates a new anonymous ID
- Clears user ID and traits
- Clears group ID and traits
- Regenerates vendor IDs
- Does not clear the event queue (pending events are still delivered)