Подписанная личность

Способ дать AI-оператору доступ к данным конкретного пользователя, не передавая нам его токен. Ваш бэкенд подписывает идентификатор пользователя, мы проверяем подпись и вызываем ваше API с подтверждённым идентификатором — токена вашего пользователя у нас не существует ни в каком виде.

Когда нужен этот режим

  • Вы не хотите, чтобы токены ваших пользователей покидали ваш периметр.
  • Авторизация на сайте построена на httpOnly-куках — тогда setToken() неприменим в принципе: JavaScript не может прочитать такую куку.

Сравнение с токеном пользователя

Токен пользователяПодписанная личность
Что хранится у настокен пользователя (в зашифрованном виде, до 24 часов)ничего, кроме подписанного вами утверждения
Работает при httpOnly-кукахнетда
Кто проверяет права на данныеваш существующий auth-слойваш код в самой ручке
Что нужно сделать на вашей стороневыпустить токен для чатаподписать идентификатор и проверять нашу подпись

В этом режиме мы удостоверяем только личность. Проверку «имеет ли этот пользователь право на запрошенные данные» ваш бэкенд должен делать сам — в режиме токена её бесплатно выполнял ваш auth-слой.

Шаг 1. Настройте интеграцию и выпустите секрет

В API-интеграции выберите authMode: "signed_identity", а у ручек с персональными данными включите useUserToken: true. Несмотря на историческое имя поля, в этом режиме оно включает передачу подтверждённой личности, а не токена пользователя. JSON создания и второй вариант в редакторе кода приведены в разделе «API-интеграции AI-агента».

В админ-панели: AI-агенты → нужный агент → API → Способ авторизации → Подписанная личность → Выпустить секрет.

Секрет показывается один раз — сохраните его в конфиге вашего бэкенда. Он используется для двух вещей: вы им подписываете личность, мы им подписываем запросы к вам. После выпуска публичный контракт показывает hasSigningSecret: true, но никогда не возвращает само значение.

Публичного v1 endpoint для выпуска или ротации секрета нет: это кабинетная операция с правами администратора. При выпуске нового секрета старый перестаёт действовать сразу — обновляйте его на своей стороне одновременно.

Шаг 2. Подпишите личность на своём бэкенде

Обычный JWT, алгоритм HS256:

ClaimЗначение
subидентификатор пользователя в вашей системе — он придёт в ваше API
expсрок жизни. Держите коротким (например, час): после истечения AI автоматически теряет доступ
JS
// сервер вашего сайта, при рендере страницы для залогиненного пользователя
const identity = jwt.sign({ sub: user.id }, APX_INTEGRATION_SECRET, {
  algorithm: 'HS256',
  expiresIn: '1h',
});

Секрет остаётся на сервере — в браузер уходит только готовая подпись. Поэтому режим и работает с httpOnly-куками: страница рендерится уже с подписью, читать куку из JS не требуется.

Шаг 3. Передайте подпись виджету

JS
window.apxChat.init('ВАШ_ТОКЕН');
window.apxChat.setIdentity(identityJwt);

// при выходе из аккаунта
window.apxChat.clearIdentity();

Жизненный цикл — такой же, как у токена пользователя: значение удаляется при закрытии последней вкладки, по clearIdentity() и автоматически не позже чем через сутки, а при переподключении виджет отправляет его сам.

Шаг 4. Что придёт в ваше API

У ручек, где включён флаг «передавать пользователя», появятся заголовки:

ЗаголовокОписание
X-Apx-User-Idsub из вашего JWT — подпись уже проверена нами
X-Apx-Timestampвремя запроса, Unix-секунды
X-Apx-Signaturev1=<hex> — HMAC-SHA256 запроса вашим секретом

Если подпись личности невалидна или истекла, запрос придёт без этих заголовков — решение отказать остаётся за вами.

Шаг 5. Проверьте нашу подпись

Подписывается канонизированная строка, склеенная точками:

PLAINTEXT
<timestamp>.<METHOD>.<path?query>.<userId>.<sha256(body)>
  • METHOD — в верхнем регистре, path?query — путь ровно в том виде, в котором пришёл запрос.
  • sha256(body) — hex-хеш сырого тела. Для запросов без тела — хеш пустой строки.
JS
const crypto = require('crypto');

function verifyApxSignature(req, secret) {
  const timestamp = req.get('X-Apx-Timestamp');
  const userId = req.get('X-Apx-User-Id');
  const provided = req.get('X-Apx-Signature');
  if (!timestamp || !userId || !provided) return false;

  // Отклонение больше 5 минут — отбрасываем: защита от повторной отправки
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;

  const bodyHash = crypto
    .createHash('sha256')
    .update(req.rawBody || '')
    .digest('hex');

  const payload = [timestamp, req.method.toUpperCase(), req.originalUrl, userId, bodyHash].join(
    '.'
  );
  const expected = 'v1=' + crypto.createHmac('sha256', secret).update(payload).digest('hex');

  const a = Buffer.from(provided);
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Считайте хеш от сырых байтов тела, до JSON.parse — пересериализация меняет строку и ломает подпись. И сравнивайте подписи функцией с постоянным временем (timingSafeEqual), а не обычным ===.

Что гарантируется

  • Токен вашего пользователя нам не передаётся и у нас не хранится.
  • Подпись личности проверяется на каждый вызов; истёкший exp = доступа больше нет, отдельного отзыва не требуется.
  • Секрет хранится у нас в зашифрованном виде и никогда не отдаётся обратно через API — только признак, что он выпущен.
  • Ни оператор, ни AI-модель не видят секрет: заголовки формируются на сервере в момент вызова.
  • Запросы уходят только на адреса, прописанные вами в интеграции; переадресации запрещены.