Ключи мобильного SDK и идентификация пользователей (user_hash)

Обновлено: 9 октября 2026 · 9 мин чтения

Без входа Messenger открывается для анонимного посетителя. Когда пользователь входит в Ваше приложение, сообщите Clomni, кто он: тогда операторы видят его имя, e-mail и атрибуты, пользователь видит одни и те же диалоги на всех устройствах, а написанное им как посетителем переходит к нему.

Шаги

  1. 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».

  2. 2

    Как это работает

    1. Пользователь входит в Ваше приложение.
    2. Ваш сервер вычисляет user_hash из ID пользователя с Identity Secret и возвращает его приложению, например в ответе на вход.
    3. Приложение вызывает loginUser с пользователем и хешем.

    Хеш доказывает, что Ваш сервер ручается за этот ID. Без него любой, кто знает ID пользователя, мог бы открыть его диалоги со своего телефона.

  3. 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_id 12345 хеш должен быть b01354f5d4c60c56ca76a26c064b9629b0d2a73e169b63b6514b974a9714cf24.

  4. 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. 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. 6

    Режимы

    Режим находится в колонке настроек › «Security» › «Mode».

    • «Off»: хеш не проверяется, принимается любой хеш. Любой, кто передаст приложению чужой user_id, может открыть чужие диалоги. Только для тестов: никогда не используйте этот режим в рабочем приложении.
    • «Recommended» (по умолчанию): переданный хеш должен быть верным, неверный хеш отклоняет вход. Пользователь без хеша принимается, но не подтверждается. После первого входа пользователя с подтверждённым хешем хеш требуется от него каждый раз.
    • «Enforced»: без верного хеша входа нет, сервер отвечает 403 identity_verification_failed. Режим можно выбрать только после первого подтверждённого входа из приложения. Старые версии приложения, не отправляющие хеш, не подключаются.

    Хороший порядок: выпустите приложение с user_hash в режиме «Recommended», следите за «Overview», пока не исчезнут предупреждения о хеше, затем включите «Enforced».

  7. 7

    Проверка хеша

    Боковое меню › «Security» › «Test a hash»: введите user_id и user_hash, сделанный Вашим сервером, нажмите «Check». Панель скажет, сделан ли хеш текущим секретом, старым секретом или не совпадает ни с одним. Правильный ответ: «Correct. The hash was made with the current secret.» Если хеш не из 64 hex-символов, панель скажет и это.

  8. 8

    Смена секрета и ключей

    • «Security» › «Make a new secret»: новый секрет показывается один раз, старый работает ещё 7 дней (панель показывает дату). За это время переведите сервер на новый секрет.
    • «Installation» › «New key»: новый ключ API, старый работает ещё 7 дней. Создавайте новый ключ, только если старый утёк, или вместе с релизом, который установит большинство пользователей.
    • «Installation» › «Revoke»: ключ останавливается сразу.
  9. 9

    Полное руководство

Документ