Appearance
传客户资料给客服
客户打开链接时,客服在工作台里看到的是「匿名访客」。把客户是谁告诉系统,客服就能看到姓名、手机号和你自己的业务信息。
不传身份时,合从靠浏览器里的一个本地标识认人。客户清缓存、换设备或者用无痕模式,标识就没了,回来会被当成新客户,之前聊过的内容他自己也看不到(客服那边的历史对话始终完整保留)。传了客户 ID 之后按 ID 认人,换手机换电脑都能接上同一段历史。
这些资料是给客服看的
传过去的信息显示在客服工作台的客户档案里:

客户自己看不到这些。聊天窗口里不显示客户自己的头像和名字,传了头像也一样,只有客服在工作台里能看到。这套接口的用途是让客服知道在跟谁说话,不承担面向客户的展示。
门槛最低的做法:网址上带参数
给链接加上参数,客服那边就能看到这位客户叫张三、手机号是多少:
https://你的域名/chat.html?u=a3f9c2&n=张三&p=13800138000这个链接由你的系统动态拼出来。 客户在你的网站、APP 或小程序里点客服入口时,你的程序取到当前登录用户的信息,拼成上面这样的地址再跳转过去。客户成千上万,不可能手工一个个编。
它的门槛低在于不用碰合从这边的东西:不改 chat.html、不用了解合从的接口,你的技术人员在自己熟悉的系统里拼个网址就完成了。相比之下,在页面里调用要改 chat.html 并调用合从的方法。
小程序里的完整写法可以直接参考,网站和 APP 同理。
| 参数 | 传什么 | 说明 |
|---|---|---|
u | 客户在你系统里的唯一 ID | 必填,其余资料都要挂在它上面 |
n | 姓名 | |
p | 手机号 | |
e | 邮箱 | |
d.<字段标识> | 你自己的业务字段 | 如 d.member_level=gold,可以带多个。字段标识见下方说明 |
d. 后面跟的不是你想怎么写就怎么写:它必须是你先在工作台里建好的那个字段标识。
在工作台的 设置 > 客户字段 里新建一个字段,填的 字段标识 就是这里要用的名字。它只能用小写字母开头、由小写字母、数字和下划线组成(member_level、store_no),最长 64 个字符,建好之后不能再改。建法见自定义字段。
没建过的字段标识传过来会被直接丢弃,客服那边看不到,链接这边也不会有任何报错。拼错一个字母就是这个后果,所以第一次接完先按调试模式看一眼收下了哪几个。
四件事要知道:
- 中文和特殊字符要做 URL 编码,否则可能传错。
- 只写资料不写
?u=是无效的,资料会被直接忽略。打开调试模式能看到是哪几项被丢掉了。 n、p、e、d.*在页面打开后会自动从地址栏消失,u保留下来用于认人。客户看不到自己的手机号挂在地址栏上,转发页面也不会带出去。抹除不重新加载页面,也不影响刷新——资料第一次进来时就已经存进客户档案了。- 头像不能走网址。它有格式校验,一个坏值会让整批资料都传不进去,需要传头像用在页面里调用。
客户 ID 怎么选
u 是这套机制的地基,选错了会出真问题。
系统按 u 认人。两个客户拿到同一个 u,系统就认为他们是同一个人——后来的那位会直接看到前一位的聊天记录。这类问题客户会投诉,而且事后很难追回。
绝对不要给多个客户用同一个 u
最常见的错误是代码里图省事写了个固定值:
js
// ✗ 所有没登录的客户都串成同一个人,互相看到对方的聊天记录
const url = 'https://你的域名/chat.html?u=guest&n=' + name拿不到客户 ID 的时候,正确做法是整个 u 参数都不要传。不传就是匿名访客,客服照常接待,各聊各的,不会串。
guest、test、0、undefined、null 这类一看就是占位的值,系统不会拿来建客户档案。不管从网址参数进来还是页面里调 identify() 传,结果都一样:这位客户没有会员身份,按匿名访客照常接待,聊天不受影响。差别只在你能不能当场发现:调 identify() 会拿到一个明确的拒绝;网址参数那条路不报错,要打开调试模式才看得出来。但这道判定放过纯数字:不少公司的会员号本身就是自增整数,1 完全可能是真实的第一个会员。所以「别人猜不到」那一条没有系统兜底,取值得你自己选对。
选值的三条要求:
- 每个客户不一样,这是硬要求。
- 不会变,各处也要一模一样。客户改昵称、换手机号之后还是同一个
u,否则跨设备认不出、历史对话接不上。多个页面或多个渠道各自拼这个值时,大小写、前后空格、有没有加前缀都要对齐 —— 差一个字符就是另一个人。 - 别人猜不到。
第三条的原因是网址上的参数客户自己能改:把 ?u=10086 改成 ?u=10087,就能看到另一个客户的对话。所以顺序自增的会员号、手机号这类能猜、能穷举的值,风险很高。
推荐用你系统里本来就有的、不可猜的标识:随机字符串、UUID,或者用密钥对客户 ID 做一次哈希。
还有一层要看业务性质。如果客服要凭这个身份帮客户查订单、看余额、改积分,网址参数的强度不够 —— 合从尚未启用签名校验,改个参数就能冒充。
这类业务改用页面里调用,由你自己的服务器生成身份,安全性就不依赖客户能改的网址参数了。称呼、手机号这类识别信息用网址传是合适的,伪造了对伪造者也没什么好处。
跨渠道认人
在不同渠道传同一个 u,系统就会认成同一个人。客户先在你的网站上咨询过,之后打开对话链接进来,只要传的是同一个值,他就能看到之前聊过的内容。
客服在工作台里看到的是:
- 客户列表里一个人一行,不会因为换了渠道变成好几个客户
- 客户档案里能看到他在所有渠道的对话记录,也能看到他还从哪些渠道来过
- 手机号、邮箱、标签这些资料归拢到同一份档案里
认的是同一个人,对话仍按渠道各走各的。 同一个客户可能在网站和对话链接上各有一条进行中的对话,未读消息也是各渠道分别计算。客服接待时面对的仍是各自独立的对话。
没传 u 的匿名访客不参与这套认人,各渠道各自独立。
认人只看客户 ID 这一个值。手机号、邮箱、姓名都只是资料,不参与判定:两次咨询填了同一个手机号,不会因此归成同一位客户。要让他们接上,就得传同一个值。
网址参数每次打开都会写入
客户每次通过链接进来,网址上带的资料都会写进他的档案,传什么覆盖什么,包括客服在工作台里手工改过的值。所以链接上拼的要是你系统里的当前值。改了 ?n= 重新发给客户,客服那边看到的就是新名字。
同一份资料重复打开不会反复提交,只有值变了才会写一次,不用担心撞限流。
要传更多,或者要随时更新
网址参数够用就不必看这一节。需要传头像、需要传数字类型的业务字段、或者要在客户登录后再补资料,就在页面里调接口。
在下载的 chat.html 文件末尾的脚本区域写:
html
<script>
window.hecongLink(function (hc) {
hc.identify({
id: 'a3f9c2e1-5b74-4d80-9c62-1fe3a4b5d6c7',
profile: { name: '张三', phone: '13800000000' },
data: { member_level: '黄金会员', order_count: 12 },
})
})
</script>脚本要写在加载客服程序那一行之后。 window.hecongLink 由加载脚本挂载,写在 <script src="..."> 之前会报 hecongLink is not defined,页面上看不出异常,但你的代码一行都不会跑。下载的文件里【你的脚本写这里】区域已经在正确位置。
注册早晚都能拿到命令对象,合从内部会排队,也可以注册多个回调。回调参数 hc 与网页接入渠道拿到的是同一套命令,除了入口名(那边是 window.hecong),业务代码两边可以照搬。
从自己的接口换取客户资料
网址上带一个标识、页面里调你自己的接口换取完整资料,敏感信息就不用出现在网址里:
html
<script>
window.hecongLink(function (hc) {
var token = new URLSearchParams(location.search).get('token')
if (!token) return
fetch('/api/customer-service/identity?token=' + token)
.then(function (r) { return r.json() })
.then(function (d) {
return hc.identify({
id: d.userId,
profile: { name: d.name, phone: d.phone },
})
})
.catch(function (e) {
// 换取失败时客户仍是匿名访客,客服照常能接待。
// 建议接入你自己的日志,否则线上会出现「时有时无的匿名客户」且难以复现
console.error('identify failed', e)
})
})
</script>客户登录前后怎么调
最常见的场景是客户先匿名浏览、聊了几句,然后才登录。从匿名到登录直接调 identify() 就行,之前的匿名对话会自动归到这个客户名下,客服看到的是连续的一段对话。
从一个客户换成另一个客户直接调 identify() 就行,合从会自动把上一位的痕迹清干净再写入新身份。
共用设备的登出流程里一定要调 reset
换客户不用手动 reset(),前提是下一位客户也登录 —— 他的 identify() 就是换人的触发点。
下一位客户不登录,就没有任何东西会触发换人:他顶着上一位的身份进来,看到上一位的历史对话。营业厅、展厅平板、APP 内嵌的 WebView 这类多人共用的场景最容易出这个问题。
所以客户退出登录时,在你自己的登出流程里调一次 hc.reset(),把身份收回来。
服务器渲染时写入
如果这个页面由你的服务器动态生成(PHP、Node、Java 等),可以直接把身份渲染进页面配置,不用再调接口:
html
<script>
window.__hcLink = {
channelId: '0190a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b',
identity: {
userId: '<%= 当前登录用户ID %>',
profile: { name: '<%= 昵称 %>', phone: '<%= 手机号 %>' },
data: { member_level: '<%= 会员等级 %>' }
}
}
</script>页面初始化完成后自动完成识别,不需要再调 identify。
静态文件不能写死客户身份
这种写法只适用于服务器为每个客户单独生成页面的情况。如果 chat.html 是一个固定不变的静态文件,所有人打开的是同一份内容,写死身份意味着所有人被识别成同一个客户,消息全部串到一个人名下。
静态托管的场景用网址参数或者页面里调用。
字段速查
写代码时对照下面这些表,只用网址参数的话前面已经够了。
两个方法
拿到命令对象 hc 之后,身份相关的操作有两个。
| 方法 | 什么时候调 | 对已有资料的影响 |
|---|---|---|
hc.identify(...) | 客户登录后;资料变了再调一次 | 传入的字段覆盖原值,没传的保留 |
hc.reset() | 客户退出登录 | 清掉身份,结束当前对话 |
identify 的完整签名是 hc.identify({ id, profile, data });reset 之后历史对话和资料都保留,只是当前这通结束。
绑身份和写资料是同一个方法:第一次调把客户认出来,之后资料变了(改了昵称、升了会员等级)再调一次,传什么覆盖什么,没传的字段不动。你的系统就是客户资料的事实源。
换客户不需要你做额外处理:已经绑定了 A,直接 identify() 成 B,合从会先把上一位的痕迹清干净(换掉匿名标识、丢掉还没落库的资料、重新领取会话凭证),再写入新身份,两个人的对话不会串在一起。这个判定在页面刷新、重新打开之后依然有效。
js
hc.identify({
id: 'c9a1f0e2-7b34-4d58-9f61-0ab2d3c4e5f6',
profile: { name: '张三', phone: '13800138000', email: 'zhangsan@example.com' },
data: { member_level: 'gold' }
})三个方法都返回 Promise,成功后可以拿到写入结果。
网页接入渠道:客户没点开聊天窗时,这个 Promise 不会返回
访客进站不点咨询按钮,合从不会加载聊天窗、也不会连服务端。这期间调 identify(),命令会先存下来,等客户真正点开聊天窗再执行,Promise 也在那时才有结果。
所以别用 await hc.identify(...) 去挡自己页面的逻辑,那会一直等下去。要拿 acceptedKeys 调试,在聊天窗打开之后再看。对话链接是整页聊天,打开即执行,没有这个问题。
id 是字符串,1 到 255 字符,空值会被拒绝,不会把人串到一起。
profile:合从内置的资料字段
| 字段 | 类型 | 上限 | 超限会怎样 |
|---|---|---|---|
name | string | 100 字符 | 截断后保存,不报错 |
phone | string | 20 字符 | 截断后保存,不报错 |
email | string | 255 字符 | 截断后保存,不报错 |
avatar | string | 500 字符 | 整次调用失败,且必须是 https:// 开头 |
头像和其他三个字段的失败方式不一样:地址不合规或超长,整个请求被拒,同一次传的姓名手机号一并丢失,客户仍然是匿名访客。其余三个超长只是截断,不影响别的字段。
手机号那条也提醒一下:带国际区号和分隔符的号码(如 +86 138-0000-0000)容易超过 20 字符,会被悄悄截成错号,客服拨过去打不通,而你收不到任何提示。传之前先规范化。
data:你自己的业务字段
会员等级、积分、内部客户编号这类字段走 data。
js
hc.identify({ id: 'c9a1f0e2-7b34-4d58-9f61-0ab2d3c4e5f6', data: { member_level: 'gold', points: 3200, is_vip: true } })| 约束 | 说明 |
|---|---|
| 键名要先定义 | 必须是工作台里建好的字段标识,见下方 |
| 键名格式 | 小写字母开头,只能用小写字母、数字和下划线(member_level、store_no) |
| 键名长度 | 最长 64 字符,超了整次调用失败 |
键名不是随便起的:在工作台的 设置 > 客户字段 里新建字段,填的 字段标识 就是这里要用的键名,创建后不可修改。显示名(比如「会员等级」)只影响客服看到的标题,代码里认的始终是字段标识。 | 值的类型 | 字符串、数字、布尔值。对象和数组会让整次调用失败 | | 值的长度 | 字符串超过 200 字符会被截断后保存 |
没定义过的键会被丢弃,接口不报错,但浏览器控制台会列出被忽略的键名。一次传多个键时,没定义过的只丢它自己,其余照常写入。返回结果里的 acceptedKeys 是实际写进去的字段名,调试时对照它。
客服那边会看到资料变动
资料真正写进客户名片之后,客服在这通对话里会看到一条灰字提示,右栏 对话动态 里能看到这次更新了哪些字段,来源标的是接入代码。
传的值和名片上现有的一样时不会提示,所以每次页面加载都调一次 identify() 不会在对话里刷屏。客户当时没有进行中的对话,资料照样写进名片,只是没有地方显示这条提示。
失败了怎么办
调用失败时 Promise 会带回一个 code:
code | 含义 | 你该怎么做 |
|---|---|---|
NETWORK | 请求没发出去或超时 | 可以重试,此时客户仍是匿名访客 |
VAL_SCHEMA | 参数不合法,最常见的是头像地址违规 | 检查参数,重试没有用 |
AUTH_TOKEN_INVALID | 会话凭证失效 | 让页面刷新一次 |
AUTH_ORIGIN_DENIED | 当前域名不在渠道的授权域名里 | 去 设置 > 渠道管理 > 安全与隐私 补上这个域名,重试没有用 |
SERVER | 服务端异常,或调用太频繁被限流 | 隔一会儿再试。同一位访客 10 分钟内只能调 5 次,短时间反复调会一直失败 |
限流那条值得单独留意:它是按访客算的,页面每次加载都调一次属于正常用法,不会撞上;但如果你在轮询里调、或者每个字段一变就单独调一次,配额很快见底,真正需要的那次反而失败。资料变了攒到一起调一次,不要拆成好几次。
一定要接 .catch。 不接的话失败是完全静默的:客户变回匿名访客,页面上没有任何迹象,你也收不到告警。
走网址参数(?d.<键名>=)时,data 的值只能是字符串;走页面调用时才能传数字和布尔值。
常见问题
网址上带了姓名,客服还是看不到
症状:链接写成 ?n=张三,客服工作台里客户仍是匿名,或者没有姓名。
成因:多半是漏了 ?u=。资料要挂在一个具体客户身上,没有客户 ID 的资料会被忽略。
解决:补上 ?u=,例如 ?u=a3f9c2&n=张三。不确定是不是这个原因,在链接后面加 ?hcdebug=1 打开调试模式,面板会直接写明哪一项没送出去、该怎么改。
不同的客户看到了同一份聊天记录
症状:两个不相干的客户打开链接,看到对方的对话内容。
成因:他们拿到了相同的 u。最常见的是代码里给拿不到 ID 的客户写了固定值,比如 ?u=vip。guest、test 这类占位值不会造成串号,它们会被当成没传,各自按匿名访客接待。
解决:改成拿不到客户 ID 时不传 u。已经串在一起的对话需要联系技术支持处理,客户档案一旦合并无法自行拆分,所以上线前务必先验证这一点。
传的自定义字段没显示
症状:链接里带了 ?d.member_level=gold,客户档案里看不到。
成因:键名没有在工作台的自定义字段里定义过。
解决:在 设置 > 客户字段 里新建对应字段,字段标识 填成链接里用的键名。调试模式的面板会列出哪几个键名没被收下,可以对照着改。
换了设备之后聊天记录接不上
症状:同一个客户在手机和电脑上打开链接,看到的是两段互不相干的对话。
成因:没有传 u,或者两次传的值不一致。匿名访客靠浏览器本地标识区分,换设备就是新访客。
解决:两端都传同一个 u。它应该来自你系统里稳定不变的客户标识,不要用会变化的值。
回调里的代码没有执行
症状:hecongLink 里的函数一直没被调用。
成因:客服程序没有加载成功,常见于渠道被停用,或者页面文件里的加载地址被改动过。
解决:确认渠道在工作台是启用的,再检查 chat.html 里的 <script src="..."> 那一行有没有被改过。详见故障排查。