网站挂件

挂件就是把机器人的聊天直接放到你的网站上:页面角落一个圆形按钮,点开即是对话。对访客而言这只是多了一个联系你的方式;对机器人而言这是一个普通渠道——和即时通讯里一样的反应、一样的客服对话。多渠道概览见渠道

安装就是把一段代码贴进网站模板。其余全部在后台设置,不必再改动网站。

在哪里取得安装代码

在后台打开 挂件安装到网站 区块 → 嵌入代码 字段。复制 按钮会把整段代码放进剪贴板。

代码形如下面这样,省略号处是你自己的密钥:

<script>
  window.mybot = { key: "eu-1a2b3c4d-..." };
</script>
<script async src="https://getmybot.dev/loader.js"></script>

如果看到的不是代码,而是「尚未为该机器人签发安装密钥」,说明网站挂件渠道还没有接入。请联系客服:密钥在接入渠道时签发。

代码下方有一行提示,说明挂件会访问哪个主机。若你的网站启用了 CSP(内容安全策略),请允许该主机加载脚本并发起网络请求,否则浏览器会悄无声息地拦掉挂件。

代码放在哪里

这段代码要放进 每一个 需要显示挂件的页面的 HTML 中。实际做法是:在网站的公共模板里放一次——页脚、CMS 中「</body> 之前的代码」字段,或标签管理器的容器。

规则很简单:

  • 最佳位置是 闭合的 </body> 之前。放在 <head> 也能跑,但浏览器会在你的内容之前先花时间处理挂件。
  • 两个标签的顺序很重要:第一个设置密钥,第二个加载挂件。不要互换,也不要拆开放在页面不同位置。
  • 第二个标签带 async,不会阻塞渲染。请不要删掉这个属性。
  • 每页一份。放两份意味着尝试启动两次。

其余的由加载器自行完成:判断你的机器人由哪个数据中心服务,并从那里取回挂件主体。这一步不需要你做任何事。

安装密钥是公开的

第一个标签里的密钥是 机器人的公开标识,不是密码。它就在页面源码里:任何访客打开「查看网页源代码」都能看到。所有聊天挂件都是这样,属于正常现象。

需要明白的是:

  • 用这个密钥 无法 登录后台、读取他人对话、导出订阅者名单,也无法修改机器人的任何设置。它只开启一件事:与这个机器人开始一段新对话。
  • 不要把密钥当作机密:隐藏或混淆它没有任何意义。
  • 如果需要更换密钥(例如与外包方结束合作),由客服操作。更换后网站上的旧代码将失效,需要更新。

允许域名列表

挂件有一份 允许域名列表(origin allowlist)——允许挂件运行的网站地址。它在接入渠道时设定,之后可以单独修改而无需重新签发密钥:你网站上的代码保持不变。

列表中的每一项都是一个 来源(origin):协议 + 主机,若端口非默认再加端口。

https://example.com
https://www.example.com
https://shop.example.com
http://localhost:3000

比对是 逐字符精确 的:

  • 没有通配符。https://*.example.com 不会生效——请逐个列出子域名。
  • example.comwww.example.com两条不同 的记录。若网站两个地址都能打开,两条都要加。
  • http://https:// 同样是不同记录。通常只需要 https://,但 http:// 上的测试环境必须显式添加。
  • 不允许带路径:https://example.com/shop 会被拒绝,来源只包含网站地址。

如果你真实的域名不在列表里,挂件不会启动。 访客的浏览器会收到「origin not allowed」的拒绝,聊天根本不会打开。这通常出现在迁移到新域名、增加子域名,或在独立地址上线第二个语言版本之后——每一种都需要单独添加一条。

如果列表 为空,则没有任何限制,挂件可从任意站点运行。请把这视为「尚未配置」,而不是「有意开放」:一旦确定了自己的域名,就把它们填上。

这项限制能防什么,不能防什么

能防: 别人的网站无法把你的挂件嵌到自己页面上。否则有人可以把你的代码复制到自家页面,真实访客的浏览器就会把与你机器人的对话内容交给那个站点。列表堵住的正是这一点。

不能防: 它挡不住已经拿到密钥、直接向平台发请求的人——不是通过浏览器,而是比如用脚本。携带站点地址的请求头是浏览器加上的;浏览器之外的程序可以不发,也可以随意伪造。所以请把这份列表看作嵌入限制,而不是安全边界。防止滥用的是平台侧的频率限制和对话审核,而不是这份列表。

外观

