把 DeskClerk 的转人工事件接到你们的 Slack

把 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. 分步操作

  1. 先搞清楚平台自己做了什么:网页访客要求转人工时,这段会话会在你的 DeskClerk 工作台里被标记为转人工,站点所有者的邮箱会收到一封通知邮件。DeskClerk 不会往 Slack 发任何东西。
  2. 在你们的 Slack 里建一个 Incoming Webhook(Slack 管理员 → Apps → Incoming Webhooks),webhook 地址只放在你们服务器上。
  3. 在你们这一侧照第 5 节调发消息接口,检查返回里的 escalation 对象。
  4. escalation 出现时,把访客原话和 conversationId POST 到你们的 Slack webhook。
  5. 把 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 钥匙明文发给我们。给我们钥匙的前几位(前缀)就够我们定位了。