Mobile SDK keys and identifying users (user_hash)
Last updated: 9 October 2026 · 9 min read
Without a login, the Messenger opens for an anonymous visitor. When a user signs in to your app, tell Clomni who they are: agents then see their name, e-mail and attributes, the user sees the same conversations on every device, and what they wrote as a visitor moves to them.
Steps
-
1
The keys and where they live
- App ID (
app_…): in the app. - Android API key (
android_…): in the Android app. - iOS API key (
ios_…): in the iOS app. - Identity Secret (a long random string): only on your server. Not in the code, not in
BuildConfig, not inInfo.plist, not in a.envshipped with the app, not in Unity assets.
An API key belongs to one platform: the iOS key does not work on Android. React Native, Flutter and Unity apps pass the key of the platform they run on. The keys are in Settings › Workspace settings › Inboxes › your channel › settings column › Installation, the Identity Secret in the Security section.
- App ID (
-
2
How it works
- The user signs in to your app.
- Your server computes a
user_hashfrom the user's ID with the Identity Secret and returns it to the app, for example in the login response. - The app calls
loginUserwith the user and the hash.
The hash proves your server vouches for this ID. Without it, anyone who knows a user's ID could open that user's conversations from their own phone.
-
3
Formula
user_hash = hex(HMAC-SHA256(identity_secret, user_id))
- The result is 64 lower-case hex characters.
user_idis a string, exactly the one the app passes tologinUser.12345and"12345"are the same,"012345"is not.- When the app signs a user in without
userId, the hash is made from the e-mail, trimmed and in lower case. - Use an ID that never changes. If the e-mail can change, do not use it as the ID.
To test your code: with the secret
test_secretanduser_id12345, the hash must beb01354f5d4c60c56ca76a26c064b9629b0d2a73e169b63b6514b974a9714cf24. -
4
Server code
Each sample reads the secret from the
CLOMNI_IDENTITY_SECRETenvironment variable.// Node.js const crypto = require('crypto'); function clomniUserHash(userId) { return crypto .createHmac('sha256', process.env.CLOMNI_IDENTITY_SECRET) .update(String(userId)) .digest('hex'); } // Return the hash with your own login response (Express). app.post('/api/login', async (req, res) => { const user = await logIn(req.body.email, req.body.password); // your own login res.json({ user, clomniUserHash: clomniUserHash(user.id) }); });<?php // PHP function clomni_user_hash(string $userId): string { return hash_hmac('sha256', $userId, getenv('CLOMNI_IDENTITY_SECRET')); }# Python import hashlib import hmac import os def clomni_user_hash(user_id) -> str: secret = os.environ["CLOMNI_IDENTITY_SECRET"].encode() return hmac.new(secret, str(user_id).encode(), hashlib.sha256).hexdigest()# Ruby require 'openssl' def clomni_user_hash(user_id) OpenSSL::HMAC.hexdigest('SHA256', ENV.fetch('CLOMNI_IDENTITY_SECRET'), user_id.to_s) end// Java import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; public final class ClomniUserHash { private ClomniUserHash() {} public static String of(String userId) throws GeneralSecurityException { String secret = System.getenv("CLOMNI_IDENTITY_SECRET"); Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256")); byte[] digest = mac.doFinal(userId.getBytes(StandardCharsets.UTF_8)); StringBuilder hex = new StringBuilder(digest.length * 2); for (byte b : digest) { hex.append(String.format("%02x", b)); } return hex.toString(); } }// C# (.NET 5 or newer) using System; using System.Security.Cryptography; using System.Text; public static class ClomniUserHash { public static string Of(string userId) { var secret = Environment.GetEnvironmentVariable("CLOMNI_IDENTITY_SECRET")!; using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret)); var digest = hmac.ComputeHash(Encoding.UTF8.GetBytes(userId)); return Convert.ToHexString(digest).ToLowerInvariant(); } }// Go package clomni import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "os" ) // UserHash is the user_hash Clomni expects for userID. func UserHash(userID string) string { mac := hmac.New(sha256.New, []byte(os.Getenv("CLOMNI_IDENTITY_SECRET"))) mac.Write([]byte(userID)) return hex.EncodeToString(mac.Sum(nil)) }In C#,
Convert.ToHexStringreturns upper case, soToLowerInvariantis needed. -
5
Login, changes and logout in the app
Call
loginUserafter your own login succeeds, with the hash from your server. Every field exceptuserIdis optional, and there isphonetoo.// Android Clomni.loginUser(ClomniUser(userId = "12345", email = "[email protected]", name = "Aysel"), userHash = hashFromYourServer) Clomni.updateUser(language = "az", customAttributes = mapOf("plan" to "premium")) Clomni.logout()
// iOS Clomni.loginUser(ClomniUser(userId: "12345", email: "[email protected]", name: "Aysel"), userHash: hashFromYourServer) Clomni.updateUser(language: "az", customAttributes: ["plan": "premium"]) Clomni.logout()
// React Native Clomni.loginUser({ userId: '12345', email: '[email protected]', name: 'Aysel' }, hashFromYourServer); Clomni.updateUser({ language: 'az', customAttributes: { plan: 'premium' } }); Clomni.logout();// Flutter await Clomni.loginUser( const ClomniUser(userId: '12345', email: '[email protected]', name: 'Aysel'), userHash: hashFromYourServer, ); await Clomni.updateUser(language: 'az', customAttributes: {'plan': 'premium'}); await Clomni.logout();
// Unity Clomni.LoginUser(new ClomniUser { UserId = "12345", Email = "[email protected]", Name = "Aysel" }, hashFromYourServer); Clomni.UpdateUser(language: "az", customAttributes: new Dictionary<string, object> { ["plan"] = "premium" }); Clomni.Logout();updateUserchanges only the fields given: name, language (az,en,ru), custom attributes. The attributes are merged into the ones the agent sees in the conversation's side panel.- Call
logouttogether with your app's own logout. It closes the Clomni session and deletes the Messenger's data on the device. Without it, the next person who signs in on this phone sees the previous user's conversations.
Call
loginUseron every app start while the user is signed in, as if they had just signed in. For the same user the SDK reuses its stored session. -
6
Modes
The mode is in settings column › Security › “Mode”.
- “Off”: the hash is not checked, any hash is accepted. Anyone who gives the app another user's
user_idcan open their conversations. For testing only: never use it in production. - “Recommended” (the default): a hash that is sent must be correct, a wrong hash refuses the login. A user without a hash is accepted but not verified. Once a user has signed in with a verified hash, a hash is required for them every time.
- “Enforced”: no login without a correct hash, the server answers
403 identity_verification_failed. It can be chosen only after the app's first verified login. Older app versions that send no hash cannot connect.
A good order: release the app with
user_hashin “Recommended” mode, watch “Overview” until no hash warning is left, then switch to “Enforced”. - “Off”: the hash is not checked, any hash is accepted. Anyone who gives the app another user's
-
7
Test a hash
Side menu › Security › “Test a hash”: enter the
user_idand theuser_hashyour server made, then click “Check”. The dashboard says whether the hash was made with the current secret, with the old secret, or matches neither. The right answer is “Correct. The hash was made with the current secret.” If the hash is not 64 hex characters, the dashboard says so. -
8
Changing the secret and the keys
- Security › “Make a new secret”: the new secret is shown once, the old one keeps working for 7 more days (the dashboard shows the date). Move your server to the new secret within that time.
- Installation › “New key”: a new API key, the old one keeps working for 7 more days. Make a new key only when the old one leaked, or together with a release most users will install.
- Installation › “Revoke”: the key stops at once.
-
9
Full guide
- PDF: Clomni Mobile SDK guide, in English, Azerbaijani and Russian.
- Online: Identifying users.
- In the dashboard: “Guide” in the channel's settings column.