文档

身份校验

借助一个由您的后端掌控的签名,识别已登录的用户,并跨设备恢复其对话历史。

为什么要校验#

默认情况下 SDK 是匿名的:它会生成一个稳定的 visitor_id 并保存在平台密钥库中。这足以支撑单台设备上的单个对话。 但要识别某个具体用户、在重新登录或换设备时恢复历史, 并可靠地把对话绑定到某个联系人档案,后端就需要证据证明客户端确实是它所声称的那个人。 如果 SDK 只是简单地发送「我是用户 42」,任何人都可以伪造成别的 id 去读取他人的聊天—— 所以 Respondo 要求提供一个只有您的后端才能生成的加密签名 userHash。

获取 identity secret#

identity_secret 是一个绑定到您的 agent(您的挂件渠道所连接的 AI 工作单元)的密钥字符串。 请在控制台中生成它:Channels → Widget → Identity verification。 由于它保存在 agent 上,因此以该 agent 为后端的所有渠道——网页挂件和移动 SDK ——共用同一个密钥。它赋予了签署身份的权限, 因此必须只保存在您的后端,绝不能随应用一起分发。

userHash 公式#

userHash 是对单个被签署的身份字符串做 HMAC-SHA256,并编码为小写十六进制:

公式text
userHash = HMAC_SHA256( identity_secret, payload )

payload = userId          // 若设置了 userId
        = email           // 否则,若设置了 email
        = (invalid)       // 若两者都为空,则没有可签署的内容
  • 密钥是 HMAC 的密钥(key);身份字符串是消息(message)—— 不能反过来。
  • 只签署一个字符串——原始的 userId(或 email), 不加盐、也不套 JSON 外壳。
  • 如果您同时传入 userId 和 email, 请签署 userId(它优先)。
  • 输出是十六进制(SHA-256 为 64 个字符),不是 base64。

后端示例#

在您的后端计算签名,并把算好的 userHash 交给应用。

Gogo
import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
)

// HMAC_SHA256(identity_secret, userId),输出为小写十六进制。
func ComputeUserHash(identitySecret, userID string) string {
    mac := hmac.New(sha256.New, []byte(identitySecret))
    mac.Write([]byte(userID))
    return hex.EncodeToString(mac.Sum(nil))
}
Node.jsjavascript
const crypto = require("crypto");

// 返回 userHash,用于在移动端传入 Respondo.identify。
function computeUserHash(identitySecret, userId) {
  return crypto
    .createHmac("sha256", identitySecret)
    .update(userId, "utf8")
    .digest("hex");
}
Pythonpython
import hmac
import hashlib

# 返回 userHash,用于在移动端传入 Respondo.identify。
def compute_user_hash(identity_secret: str, user_id: str) -> str:
    return hmac.new(
        identity_secret.encode(),
        user_id.encode(),
        hashlib.sha256,
    ).hexdigest()
PHPphp
<?php
// 返回 userHash,用于在移动端传入 Respondo.identify。
function computeUserHash(string $identitySecret, string $userId): string {
    return hash_hmac('sha256', $userId, $identitySecret);
}

哈希如何抵达 Respondo#

Respondo 没有单独的身份接口。没有 /identity 这样的路由可供调用,也不需要注册任何东西。 userHash 只是一个字段,它随 SDK 本来就会发出的请求一起传输。

一共两跳,而只有第一跳需要您来实现:

  1. 您的后端 → 您的应用。怎么把哈希送过去由您决定。最省事的做法是在您本来就会返回的登录/初始化响应里 多加一个字段——不需要额外的往返请求。在您自己的后端上开一个专门的接口(比如 POST /myapp/identity 返回 { userId, userHash })同样可行。Respondo 不提供这个接口——它由您来实现。
  2. 您的应用 → Respondo。这一跳由 SDK 代劳。只要您调用了 identify, SDK 就会把哈希附加到每个相关请求上,后端每次都会重新校验它。
SDK 会把它放在哪里(仅供参考——您不需要手动发送这些)text
POST /api/v1/chat                      body   identity.userHash
GET  /api/v1/chat/resume               query  user_hash
GET  /api/v1/chat/history              query  user_hash
GET  /api/v1/chat/ws                   frames    user_hash (subscribe/identify JSON frames after connect — not in the handshake URL)
GET  /api/v1/widget/tours              query  user_hash
GET  /api/v1/widget/checklists         query  user_hash
POST /api/v1/widget/push/register      body   user_hash

哈希在每个请求上都会被重新校验,而不是一次换取一个会话——正是这一点让偷来的 userId 单靠自己毫无用处。

如果您后端上的这个下发接口还没写好,您自己应用发出的请求就会 404, identify 永远不会被调用,聊天就会以匿名方式运行。这是预期行为,不是 Respondo 的错误——您自己路径上的 404 是您这边的待办事项,而不是集成坏了。

向 SDK 传入身份#

从您的后端获取已签署的身份,然后在任意平台把 userHash 传入 identify:

Kotlinkotlin
Respondo.identify(
    RespondoIdentity(userId = "42", email = "user@example.com", userHash = hash),
)
Swiftswift
Respondo.identify(
    RespondoIdentity(userId: "42", email: "user@example.com", userHash: hash)
)
Dartdart
Respondo.identify(RespondoIdentity(
  userId: '42', email: 'user@example.com', userHash: hash,
));

userHash 无效时的行为#

当 agent 上启用了校验而签名缺失或错误时,后端会执行静默降级为匿名: 聊天仍然可用, visitor_id 会保留,对话会绑定到匿名联系人——但不会关联到用户档案,也没有跨设备历史。 如果某个用户「无法被识别」,几乎总是签名的问题:请检查您签署的是 userId(不是 email 也不是 JSON)、 用的是正确的 identity_secret、 并且输出的是小写十六进制。如果 agent 的 identity_secret 为空,则校验关闭, userId / email 会被原样接受。

轮换 identity_secret 实际上是一次硬切换:用旧密钥生成的每个哈希都会立即失效, 因此已登录的用户会静默回退为匿名,直到您的后端用新密钥重新计算并重新提供他们的 userHash。 只有当您能在同一时间窗口内更新签名端时,才去轮换该密钥。