Users, logins and leads
This is where X3 differs from app-only attribution tools: X3 already knows the web journey, the leads and their CRM status. A signup or login in your app is one more key in the same journey graph that links web sessions, so the app usage of a person lands in the lead profile you already have.
The example: on 3 October a person fills in a Meta Instant Form for Nordlicht (a lead with an
e-mail hash). On 5 October they click a retargeting ad, land on nordlicht.app, have a training
plan sent by e-mail (web identify with the e-mail hash) and tap "Get the app". The same evening
they create an account in the iPhone app with the same e-mail. The shared e-mail hash links the
Instant Form lead, the web session with its fbclid, the install and the app sessions. The iOS
install gets tier identity retroactively, and the purchase conversion "Subscription" goes to Meta
with the fbclid of the web session, although the purchase happened in the app. Had they left
nothing on the website, the install would stay without a campaign and only the Instant Form lead
would be linked.
Login and signup
Call identify after a signup, after every login and at app start when the user is still logged in
(so daily use counts without a new login):
X3.identify(userId: user.id, email: user.email) // signup, login, app start when logged in
X3.track("account_created", properties: ["method": "apple"]) // once, at signup
X3.track("login", properties: ["method": "password"]) // every login, also re-logins
X3.reset() // at logoutX3.identify(user.id, user.email, null)
X3.track("account_created", mapOf("method" to "google"))
X3.track("login", mapOf("method" to "password"))
X3.reset()X3.identify(user.id, { email: user.email });
X3.track("login", { method: "password" });
X3.reset();await X3.identify(user.id, email: user.email);
await X3.track('login', {'method': 'password'});
await X3.reset();What leaves the device. Only SHA-256 hashes: of the user id (xid), of the normalized e-mail
(em, trimmed and lowercased) plus the Google variant without dots in Gmail addresses (emg), and of
the lead id (lid). The same normalization as identify on the web, so hashes from app and web
match. Plaintext never goes through the app ingest. X3 stores keyed hashes of these values;
plaintext e-mail addresses exist only for real inquiries (X3.lead() or your server) and only when
the project stores plaintext (Einstellungen › Erweitert › „Kontaktdaten im Klartext speichern“).
What it needs. identify is sent only while the purpose analytics is granted, and the
project must allow identify from apps (default on). The identity is persisted on the device and sent
again with the first batch of every new session until reset().
No lead from a login. On the web identify with an e-mail creates a lead. In apps that would be
wrong: every login would become a lead. App identify only links. A lead comes from:
X3.lead()(an inquiry form in the app, leads);- a conversion definition on the event
account_created; - the per-app switch Signup ist Lead (Signup is a lead, default off): an
account_createdevent plus an identity in the same session creates a lead with the formapp-signup.
Known lead or CRM id. When the app knows the X3 lead id or your CRM id (for example from a magic
link), pass it as leadId. It links deterministically, like identify({ leadId }) on the web.
Account token. After identify the SDK derives an account token: a UUID (version 5) from the
bundle id and the hashed user id. It is pseudonymous (it reveals neither the user id nor the
e-mail address) but linked to the person. Pass it to the stores
(appAccountToken, setObfuscatedAccountId) so that store notifications name the person
(purchases).
Re-logins, active users, several devices
- Re-logins. Every
loginevent and every app session with a known identity appears in the lead's timeline. The lead profile card App shows the last login, logins in the last 7 and 30 days, active days and devices. A login after more than 14 days without activity can be a goal ("came back"). - Several devices. iPhone, iPad, Android and browser with the same user id end up in one journey
(key
external_id, capped per key against shared test accounts). The dashboard shows devices per person, never a device graph across persons. - Shared devices.
X3.reset()at logout separates people: later sessions inherit no identity, the account token is forgotten, a new session starts; the install id stays. - Install key. Every app session also carries the install id as a journey key (kind
install), so all sessions of one install form one journey even before a login.
Server-side alternative
If you prefer not to send identities from the app at all, report signups and logins from your own
server: leads.create for signups that are leads and
server events for logins, with the session id from X3.ids() or
X3.sessionId. This is plain server processing on your side; the app then only sends usage.
Deleting a user
Delete a user's app data with one request. It removes the identity keys (user id and e-mail hashes) and install keys, the account token mapping, the recordings of the affected sessions and the device proof of the install; leads with these hashes are erased through the normal lead erasure in the same request. Sessions stay as unlinked analytics without the person.
curl -X POST https://api.x3growth.io/v1/projects/$X3_PROJECT_ID/app-users/erase \
-H "authorization: Bearer $X3_SECRET_KEY" -H "content-type: application/json" \
-d '{ "userId": "12345", "email": "jane@example.com" }'The body takes at least one of userId, email and installId (the value of X3.installId /
X3.ids()); X3 hashes them the same way as the SDK. The secret key needs the scope leads:write;
every call is in the audit log, and the answer counts what was deleted per kind.
Apple requires apps that let people create an account to offer account deletion inside the app.
Call this endpoint from your server when an account is deleted, then call X3.reset() in the app.
Deleting the data of this device
X3.eraseDeviceData() (iOS and Android; not in the 1.0.0 React Native and Flutter wrappers) asks
X3 to delete the sessions, recordings and journey keys of this install. The device proves it owns
the install with a secret that never left it before, so no account and no secret key are needed.
Afterwards the SDK deletes its local data except the consent choice and continues with a new install
id; call X3.setOptOut(true) as well when the user also wants no further measurement. Use it for a
"Delete my usage data" button in your privacy settings. In React Native and Flutter apps, delete by
install id from your server with the request above.