安装插件
嵌入代码
每个站点有自己的 Key。在 站点 → 你的站点 复制嵌入代码,粘贴到 </body> 之前:
<script src="https://cdn.sealreply.com/widget.js" data-site-key="sk_…" async></script>- 插件只在站点允许的域名(及其子域名)上加载,并且要先验证第一个域名;在其他域名上它不会显示。
- 插件渲染在 Shadow DOM 里:你网站的 CSS 不会影响它,它的 CSS 也不会影响你的网站。
- 默认跟随访客浏览器的语言(站点也可以固定一种语言),并用
localStorage记住回访的访客。
内容安全策略(CSP)
如果你的网站设置了 CSP 响应头,需要放行:
script-src https://cdn.sealreply.com;
connect-src https://api.sealreply.com wss://rt.sealreply.com;
img-src https://api.sealreply.com;JavaScript 接口
脚本加载完成后,可以用 window.SealReply 控制插件:
| 调用 | 作用 |
|---|---|
SealReply.open() | 打开聊天窗 |
SealReply.close() | 关闭聊天窗 |
SealReply.on(event, handler) | 发生某事件时调用 handler(见下表) |
SealReply.off(event, handler) | 不再调用 |
SealReply.identify(identity) | 告诉插件你网站上已登录的用户是谁(见识别已登录用户) |
SealReply.reset() | 忘记当前访客,例如用户退出登录时;下一次聊天重新开始 |
SealReply.version | 插件版本 |
| 事件 | 何时触发 | handler 收到 |
|---|---|---|
ready | 插件加载完成 | 无 |
open / close | 聊天窗打开或关闭(访客操作或你的代码调用都会触发) | 无 |
message | 收到客服(或你的集成)发来的消息 | { id, text, content_type, created_at } |
脚本是 async 加载的,调用 SealReply 之前要等它加载完:监听 window 上的 sealreply:ready 事件,或先检查是否已经加载:
<button id="help">联系客服</button>
<script>
function whenReady(fn) {
if (window.SealReply) fn();
else window.addEventListener("sealreply:ready", fn, { once: true });
}
whenReady(() => {
document.getElementById("help").onclick = () => SealReply.open();
SealReply.on("message", (m) => console.log("新回复:", m.text));
});
</script>你的回调里抛出的错误会被捕获,不会影响聊天功能。
识别已登录用户
如果你的网站有自己的登录系统,插件可以让客服知道在和谁聊天:访客以自己的名字出现、标记为已验证,在任何设备上都是同一个访客。为了防止有人冒充他人,每个用户的 ID 都要在你的服务器上用一个只有你和 SealReply 知道的密钥签名。
在站点 → 你的站点 → 访客身份验证中点击开启,复制密钥。密钥只显示这一次,请像密码一样保存在服务器上。
在服务器上计算用户的
identity_hash:用密钥对用户 ID 做 HMAC-SHA256,结果用十六进制表示。jsimport { createHmac } from "node:crypto"; const identityHash = createHmac("sha256", process.env.SEALREPLY_IDENTITY_SECRET) .update(String(user.id)) .digest("hex");php$identityHash = hash_hmac('sha256', (string) $user->id, getenv('SEALREPLY_IDENTITY_SECRET'));pythonimport hmac, hashlib, os identity_hash = hmac.new(os.environ["SEALREPLY_IDENTITY_SECRET"].encode(), str(user.id).encode(), hashlib.sha256).hexdigest()gomac := hmac.New(sha256.New, []byte(os.Getenv("SEALREPLY_IDENTITY_SECRET"))) mac.Write([]byte(userID)) identityHash := hex.EncodeToString(mac.Sum(nil))在用户已登录的页面,于
widget.js之前传给插件:html<script> window.sealreplySettings = { identity: { external_id: "42", // 用户在你网站上的 ID(最多 128 个字符) identity_hash: "<服务器算出的签名>", name: "张三", // 可选,显示给客服 email: "zhang@example.com", // 可选 }, }; </script> <script src="https://cdn.sealreply.com/widget.js" data-site-key="sk_…" async></script>单页应用中,用户登录后调用
SealReply.identify({ external_id, identity_hash, name, email })(插件会以该用户身份重新加载),退出登录时调用SealReply.reset()。
效果:
- 签名有效时,访客与
external_id绑定:对话在不同设备之间延续。external_id按你签名时的原样校验(不去空格、不改大小写)。 - 只有
external_id经过签名。name和email只在访客资料里还没有时填入,不会覆盖客服填写的内容;它们代表页面上的说法,不算已验证。 - 没有签名、签名错误,或站点未开启验证时,访客只是保持匿名,插件照常工作。
- 用户退出登录后,浏览器里的匿名 ID 不能再打开他的对话,只有有效签名才可以。但仍请在退出登录时调用
SealReply.reset():否则在共用电脑上,之后以匿名身份开始的对话会被带到下一个在这台电脑上登录的用户名下。 - 已登录的访客可以在插件菜单里查看自己在你网站上的历史对话,换设备也能看到。只列出他第一次登录之后开始的对话,共用电脑上此前别人以匿名身份聊的内容不会出现。请像对待密码一样对待
identity_hash:拿到它的人能读到这个用户的聊天记录,所以只在你的服务器上签名,不要放进网址或其他人能看到的页面。 - 签名不会过期。如果怀疑签名泄露,请生成新密钥。
- 生成新密钥会让旧密钥立即失效,请先更新服务器上的密钥;关闭后所有访客重新变为匿名。
WARNING
identity_hash 只能在服务器上计算,切勿把密钥放进网页或客户端应用。
传入会员信息
你还可以把会员等级、订单数、累计消费等信息传给客服,客服在会话右侧的"访客"页签里能直接看到,不用再去后台查。为了防止访客在浏览器里改成"钻石会员"之类的内容,这些信息同样要在服务器上签名。
准备一组属性:键最多 40 个字符,不能包含
=或换行;值最多 200 个字符,不能换行;最多 30 个。拼出要签名的文本:先写用户 ID,然后把属性按键名排序,每个属性一行
键=值,各部分之间用换行符\n连接。例如用户42、属性{ plan: "金卡", orders: "12" }得到:text42 orders=12 plan=金卡用同一个密钥对这段文本做 HMAC-SHA256,得到
attributes_hash:jsconst attributes = { plan: "金卡", orders: "12" }; const message = [String(user.id), ...Object.keys(attributes).sort().map((k) => `${k}=${attributes[k]}`)].join("\n"); const attributesHash = createHmac("sha256", process.env.SEALREPLY_IDENTITY_SECRET).update(message).digest("hex");php$attributes = ['plan' => '金卡', 'orders' => '12']; ksort($attributes, SORT_STRING); $lines = [(string) $user->id]; foreach ($attributes as $k => $v) { $lines[] = "$k=$v"; } $attributesHash = hash_hmac('sha256', implode("\n", $lines), getenv('SEALREPLY_IDENTITY_SECRET'));pythonattributes = {"plan": "金卡", "orders": "12"} message = "\n".join([str(user.id)] + [f"{k}={attributes[k]}" for k in sorted(attributes)]) attributes_hash = hmac.new(os.environ["SEALREPLY_IDENTITY_SECRET"].encode(), message.encode(), hashlib.sha256).hexdigest()gokeys := make([]string, 0, len(attributes)) for k := range attributes { keys = append(keys, k) } sort.Strings(keys) lines := []string{userID} for _, k := range keys { lines = append(lines, k+"="+attributes[k]) } mac := hmac.New(sha256.New, []byte(os.Getenv("SEALREPLY_IDENTITY_SECRET"))) mac.Write([]byte(strings.Join(lines, "\n"))) attributesHash := hex.EncodeToString(mac.Sum(nil))和身份一起传给插件:
jswindow.sealreplySettings = { identity: { external_id: "42", identity_hash: "<用户 ID 的签名>", attributes: { plan: "金卡", orders: "12" }, attributes_hash: "<属性的签名>", }, };
签名正确时,这组属性会整体替换访客原来的会员信息;签名不对或缺少签名时,属性被忽略,身份识别照常进行。键请只用英文字母、数字和下划线(如 total_spent),这样各种语言的默认排序结果都一样;值可以是任何文字。