Anonymous by default
From first launch the SDK mints a stable anonymous id (anon_ followed by 21 random characters) and stores it on the device. Everything works before anyone logs in: events queue and send, sessions are tracked, and the push subscription registers under that id. Nothing about the SDK requires a logged-in user.
When you later call identify, the anonymous subscriber becomes the identified one. The push subscription, the topic preferences, the attributes and the whole event history come with it, so the events someone generated during onboarding are still on their timeline once they sign up. See Merging an anonymous user.
Identify
identify(_ externalId: String, email: String? = nil, identityHash: String? = nil, attributes: [String: JSONValue]? = nil, subscribe: [Channel: Bool] = [:]). It returns immediately and does its work on a serial queue, so identity calls never race each other.
Four things happen. When the device was anonymous, that anonymous subscriber is merged into the identified one. The subscriber is then created or updated with the id, email and attributes you passed, along with the device context. If the identity actually changed, the queued events are flushed and the push subscription is re-registered under the new id.
The email is saved on the subscriber as the email attribute whether you pass it as email: or inside attributes, and it is saved even before an email provider is connected to the tenant. Once one is, identify also subscribes the address, so the user can be emailed without another call. Pass subscribe: [.email: false] to keep an address on file without subscribing it, for example while it is still unverified.
Call it at login, and on every launch where the user is already signed in. It is an idempotent upsert on your own id, so calling it repeatedly costs nothing.
externalId is your own user id. It is the same id your backend addresses in PUT /v1/subscribers/:externalId and in to on a send, which is what keeps the app and your server talking about the same person. See Subscribers.Merging an anonymous user
The SDK remembers the anonymous id it minted and sends it asanonymousId on the first identify after a device was anonymous. The API merges that subscriber into the identified one, so signing up never loses what came before.
The merge takes one of two shapes. When the id you identify with is new, which is what a first sign-up does, the subscriber is simply renamed: it keeps its row, its id, its actor and its event sequence, so nothing moves at all and it is the same subscriber under a new name. When that id already exists, for example a second device or a user signing back in, there are genuinely two subscribers, so the anonymous one is absorbed into the identified one: its subscriptions and preferences move, its retained events are copied onto the identified timeline, and it is retired.
Either way a
$subscriber.merged event lands on the timeline carrying the id it merged from, and the anonymous id is kept as an alias of the identified subscriber. Nothing is thrown away: GET /v1/subscribers/anon_9RmZ… still answers with the person that device turned out to be, and a client that is still holding the old id reaches the right subscriber instead of creating a new one.
Calling
identify again with the same anonymous id does nothing the second time, so retries and every-launch calls are safe. Only an anonymous, never-verified subscriber can be merged; a request naming an identified subscriber is refused with merge_source_identified.Attributes
[String: JSONValue], and JSONValue is expressible by string, integer, float, boolean, nil, array and dictionary literals, so ordinary Swift literals work without any wrapping.
The server merges what the device sends into what is already on the subscriber. Adding and changing keys from the app is safe, and nothing your backend set is wiped by a call from the device.
The SDK maintains one attribute of its own, the device’s notification permission, refreshed on launch and on every change. Segments read it like any other attribute.
Logout
Identity verification
A client key is embedded in your binary, so on its own it lets any caller claim any external id. Verification closes that: your backend computes an HMAC of the user’s id under the tenant’s identity secret and hands the result to the app at login.identify and the subscriber is marked verified. The SDK stores it and attaches it to every call it makes from then on, including events, subscription updates and preference changes, for as long as the user stays identified. logout discards it along with the id.