Setting a User ID and Hashed User Details
Send your internal user ID to Singular to enable cross-device tracking and user-level data reporting.
Note: If you use Singular's Cross-Device solution , you must collect the User ID across all platforms.
User ID Requirements
Privacy and Best Practices
Follow these guidelines when implementing user ID tracking to ensure privacy compliance and proper cross-device measurement.
- No PII: The User ID should not expose Personally Identifiable Information (PII) such as email addresses, usernames, or phone numbers. Use a hashed value unique to your first-party data. To send an email address or phone number, use the dedicated Hashed User Details API instead.
- Consistency Across Platforms: The User ID value must be the same internal identifier you capture across all platforms (Web/Mobile/PC/Console/Offline) for accurate cross-device measurement.
- First-Party Data: Singular includes the User ID in user-level exports, ETL, and Internal BI postbacks (if configured). The User ID is first-party data and is not shared with third parties.
-
Persistence:
The User ID persists until explicitly
unset using
unsetCustomUserId()or until the app is uninstalled. Closing or restarting the app does not clear the User ID.
Implementation Overview
When to Set the User ID
Use
Singular.setCustomUserId()
to set the user identifier
and
Singular.unsetCustomUserId()
to clear it during logout.
Best Practice:
If multiple users share a single
device, implement a logout flow that calls
setCustomUserId()
on login and
unsetCustomUserId()
on logout.
If you already know the user ID when the app opens, configure it using
withCustomUserId()
before initializing the Singular SDK.
This ensures Singular receives the User ID from the first session. However,
the User ID is typically unavailable until the user registers or logs
in, in which case call
setCustomUserId()
after the registration
or authentication flow completes.
SDK Methods
Set Custom User ID
Send your internal user ID to Singular for cross-device tracking and user-level reporting.
// TurboModule direct API (React Native 0.76+ New Architecture)
import NativeSingular from 'singular-react-native/js/NativeSingular';
// Set the user ID after login or registration
NativeSingular.setCustomUserId('user_123456');
import { Singular } from 'singular-react-native';
// Set the user ID after login or registration
Singular.setCustomUserId('user_123456');
Method Signature:
static setCustomUserId(customUserId: string): void
Example: Set User ID After Login
Call
setCustomUserId()
immediately after the user successfully
completes authentication to ensure all subsequent events are associated
with their user ID.
// TurboModule direct API (React Native 0.76+ New Architecture)
import NativeSingular from 'singular-react-native/js/NativeSingular';
async function handleUserLogin(email, password) {
try {
// Your authentication logic
const response = await authenticateUser(email, password);
if (response.success) {
// Set the user ID in Singular after successful login
NativeSingular.setCustomUserId(response.userId);
console.log('User ID set:', response.userId);
// Navigate to home screen
navigateToHome();
}
} catch (error) {
console.error('Login failed:', error);
}
}
import { Singular } from 'singular-react-native';
async function handleUserLogin(email, password) {
try {
// Your authentication logic
const response = await authenticateUser(email, password);
if (response.success) {
// Set the user ID in Singular after successful login
Singular.setCustomUserId(response.userId);
console.log('User ID set:', response.userId);
// Navigate to home screen
navigateToHome();
}
} catch (error) {
console.error('Login failed:', error);
}
}
Unset Custom User ID
Clear the user ID when a user logs out to ensure accurate session tracking for multi-user devices.
// TurboModule direct API (React Native 0.76+ New Architecture)
import NativeSingular from 'singular-react-native/js/NativeSingular';
// Unset the user ID on logout
NativeSingular.unsetCustomUserId();
import { Singular } from 'singular-react-native';
// Unset the user ID on logout
Singular.unsetCustomUserId();
Method Signature:
static unsetCustomUserId(): void
Example: Unset User ID on Logout
Call
unsetCustomUserId()
during the logout flow to clear
the user ID and prevent incorrect attribution of subsequent events.
// TurboModule direct API (React Native 0.76+ New Architecture)
import NativeSingular from 'singular-react-native/js/NativeSingular';
async function handleUserLogout() {
try {
// Clear app data and user session
await clearUserSession();
// Unset the user ID in Singular
NativeSingular.unsetCustomUserId();
console.log('User ID cleared');
// Navigate to login screen
navigateToLogin();
} catch (error) {
console.error('Logout failed:', error);
}
}
import { Singular } from 'singular-react-native';
async function handleUserLogout() {
try {
// Clear app data and user session
await clearUserSession();
// Unset the user ID in Singular
Singular.unsetCustomUserId();
console.log('User ID cleared');
// Navigate to login screen
navigateToLogin();
} catch (error) {
console.error('Logout failed:', error);
}
}
Set User ID During Initialization
If the user ID is available when the app launches (e.g., user is already
logged in), configure it during SDK initialization using
withCustomUserId()
. This ensures the first session includes
the user ID.
// TurboModule direct API (React Native 0.76+ New Architecture)
import React, { useEffect } from 'react';
import NativeSingular from 'singular-react-native/js/NativeSingular';
import type { SingularConfig } from 'singular-react-native/js/NativeSingular';
import AsyncStorage from '@react-native-async-storage/async-storage';
export default function App() {
useEffect(() => {
initializeSingular();
}, []);
async function initializeSingular() {
// Check if user is already logged in
const userId = await AsyncStorage.getItem('user_id');
// Create configuration object
const config: SingularConfig = {
apikey: 'YOUR_SDK_KEY',
secret: 'YOUR_SDK_SECRET',
...(userId ? { customUserId: userId } : {}),
};
// Initialize SDK
NativeSingular.init(config);
}
return (
// Your app components
null
);
}
import React, { useEffect } from 'react';
import { Singular, SingularConfig } from 'singular-react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
export default function App() {
useEffect(() => {
initializeSingular();
}, []);
async function initializeSingular() {
// Check if user is already logged in
const userId = await AsyncStorage.getItem('user_id');
// Create configuration
const config = new SingularConfig(
'YOUR_SDK_KEY',
'YOUR_SDK_SECRET'
);
// If user ID exists, set it during initialization
if (userId) {
config.withCustomUserId(userId);
}
// Initialize SDK
Singular.init(config);
}
return (
// Your app components
);
}
Configuration Method Signature:
withCustomUserId(customUserId: string): SingularConfig
Recommendation:
Use
withCustomUserId()
during initialization for apps with persistent login sessions. For
apps where users must log in each time, call
setCustomUserId()
after authentication.
Hashed User Details (Email and Phone)
The User ID above is your own opaque identifier. If you also want to send the user's email address or phone number to Singular, use the separate SingularUserDetails API. The SDK normalizes and SHA-256 hashes these values on the device, so the raw email address and phone number are never transmitted.
Availability: React Native SDK version 4.3.0 and above.
Choosing a Hashing Mode
There are two ways to supply user details. Pick one per user and stay consistent.
- SDK-hashed (recommended): Pass the cleartext email or phone number. The SDK normalizes the value, hashes it, and generates every variant Singular can match on.
- Pre-hashed: Pass values you have already normalized and SHA-256 hashed yourself. The SDK stores them exactly as given and does no further processing. Use this when your app is not permitted to hold cleartext user details at the point of the call.
Important: If you set both a cleartext value and its matching pre-hashed variant, the pre-hashed value wins.
SingularUserDetails Setters
| Setter | Details |
|---|---|
setEmail |
Mode: SDK-hashed Cleartext email address. The SDK trims whitespace and lowercases the value before hashing. For Example: |
setPhoneNumber |
Mode: SDK-hashed Cleartext phone number. The SDK generates two variants: an E.164 form that keeps a leading Example: |
setEmailSTD |
Mode: Pre-hashed SHA-256 hash of the email address after trimming and lowercasing. |
setEmailNoDots |
Mode: Pre-hashed SHA-256 hash of the email address after trimming, lowercasing, and removing any |
setPhoneE164 |
Mode: Pre-hashed SHA-256 hash of the phone number in E.164 form, keeping the leading |
setPhoneDigits |
Mode: Pre-hashed SHA-256 hash of the phone number with every non-digit removed, including the leading |
Every setter returns the same object, so calls can be chained. On the New Architecture you may also pass a plain object using the same six keys — email, phoneNumber, emailSTD, emailNoDots, phoneE164 and phoneDigits.
Set User Details at Initialization
Set SingularConfig.userDetails before calling init() so the user details are attached to the first session the SDK sends.
// TurboModule direct API (React Native 0.76+ New Architecture)
import NativeSingular from 'singular-react-native/js/NativeSingular';
import type { SingularConfig } from 'singular-react-native/js/NativeSingular';
const config: SingularConfig = {
apikey: 'YOUR_SDK_KEY',
secret: 'YOUR_SDK_SECRET',
userDetails: {
email: 'user@example.com',
phoneNumber: '+15551234567'
}
};
NativeSingular.init(config);import { Singular, SingularConfig, SingularUserDetails } from 'singular-react-native';
const userDetails = new SingularUserDetails()
.setEmail('user@example.com')
.setPhoneNumber('+15551234567');
const config = new SingularConfig('YOUR_SDK_KEY', 'YOUR_SDK_SECRET')
.withUserDetails(userDetails);
Singular.init(config);Configuration Method Signature:
withUserDetails(userDetails: SingularUserDetails): SingularConfig
Set User Details After Initialization
If the email address or phone number is only known after login or registration, call setUserDetails() at that point instead. The values are attached to every session and event the SDK sends from then on.
// TurboModule direct API (React Native 0.76+ New Architecture)
import NativeSingular from 'singular-react-native/js/NativeSingular';
// Cleartext values, hashed on the device by the SDK
NativeSingular.setUserDetails({
email: 'user@example.com',
phoneNumber: '+15551234567'
});
// Or supply your own SHA-256 hashes
NativeSingular.setUserDetails({
emailSTD: 'b4c9a289323b21a01c3e940f150eb9b8c542587f1abfd8f0e1cc1ffc5e475514',
phoneE164: '8a59780bb8cd2ba022bfa5ba2ea3b6e07af17a7d8b30c1f9b3390e36f69019e4'
});import { Singular, SingularUserDetails } from 'singular-react-native';
// Cleartext values, hashed on the device by the SDK
const userDetails = new SingularUserDetails()
.setEmail('user@example.com')
.setPhoneNumber('+15551234567');
Singular.setUserDetails(userDetails);
// Or supply your own SHA-256 hashes
const hashed = new SingularUserDetails()
.setEmailSTD('b4c9a289323b21a01c3e940f150eb9b8c542587f1abfd8f0e1cc1ffc5e475514')
.setPhoneE164('8a59780bb8cd2ba022bfa5ba2ea3b6e07af17a7d8b30c1f9b3390e36f69019e4');
Singular.setUserDetails(hashed);Method Signature:
static setUserDetails(userDetails: SingularUserDetails): void
Note: setUserDetails() has no effect before init() — the native SDK logs an error and returns. Use SingularConfig.userDetails to cover the first session.
Clear User Details
Stored user details persist on the device across app launches — in the Keychain on iOS and in encrypted storage on Android. Call clearUserDetails() on logout, or whenever the user withdraws consent, to remove them.
// TurboModule direct API (React Native 0.76+ New Architecture)
import NativeSingular from 'singular-react-native/js/NativeSingular';
// Remove stored user details on logout
NativeSingular.clearUserDetails();import { Singular } from 'singular-react-native';
// Remove stored user details on logout
Singular.clearUserDetails();Method Signature:
static clearUserDetails(): void
Note: Because the values are persisted, setting SingularConfig.userDetails on a later launch does not erase what was stored earlier. clearUserDetails() is the only way to remove it.
Validation and Privacy Behavior
-
JavaScript layer:
SingularUserDetailsdrops any value that is not a non-empty string, including the literal strings"null"and"undefined". It does not check the format of the value. -
Email validation: Applied by the native SDK. A cleartext email must contain exactly one
@and a dot after it. Invalid values are rejected and logged, not sent. - Phone validation: Applied by the native SDK. A cleartext phone number must contain at least 6 digits. Shorter values are rejected and logged.
-
Wrong-mode protection: A value that already looks hashed is rejected by
setEmailandsetPhoneNumber. Likewise, the pre-hashed setters reject anything that is not a 64-character SHA-256 hex string. - Limit Data Sharing: While Limit Data Sharing is enabled, the user details payload is withheld from all requests. See Data Privacy.
- Consent: Collect and send email addresses and phone numbers only where you have a lawful basis to do so. Hashing does not remove your obligations under GDPR, CCPA, or equivalent regulations.