Contacts
The SDK ties the device to a contact in Positive User, starting with an anonymous contact and identifying it when the user logs in. How contacts are matched and when they switch: Contacts overview.
Requirements
- The SDK installed and initialized, see Installation.
- Custom contact attributes created in the panel before you send them. Unknown attribute names are ignored.
Setup
- Start a session. Call
ping()after initializing the SDK on every launch. The first ping creates an anonymous contact. The SDK stores its key and sends it with every following request. - Identify the contact. At login or registration, call
updateContact(_:fcmToken:_:)with aContactthat holds your own user ID and the data you have. Pass the FCM token in the same call. - Log out. Call
logout(fcmToken:_:)when the user logs out of your app.
// 1. App launch
UserSDK.default?.ping()
// 2. Login
let contact = Contact(userId: "12345", email: "jane@example.com", firstName: "Jane",
lastName: "Doe", attributes: ["plan": "pro"])
UserSDK.default?.updateContact(contact, fcmToken: currentFcmToken)
// 3. Logout
UserSDK.default?.logout(fcmToken: currentFcmToken)
Send your own user ID (userId of the Contact) at login. It is the most reliable way to keep one contact for the same person across reinstalls and devices.
After login, the contact in the panel shows the email and user ID you sent. For a new user ID, the activity from before login stays on the same contact. If a contact with this user ID already exists, the SDK switches to that contact.
Handling
Results of contact methods
Every contact method accepts an optional completion handler (Bool, Error?) -> Void. It runs on the main thread when the request finishes: true when the backend accepted it, false with a UserSDKError when the backend rejected it, all retries failed or the data could not be sent. Requests are queued and survive the app going offline, see Delivery and Retries.
A rejected request (for example HTTP 400 or 401) calls completion(false, error). Before 1.1.0 the completion reported success regardless of the response. If your code treats true as proof of delivery, review it.
Contact data is sent once
What you pass to updateContact is added to the contact. The SDK keeps no copy, so later pings do not repeat it.
Before 1.3.0, setUserData and setCustomUserData stored the data on the device and every later ping sent it again. updateContact sends the data once. The first updateContact call also clears what the old methods stored. The phone number is sent as phone_number instead of phone. The backend accepts both.
Switching contacts
A userId different from the current contact's switches to the contact with that user ID, or creates one. That is why updateContact belongs in your login flow, with the push token, so the identity and the token reach the backend in one request.
Removing a value
A field you leave nil keeps its value on the contact. The SDK cannot clear a value. Clear it in the panel or through the REST API.
Attributes
attributes takes your own contact attributes and standard ones such as gender or score. Your own attributes must exist in the panel, other names are dropped there without an error. The typed fields of Contact win over the same keys in attributes.
Values must be JSON values, see Attribute value types. A value JSON cannot hold, for example Date, URL or a Double that is NaN, or an empty attribute name ends with invalidData and nothing is sent. Before 1.3.0 such values crashed the app.
A value whose type does not match the attribute's type makes the backend reject the request, and the completion reports an error.
Methods
ping(fcmToken:_:)
Creates or updates the contact with the current device data. The first successful ping creates an anonymous contact and sets userKey.
| Name | Type | Required | Description |
|---|---|---|---|
fcmToken | String? | No | An FCM token to store and send with this ping. Default nil keeps the stored token. |
completion | ((Bool, Error?) -> Void)? | No | Called with the result on the main thread. |
- Swift
- Objective-C
UserSDK.default?.ping { success, error in
if !success { print("Ping failed: \(String(describing: error))") }
}
[[UserSDK default] pingWithFcmToken:nil completion:^(BOOL success, NSError *error) {
if (!success) { NSLog(@"Ping failed: %@", error); }
}];
A ping answered without a contact key ends with notCollectingData: the app does not collect data, check the app settings in the panel.
updateContact(_:fcmToken:_:)
Adds data to the current contact in one request. Added in 1.3.0.
| Name | Type | Required | Description |
|---|---|---|---|
contact | Contact | Yes | The data to add, see the table below. |
fcmToken | String? | No | An FCM token sent in the same request and stored for later pings. Default nil. |
completion | ((Bool, Error?) -> Void)? | No | Called with the result on the main thread. |
Contact fields, all optional:
| Field | Type | Sent as |
|---|---|---|
userId | String? | user_id, your own user ID |
email | String? | email |
firstName | String? | first_name |
lastName | String? | last_name |
phoneNumber | String? | phone_number |
attributes | [String: Any] | Each key as a contact attribute. Default [:]. |
- Swift
- Objective-C
let contact = Contact(userId: "42", email: "jane@example.com", firstName: "Jane",
attributes: ["plan": "pro"])
UserSDK.default?.updateContact(contact, fcmToken: fcmToken) { success, error in
// contact updated
}
UserSDKContact *contact = [[UserSDKContact alloc] initWithUserId:@"42"
email:@"jane@example.com"
firstName:@"Jane"
lastName:nil
phoneNumber:nil
attributes:@{@"plan": @"pro"}];
[[UserSDK default] updateContact:contact fcmToken:fcmToken completion:^(BOOL success, NSError *error) {
// contact updated
}];
In Objective-C the class is UserSDKContact.
logout(fcmToken:_:)
Ends the session of the current contact and starts a new anonymous one, in this order:
- records a
logoutevent on the contact who is leaving, - deletes the contact key, the contact data and a waiting in-app message on the device, and drops pings still waiting in the queue,
- sends a ping with the same push token, which creates a new anonymous contact,
- after that ping, removes the given FCM token from the contact who is leaving.
| Name | Type | Required | Description |
|---|---|---|---|
fcmToken | String? | Yes | The token to remove from the contact who is leaving. The anonymous contact gets this token. nil skips the removal, and the anonymous contact gets the token the SDK already has. |
completion | ((Bool, Error?) -> Void)? | No | Called on the main thread with true when the new ping and the token removal succeeded. |
- Swift
- Objective-C
UserSDK.default?.logout(fcmToken: currentFcmToken) { success, error in
// a new anonymous contact is ready
}
[[UserSDK default] logoutWithFcmToken:currentFcmToken completion:^(BOOL success, NSError *error) {
// a new anonymous contact is ready
}];
Local data is deleted even when the network requests fail. Events collected offline before logout are still sent, for the contact who logged out. Contact updates of that contact still waiting in the queue end with loggedOut.
The anonymous contact created by logout gets the same push token, so the device keeps receiving push notifications. Before 1.3.0 it had no token. A push notification sent to the logged out contact does not reach the device. Turning push notifications off after logout is up to your app. If you call setFcmToken(_:) after logout only to keep push notifications, remove that call.
logout records a logout event for the contact who is leaving. If your app already sends its own logout event, remove it to avoid counting logouts twice.
userKey
The key of the current contact in Positive User, read only. It is nil until the first successful ping and while logout is in progress, and changes after logout and when updateContact switches to another contact. It is not your own user ID.
let contactKey = UserSDK.default?.userKey
Renamed from userId in 1.3.0. userId still works and is deprecated until 2.0.
Deprecated methods
setUserData(_:fcmToken:_:), setCustomUserData(_:fcmToken:_:) and UserSDK.UserDataKey are deprecated since 1.3.0 and stay until 2.0. They store the data on the device and send it with every later ping. Replace them with updateContact(_:fcmToken:_:):
| 1.2.x | Contact |
|---|---|
.userId | userId |
.email | email |
.firstName | firstName |
.lastName | lastName |
.phone | phoneNumber |
setCustomUserData dictionary | attributes |
Common errors
| Symptom | Cause | Fix |
|---|---|---|
| Events go to a different contact than the one you look at in the panel | Your app also identifies the user through the REST API or another integration. | Identify the contact only through the SDK on the device. See Contacts overview. |
| A new contact appears at every login | A different userId is sent for the same person. | Send the same stable user ID every time. |
| Custom attributes do not appear | The attribute does not exist in the panel. | Create the attribute in the panel first. |
A value set to nil is still on the contact | nil keeps the value, it does not clear it. | Clear the attribute in the panel or through the REST API. |
| Push notifications still go to the logged out contact | logout was called with nil, and the SDK got no token during this launch. | Pass the current token to logout(fcmToken:_:). |
Contact data sent with updateContact is overwritten by older values | The app still calls setUserData or setCustomUserData, whose data every ping sends again. | Replace them with updateContact. |
updateContact fails with invalidData | An attribute value JSON cannot hold, for example Date, or an empty attribute name. | Send dates as ISO 8601 strings. |
| Contact data from the staging workspace appears after switching to production | The contact key is kept in the Keychain. | Delete the app before switching baseURL. |