Ключи мобильного SDK и идентификация пользователей (user_hash)
Обновлено: 9 октября 2026 · 9 мин чтения
Без входа Messenger открывается для анонимного посетителя. Когда пользователь входит в Ваше приложение, сообщите Clomni, кто он: тогда операторы видят его имя, e-mail и атрибуты, пользователь видит одни и те же диалоги на всех устройствах, а написанное им как посетителем переходит к нему.
Шаги
-
1
Ключи и где они хранятся
- App ID (
app_…): в приложении. - Ключ API для Android (
android_…): в приложении Android. - Ключ API для iOS (
ios_…): в приложении iOS. - Identity Secret (длинная случайная строка): только на Вашем сервере. Не в коде, не в
BuildConfig, не вInfo.plist, не в.env, который уходит с приложением, не в ассетах Unity.
Ключ API привязан к платформе: ключ iOS не работает на Android. Приложения на React Native, Flutter и Unity передают ключ той платформы, на которой работают. Ключи находятся в «Settings › Workspace settings › Inboxes» › Ваш канал › колонка настроек › «Installation», Identity Secret в разделе «Security».
- App ID (
-
2
Как это работает
- Пользователь входит в Ваше приложение.
- Ваш сервер вычисляет
user_hashиз ID пользователя с Identity Secret и возвращает его приложению, например в ответе на вход. - Приложение вызывает
loginUserс пользователем и хешем.
Хеш доказывает, что Ваш сервер ручается за этот ID. Без него любой, кто знает ID пользователя, мог бы открыть его диалоги со своего телефона.
-
3
Формула
user_hash = hex(HMAC-SHA256(identity_secret, user_id))
- Результат: 64 hex-символа в нижнем регистре.
user_idявляется строкой, ровно той, что приложение передаёт вloginUser.12345и"12345"одинаковы,"012345"уже нет.- Если приложение выполняет вход без
userId, хеш берётся от e-mail без пробелов по краям, в нижнем регистре. - Используйте ID, который не меняется. Если e-mail может меняться, не используйте его как ID.
Для проверки кода: с секретом
test_secretиuser_id12345хеш должен бытьb01354f5d4c60c56ca76a26c064b9629b0d2a73e169b63b6514b974a9714cf24. -
4
Код сервера
Каждый пример читает секрет из переменной окружения
CLOMNI_IDENTITY_SECRET.// 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)) }В C#
Convert.ToHexStringвозвращает верхний регистр, поэтому нуженToLowerInvariant. -
5
Вход, изменения и выход в приложении
Вызывайте
loginUserпосле успешного входа в Ваше приложение, с хешем от сервера. Все поля, кромеuserId, необязательны, есть иphone.// 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();updateUserменяет только переданные поля: имя, язык (az,en,ru), пользовательские атрибуты. Атрибуты объединяются с теми, что оператор видит на боковой панели диалога.- Вызывайте
logoutвместе с выходом из Вашего приложения. Он закрывает сессию Clomni и удаляет данные Messenger на устройстве. Без него следующий человек на этом телефоне увидит диалоги предыдущего пользователя.
Пока пользователь вошёл в приложение, вызывайте
loginUserпри каждом запуске, как будто он только что вошёл. Для того же пользователя SDK использует сохранённую сессию. -
6
Режимы
Режим находится в колонке настроек › «Security» › «Mode».
- «Off»: хеш не проверяется, принимается любой хеш. Любой, кто передаст приложению чужой
user_id, может открыть чужие диалоги. Только для тестов: никогда не используйте этот режим в рабочем приложении. - «Recommended» (по умолчанию): переданный хеш должен быть верным, неверный хеш отклоняет вход. Пользователь без хеша принимается, но не подтверждается. После первого входа пользователя с подтверждённым хешем хеш требуется от него каждый раз.
- «Enforced»: без верного хеша входа нет, сервер отвечает
403 identity_verification_failed. Режим можно выбрать только после первого подтверждённого входа из приложения. Старые версии приложения, не отправляющие хеш, не подключаются.
Хороший порядок: выпустите приложение с
user_hashв режиме «Recommended», следите за «Overview», пока не исчезнут предупреждения о хеше, затем включите «Enforced». - «Off»: хеш не проверяется, принимается любой хеш. Любой, кто передаст приложению чужой
-
7
Проверка хеша
Боковое меню › «Security» › «Test a hash»: введите
user_idиuser_hash, сделанный Вашим сервером, нажмите «Check». Панель скажет, сделан ли хеш текущим секретом, старым секретом или не совпадает ни с одним. Правильный ответ: «Correct. The hash was made with the current secret.» Если хеш не из 64 hex-символов, панель скажет и это. -
8
Смена секрета и ключей
- «Security» › «Make a new secret»: новый секрет показывается один раз, старый работает ещё 7 дней (панель показывает дату). За это время переведите сервер на новый секрет.
- «Installation» › «New key»: новый ключ API, старый работает ещё 7 дней. Создавайте новый ключ, только если старый утёк, или вместе с релизом, который установит большинство пользователей.
- «Installation» › «Revoke»: ключ останавливается сразу.
-
9
Полное руководство
- PDF: руководство Clomni Mobile SDK, на русском языке.
- Онлайн: Identifying users (на английском).
- В панели: раздел «Guide» в колонке настроек канала.