挂件外观 中可设置:

  • 位置 —— 按钮在左下角或右下角。
  • 强调色 —— 按钮与聊天元素的颜色。使用你的品牌色,挂件才不会显得突兀。
  • 聊天名称 —— 启动按钮上的文字和聊天面板标题。通常是公司名或机器人自我介绍时使用的名字。

旁边有 预览 —— 一个实时挂件,保存前就能看到效果。

挂件目前还没有欢迎消息:可配置的只有上述三项。第一条消息由机器人在访客说话后按你平常的反应规则发送。

改动会自动生效:挂件在页面加载时读取设置,无需修改已贴入的代码。

访客是匿名的

访客无需填写任何内容即可发消息。首次启动时,挂件从平台获得一个 匿名访客标识 并存入浏览器,这样访客再次到访时看到的是自己的既有对话,而不是空白聊天。

由此可知:

  • 在对话列表中这类访客显示为匿名:在他自己写出之前,没有姓名、电话和邮箱。
  • 标识只存在于某一个浏览器中。换浏览器、换设备或清除网站数据,都意味着一个记录为空的新访客。
  • 匿名访客可以与你已经认识的客户关联起来,例如已登录会员区的用户。做法见下一节。

把访客关联到你自己的用户

如果访客已经在你的网站上登录,你可以告诉平台他是谁。关联之后,会话就不再匿名:它会并入这个人的档案,之前的匿名消息也不会丢失。

一次调用即可,附带一个负责生成证明的函数:

mybot.identify(async (visitorId) => {
  const res = await fetch("/mybot-sign", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ userId: currentUser.id, visitorId }),
  });
  return res.json(); // { userId, signature, expiresAt }
});

挂件会用 visitorId——这个浏览器当前的匿名访客标识,唯一只有挂件才知道的值——调用你的函数,并等待它返回,或者解析为,{ userId, signature, expiresAt }。你的函数唯一要做的事,是把 visitorId 连同已登录用户的标识(上面的 currentUser.id 换成你站点上实际的值)一起交给你自己的服务器,再原样把服务器的响应返回。真正的签名发生在你的服务器上,不是在这个函数里——见下文。

之所以是一个回调,而不是预先算好的值,也不是直接读取 visitorId 的方式,有两个原因:

  • 存在的时机。 挂件在启动过程中异步生成 visitorId——加载脚本运行的那一刻它还不存在,mybot.identify 本身也要等这个过程结束后才会安装(详见下文)。所以到你的页面能够调用 mybot.identify 的那一刻,你的函数收到的标识必然是真实的。普通的 getter 不会有这种保证:没有什么能阻止页面提早一行读取它而一无所获,悄无声息地生成一个永远对不上、又没有任何线索能说明原因的签名。
  • 作用域。 你自己页面的代码完全不需要手动持有、保存或传递 visitorId——它只存在于这一个函数内部,只为它需要发起的那一次调用而存在。

visitorId 一旦离开挂件,就成了一种持有即生效的凭证(bearer capability):任何能为它拿到签名的人,都可能被并入那位访客的会话。只把它发给你自己的服务器,通过你自己经过身份验证的请求,别的任何地方都不要发——不要记进日志,不要转发给第三方,也不要放进客户端的分析调用里。

签名只在你自己的服务器上计算

在你上面的函数内部,你自己的服务器——绝不是浏览器——负责计算签名:用挂件密钥做的 HMAC-SHA256,取十六进制的 前 32 个字符,对象是拼成一条消息的三个值。同一个公式也显示在后台密钥旁边的「Signing algorithm」下方:

signature = hex(HMAC-SHA256(key: secret, message: userId + "\n" + visitorId + "\n" + expiresAt)).slice(0, 32)

上面每一处 + "\n" + 都是各部分之间一个真正的换行符,而不是反斜杠加 n 这两个字符。如果你的 HMAC 函数接收的是一整条字符串,就用真正的换行把三部分拼起来——分三次调用 .update() 而中间不插入换行,会对一条不同的、错误的消息求哈希。

三个部分:

  • userId —— 已登录用户的标识,与你的服务器从上面那个函数收到、并在响应里原样返回的值相同。
  • visitorId —— 你的函数作为参数收到、并原封不动转发给你服务器的匿名访客标识。
  • expiresAt —— 以秒为单位的 unix 时间戳(不是毫秒),由你的服务器在签名时生成,标记这条具体签名的有效截止时刻。平台会拒绝 expiresAt 已经过去的调用,也会拒绝比当前时间超过 24 小时的调用——请在从函数返回之前才签名,不要签一次后续请求反复复用。

