把 DeskClerk 的转人工事件接到你们的 Slack
1. 这一页适合谁
你们的客服团队平时泡在 Slack 里,想在网页访客要求转人工的那一刻就知道。
2. 你能得到什么 / 你得不到什么
你能得到
- 你网站上那套 AI 回答,变成两个简单的 HTTPS 接口,你们自己的代码直接调。
- 会话记忆:一个 Slack 会话对应一个
conversationId,AI 记得前面说过什么。 - 平台替你判好的转人工信号:该换人时,返回里带一个
escalation对象,你们不用自己写判断规则。 - 所有渠道共用一套接口。加 Slack 不用改你在 DeskClerk 里的任何设置。
你得不到
- 我们不在 Slack 里面运行。我们没有 Slack 的应用、机器人或连接器,也没有任何市场上架。
- 我们不收也不发 Slack 的消息。收和发,仍然是你自己的后端(或者你在这个渠道用的那套平台)在做。
- 我们不知道你渠道里的用户 ID。
external_user_id是你自己传的,只要在你那边稳定就行。 - 用量按你套餐的月度额度算,按回答条数计,不够了随时在工作台加购。
3. 开始之前要准备什么
- 一个至少发布过一次的 DeskClerk 站点。 没发布,AI 手里就没有可答的内容。
- 你的 Site ID(一个 UUID,例如
7b0c9f5e-2a8d-4f31-8e6b-1c2d3e4f5a6b)。工作台的站点设置里能看到。 - 一把带
chat:write权限的 API 钥匙。 在工作台建,只放在你们服务器上——不要放进手机 App,也不要放进前端 JavaScript。 - 一台能发 HTTPS 请求的服务器,以及一个存
conversationId的地方(每个用户会话存一个)。 - 你要当
locale传的那个语言代码。 必须是你这个站点已开启的语言(例如en、zh),照站点设置里显示的写法原样传。 - 你 Slack 的消息后端。 Slack 里收消息、发消息都在你那边;DeskClerk 只是你调用的回答引擎。
4. 分步操作
- 先搞清楚平台自己做了什么:网页访客要求转人工时,这段会话会在你的 DeskClerk 工作台里被标记为转人工,站点所有者的邮箱会收到一封通知邮件。DeskClerk 不会往 Slack 发任何东西。
- 在你们的 Slack 里建一个 Incoming Webhook(Slack 管理员 → Apps → Incoming Webhooks),webhook 地址只放在你们服务器上。
- 在你们这一侧照第 5 节调发消息接口,检查返回里的
escalation对象。 escalation出现时,把访客原话和 conversationId POST 到你们的 Slack webhook。- 把 DeskClerk 的
conversationId和 Slack 里的话题对应起来,方便人工接对人。
DeskClerk 里没有 Slack 应用,也没有往 Slack 频道推送的功能。 这一页讲的桥,跑在你们的服务器上,一头连 DeskClerk 的接口,一头连 Slack 的 Incoming Webhook。
下面两步就是全部对接:先建会话,再逐条发消息。没有别的必做项;我们的接口只增不改,你今天写的代码以后一直能用。
5. 可复制的完整示例
先把三个值定下来,剩下的都是固定写法:
| 变量 | 示例值 | 从哪来 |
|---|---|---|
BASE_URL |
https://chat.example.com |
承载你挂件和帮助中心的那个域名 |
SITE_ID |
7b0c9f5e-2a8d-4f31-8e6b-1c2d3e4f5a6b |
工作台 → 站点设置 |
DESKCLERK_API_KEY |
你的钥匙 | 工作台 → API 钥匙(chat:write 权限) |
第一步 · 建会话(每个会话建一次):
curl -sS -X POST "$BASE_URL/api/v1/integrations/sites/$SITE_ID/conversations" \
-H "X-Integration-Api-Key: $DESKCLERK_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: conv-slack-12025550123-1" \
-d '{"channel":"slack","external_user_id":"12025550123","user_name":"张三","locale":"zh","timezone":"Asia/Shanghai"}'
返回里的 data.id 就是 conversationId,存到你自己的会话 ID 旁边。示例里存下来的会话 ID 是 0f4c2a91-5d63-4b8e-9f27-3a1b6c8d5e40。
第二步 · 逐条发消息、拿回答:
curl -sS -X POST "$BASE_URL/api/v1/integrations/sites/$SITE_ID/conversations/$CONVERSATION_ID/messages" \
-H "X-Integration-Api-Key: $DESKCLERK_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: msg-slack-12025550123-1" \
-d '{"content":"你们发货到加拿大吗?"}'
一段完整的 Node.js 处理代码,两个必须处理的分支都写了:
const BASE_URL = "https://chat.example.com";
const SITE_ID = "7b0c9f5e-2a8d-4f31-8e6b-1c2d3e4f5a6b";
const API_KEY = process.env.DESKCLERK_API_KEY;
async function reply(channel, externalUserId, locale, text) {
const convRes = await fetch(`${BASE_URL}/api/v1/integrations/sites/${SITE_ID}/conversations`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Integration-Api-Key": API_KEY,
"Idempotency-Key": `conv-${channel}-${externalUserId}-${Date.now()}`
},
body: JSON.stringify({
channel,
external_user_id: externalUserId,
locale,
timezone: "Asia/Shanghai"
})
});
const conv = await convRes.json();
if (conv.code !== 0) {
throw new Error(`create conversation failed: ${conv.code} ${conv.message}`);
}
const conversationId = conv.data.id;
const msgRes = await fetch(
`${BASE_URL}/api/v1/integrations/sites/${SITE_ID}/conversations/${conversationId}/messages`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Integration-Api-Key": API_KEY,
"Idempotency-Key": `msg-${conversationId}-${Date.now()}`
},
body: JSON.stringify({ content: text })
}
);
const msg = await msgRes.json();
if (msg.code !== 0) {
throw new Error(`send message failed: ${msg.code} ${msg.message}`);
}
// 这一轮没有 AI 回答:转到你们那边的人工。
if (!msg.data.aiMessage) {
return { conversationId, text: "稍后会有同事在这里回复您。", escalate: true };
}
// 该不该转人工,平台已经判好了。
const escalate = Boolean(msg.data.escalation);
return { conversationId, text: msg.data.aiMessage.content, escalate };
}
reply("slack", "12025550123", "zh", "你们发货到加拿大吗?").then(console.log);
发消息这个接口是同步的,最长可能要等 45 秒,所以你的 HTTP 客户端超时要设得比这长。要边生成边显示,就改用流式的那个接口 .../messages/stream,请求体一样。
6. 常见问题
-
报
403 Forbidden/code 40301。 钥匙没带、被停用,或者这把钥匙没有这个接口要的权限。先确认请求头名字就是X-Integration-Api-Key(或者Authorization: Bearer <key>)、钥匙状态是ACTIVE、这把钥匙的站点列表里有这个 Site ID。 -
报
429 Too Many Requests/code 42901。 请求太快,超过这个站点的频率上限。读响应里的Retry-After,等够秒数再退避重试,别在循环里死磕。 -
网页右下角没有出现客服气泡。 在浏览器里查看页面源代码,搜
widget.js:搜不到说明这段代码没进最终页面(缓存插件或主题设置把它丢了);搜得到就打开浏览器控制台看有没有 Content-Security-Policy 报错,需要把https://chat.example.com加进script-src和connect-src。广告拦截插件也会把气泡藏掉,换一个干净浏览器配置再试。 -
气泡出来了,但回答老是说不知道。 知识是空的、还在处理中,或者知识的语言标签跟访客提问的语言对不上。到工作台「站点 → 知识」里看:总条数要大于 0、待处理条数要是 0、语言标签要和访客提问一致。
-
建会话报
400,提示Locale must be enabled for this site's Help Center。locale必须填你这个站点已经开启的语言代码(例如en、zh),不能填en-US、zh-CN这种带地区的写法。到站点设置里看一眼语言列表,照那里显示的代码原样传。 -
aiMessage是null,但接口是成功的。 这不是报错:说明这一轮助手没有给出回答(比如这个会话的助手被暂停了)。显示你们自己的兜底话术,并把会话转给人工。 -
发消息报
404。 基本都是conversationId不对——要用你存下来的data.id(UUID),不是你自己的会话 ID。Site ID 写错也是同样的状态码。 -
sources[].score是什么? 0 到 1 的检索相似度,适合做「查看依据」的界面。它不是回答准确率。 -
要不要把聊天历史一起传过去? 不用。只传这一条新消息,平台按
conversationId自己记着上下文。
7. 出问题找谁
- 产品和配置问题:
[email protected] - 每次联系都请带上: 你的 Site ID、出问题的请求(方法和路径)、HTTP 状态码、响应体里的
code、以及你用的Idempotency-Key。有这几样我们就能在日志里把这笔请求找出来。 - 安全提醒: 别把 API 钥匙明文发给我们。给我们钥匙的前几位(前缀)就够我们定位了。