这个有效期正是这次改动的核心,而不是次要细节。旧公式只覆盖用户标识,所以一个签名一旦被捕获——不管是记在了某处日志里,还是从流量里截获的,方式不重要——就永远有效,并且对任何访客都成立,而不只是对当初签发的那一个。谁拿到了它,都能在完全不同的浏览器里重放,平台就会把一个陌生人的匿名浏览记录并入真实客户的档案。把签名绑定到某个具体的 visitorId、再给它一个短寿命,就把这个漏洞的两头都堵上了:签名只对签发时的那一次会话有效,别处一律不认;一旦过了 expiresAt,它就彻底失效——所以即便签名泄露,也只是限于单次会话的短暂风险,而不是长期隐患。

请在你的服务器上把三个值都算好,从函数里已经算好的状态返回。 这不是走形式:要在浏览器里算签名,就得把密钥本身发到浏览器,等于交给页面的每一位访客。此后任何人都能冒充你的任意客户并读取其会话。把签名(以及计算它所用的那对 visitorId/expiresAt)交还给浏览器是安全的,密钥不是。

调用本身在界面上没有任何提示:关联发生在服务端,静默完成。如果你的函数抛出异常或它的 promise 被拒绝,又或者平台拒绝了得到的签名——对不上,或者 expiresAt 缺失、已经过去,或比当前时间超过 24 小时——那也就到此为止:访客毫无察觉,继续以匿名身份聊天。

mybot.identify 不会立刻存在——它要等挂件主程序加载完成、带着真实的 visitorId 一起才会出现(见上面的「存在的时机」)。请在页面加载的回调里调用,而不要写在 <head> 的第一行。

密钥在哪里,以及如何更换

挂件密钥就是你用来签名的那把钥匙。它在挂件设置的 「identify() secret」 区块里。若该区块提示挂件尚未接入,请先接入 web 渠道——密钥会随之出现。

查看。「Reveal secret」 按钮取回值。它不会直接以明文出现:你先看到的是圆点,显示字符要用另一个开关 「Show」「Mask」 再遮回去)。旁边还有 「Clear from screen」,把值从屏幕上移除。

可以反复查看,而且查看不会让任何东西失效:关掉这个界面之后密钥依然有效。之所以如此,是因为它以加密而非哈希的方式存储——平台每次校验签名都需要它的真实值。半年后你部署新后端时,回来再看一次即可。

复制之后请自己收尾。剪贴板平台清不掉:在浏览器里没有可靠的做法,所以我们也不作此承诺。用完后把值从屏幕上移除,并手动清空剪贴板,在共用电脑上尤其如此。

更换。 「Rotate secret」 按钮,带确认步骤。这是 立刻生效的破坏性 操作:新密钥一旦生成,旧的当即无法通过校验。没有重叠窗口,也不存在两把钥匙同时有效的瞬间。

被打断的恰恰是把访客关联到你的用户这一环。会话不会中断——访客照常发消息、照常收到回复,只是以匿名身份——直到你的后端改用新密钥签名。安装代码和允许域名列表都不受影响,你的网站上无需改动任何东西。

所以更换要有意为之:先准备好使用新值的后端发布,然后再点「Rotate secret」。为了看看会发生什么而点击,是个坏主意。更换后新值会立即显示在屏幕上,可以当场复制。

挂件在页面上的行为

挂件被刻意设计成不与你的网站互相干扰:

  • 它运行在 隔离容器 中(封闭的 Shadow DOM)。你的样式进不去,挂件的样式也出不来。副作用是:你无法用自己的 CSS 改挂件外观——请使用外观设置。
  • 容器铺满屏幕,但 不拦截点击:点击会穿透到页面,只有按钮和聊天面板本身会响应。
  • 挂件始终位于页面内容之上,你的图层无法遮住它。
  • 挂件内部的任何错误都留在内部:最坏也只是聊天按钮不可用,你的网站照常运行。若挂件没出现,请查看浏览器控制台——多半是 CSP,或是域名没加进列表。

如果挂件没有出现

按顺序排查:

  1. 打开网页源代码,确认两个标签都在,且密钥不为空。
  2. 确认网站地址与列表中的写法完全一致,包括 https://www
  3. 查看浏览器控制台:出现内容安全策略相关提示,说明需要在网站 CSP 中放行平台的主机地址。
  4. 确认没有广告拦截器把挂件删掉——在不带扩展的隐私窗口里试一次。

接下来