Chat 平台 API 集成指南

您可以使用本指南通过 Apps API 构建服务器端聊天集成。完成本教程后,您的集成将能够:

  • 向 Apps API 进行身份验证。

  • 创建或更新最终用户。

  • 为该最终用户发起聊天。

  • 接收并验证来自 Contact Center AI 平台 的网络钩子事件。

  • 在聊天中发送文本消息。

  • 处理可选分支,例如预聊天记录导入、队列选择虚拟代理路由、升级分流和媒体附件。

  • 在对话结束后结束聊天。

本指南面向正在构建后端服务的开发者,该服务可将客户自有的聊天体验连接到 CCAI Platform。它假设您可以在 CCAI 平台中创建 API 凭据、托管 HTTPS webhook 端点、安全地存储密钥,以及从服务器发出 HTTP 请求。

本指南是对 Apps API Chat 端点的补充。请参阅 API 参考文档,了解详尽的请求和响应架构;并参阅本指南,了解建议的端到端实现流程。

术语

以下定义适用于本文档:

  • 客户:在自己的软件中实现聊天集成的 CCAI 平台客户。

  • 使用方:客户自有服务器端应用,用于向 Apps API 发出请求并接收 CCAI 平台 webhook 事件。

  • 最终用户:使用客户的软件开始或继续与客服或虚拟客服对话的人员。

  • 聊天:Apps API 创建的 CCAI 平台对话资源。

  • Webhook 端点:使用方应用中用于接收来自 CCAI 平台的聊天事件的 HTTPS 端点。

准备工作

在开始之前,请确保满足以下条件:

  • 应用 API 凭据

    • 在 CCAI 平台中创建 API 凭据,依次选择设置 > 开发者设置 > API 凭据

    • 安全地存储凭据 Secret。请勿在浏览器或移动客户端代码中公开该密钥。

  • 租户网址详细信息

    • 确定您的 CCAI Platform 子网域和网域。

    • Apps API 的基本网址为: https://YOUR_SUBDOMAIN.YOUR_DOMAIN/apps/api/v1

  • 网络钩子端点

    • 托管一个可接收来自 CCAI Platform 的 POST 请求的公共 HTTPS 端点。

    • 在 CCAI 平台开发者设置中配置端点。

    • 生成并存储 Webhook 主密钥和辅助密钥。

  • 队列或菜单配置

    • 确定新对话进入的队列或菜单。

    • 如果您使用队列选择虚拟客服,请先配置该虚拟客服并将其分配给入口队列,然后再通过 API 创建对话。

  • 最终用户身份

    • 确定您的系统将为每位最终用户使用哪个稳定标识符。

    • 存储 Apps API 返回的 CCAI Platform 最终用户 ID。

  • 速率限制处理

    • CCAI Platform 会对 Apps API 进行速率限制。在集成中构建重试和退避机制,并避免为单个租户发送大量请求。

身份验证和 Webhook 安全性

您的集成使用两条身份验证路径:

  • 针对从您的服务器向 CCAI Platform 发出的请求进行 Apps API 身份验证。

  • 针对从 CCAI Platform 发送到您服务器的请求进行网络钩子签名验证。

对 Apps API 请求进行身份验证

请求使用 HTTP 基本身份验证。在 CCAI 平台中,依次点击设置 > 开发者设置 > API 凭据,创建 API 令牌,并在 password 字段中传递该令牌(推荐)。如果您的租户使用旧版身份验证路径,则可以改为将公司密钥作为用户名传递,并将公司密钥作为密码传递。如需了解完整的身份验证设置,请参阅 Apps API 参考文档。以下示例演示了如何使用基本身份验证对 Apps API 请求进行身份验证:

curl -X GET \
  https://YOUR_SUBDOMAIN.YOUR_DOMAIN/apps/api/v1/chats/{chat_id} \
  -u "YOUR_SUBDOMAIN:YOUR_API_TOKEN" \
  -H "Accept: application/json"

将凭据存储在服务器端密钥存储区中,根据您的安全政策轮换这些凭据,并且绝不在浏览器或移动应用中提供这些凭据。

验证 webhook 请求

CCAI 平台会将聊天事件发送到您的网络钩子端点。每个网络钩子请求都包含:

  • X-Signature

  • X-Signature-Timestamp

X-Signature 标头可以包含主签名、辅助签名或同时包含两者:

primary=<primary_signature> secondary=<secondary_signature>

每个签名都是经过 Base64 编码的 HMAC-SHA256 摘要。签名值是时间戳标头与原始 JSON 请求正文的串联:

X-Signature-Timestamp + raw_request_body

在您的网络钩子处理程序中:

  1. 请参阅 X-SignatureX-Signature-Timestamp

  2. 如果缺少任一标头,则拒绝相应请求。

  3. 拒绝过时的时间戳,以降低重放风险。

  4. 在解析 JSON 之前读取原始请求正文。

  5. 使用每个有效的 Webhook Secret 计算预期签名。

  6. 使用恒定时间比较来比较收到的签名和预期签名。

  7. 如果任何有效密钥匹配,则接受请求。

以下 Ruby 实现示例演示了如何验证 UJET Webhook 签名:

require "base64"
require "openssl"
require "active_support/security_utils"

def parse_ujet_signature(header)
  header.to_s.split(/\s+/).each_with_object({}) do |part, result|
    key, value = part.split("=", 2)
    result[key] = value if key && value
  end
end

def expected_signature(secret, timestamp, raw_body)
  Base64.strict_encode64(
    OpenSSL::HMAC.digest(
      OpenSSL::Digest.new("sha256"),
      secret,
      "#{timestamp}#{raw_body}"
    )
  )
end

def secure_match?(received, expected)
  return false if received.nil? || expected.nil?
  return false unless received.bytesize == expected.bytesize

  ActiveSupport::SecurityUtils.secure_compare(received, expected)
end

def verify_ujet_webhook!(request, primary_secret:, secondary_secret:)
  signature_header = request.headers["X-Signature"]
  timestamp = request.headers["X-Signature-Timestamp"]

  return false if signature_header.nil? || timestamp.nil?

  # Optional but recommended: reject stale requests.
  return false if (Time.now.utc - Time.at(timestamp.to_i).utc).abs > 5.minutes

  raw_body = request.body.read
  signatures = parse_ujet_signature(signature_header)

  expected = [
    expected_signature(primary_secret, timestamp, raw_body),
    expected_signature(secondary_secret, timestamp, raw_body)
  ].compact

  received = [
    signatures["primary"],
    signatures["secondary"]
  ].compact

  received.any? do |received_signature|
    expected.any? do |expected_signature_value|
      secure_match?(received_signature, expected_signature_value)
    end
  end
end

如果验证成功,请快速返回成功响应,并以幂等方式处理事件。Webhook 传送和 API 响应可能会以不同的顺序到达,因此请构建集成,使其能够容忍多次收到相同的状态更改,而不会创建重复记录。

集成流程

以下流程会创建最终用户、发起聊天、接收 CCAI 平台事件、交换消息并结束聊天。

创建或更新最终用户

目标:确保在创建对话之前,CCAI Platform 具有最终用户记录。

端点

使用以下端点创建或更新最终用户:

POST /apps/api/v1/end_users

示例请求

以下示例展示了用于创建或更新最终用户的请求正文:

{
  "identifier": "customer-user-12345",
  "email": "customer.user@example.com",
  "name": "Customer User",
  "phone": "+15551234567"
}

存储内容

将响应中的 CCAI Platform 最终用户 ID 存储在您的系统中。创建对话时,请使用该 ID。

预期用途

  • 如果最终用户不存在,CCAI Platform 会创建一条新记录。

  • 如果已存在具有相同标识符的最终用户,CCAI Platform 会更新相应记录并返回现有最终用户的信息。

创建聊天

目标:为最终用户启动新的 CCAI Platform 对话。

端点

使用以下端点开始新对话:

POST /apps/api/v1/chats

示例请求

以下示例展示了用于创建对话的请求正文:

{
  "chat": {
    "menu_id": 123,
    "end_user_id": 456,
    "lang": "en"
  }
}

虚拟客服路由的可选上下文

如果您的队列选择虚拟客服需要来自应用的上下文,请在创建对话时添加上下文载荷,如以下示例所示:

{
  "chat": {
    "menu_id": 123,
    "end_user_id": 456,
    "lang": "en",
    "context": {
      "value": {
        "customer_tier": "gold",
        "issue_type": "billing"
      }
    }
  }
}

虚拟客服可以使用该上下文中的值来决定哪个队列接收聊天。

预期用途

  • Apps API 会返回聊天资源。

  • CCAI 平台会向您配置的网络钩子端点发送 chat_created 网络钩子事件。

  • API 响应和 webhook 事件可以按任意顺序到达。将两者都视为同一聊天记录(以聊天 ID 为键)的更新。

处理聊天 webhook 事件

目标:使消费类应用与 CCAI Platform 对话状态保持同步。

您的网络钩子端点处理来自 CCAI 平台的聊天生命周期和消息事件。至少存储:

  • 聊天 ID。

  • 活动类型。

  • 事件时间戳。

  • 如果事件包含消息,则为消息发送者、消息类型和消息内容。

  • 当事件描述的是路由行为时,任何升级或分流数据。

建议的行为

  • 在处理事件之前,验证每个 Webhook 签名。

  • 存储已处理的事件 ID 或确定性事件键,以避免重试创建重复项。

  • 在接受事件后返回 2xx 响应。

  • 尽可能异步处理下游副作用。

预期用途

当 CCAI 平台发送聊天创建、传入消息、客服消息、升级变更和聊天完成等事件时,您的应用会更新其聊天状态。

发送短信

目标:从消费类应用向 CCAI Platform 对话发送最终用户消息。

端点

使用以下端点将文本消息发送到聊天中:

POST /apps/api/v1/chats/{chat_id}/message

示例请求

以下示例展示了用于发送短信的请求正文:

{
  "from_user_id": 456,
  "message": {
    "type": "text",
    "content": "Hello, I need help with my order."
  }
}

预期用途

  • CCAI 平台接受消息。

  • 该消息会显示在客服人员或虚拟客服人员的对话中。

  • 您的 webhook 端点会收到相应消息的消息事件,包括您自己的应用通过 Apps API 发送的消息。

接收并显示来自 CCAI 平台的消息

目标:在客户拥有的聊天体验中显示客服或虚拟客服消息。

当网络钩子端点收到消息事件时:

  1. 验证 webhook 签名。

  2. 检查活动是否为新活动。

  3. 通过聊天 ID 识别聊天。

  4. 标识发件人和消息类型。

  5. 在客户拥有的聊天界面中呈现消息。

  6. 持久保存事件,以便刷新或重试不会丢失对话历史记录。

预期用途

客户自有聊天界面会按正确的顺序显示客服人员、虚拟客服和最终用户发送的消息。如果事件到达顺序不正确,请使用事件时间戳和您自己的持久层来协调展示顺序。

从虚拟客服升级到人工客服

目标:当最终用户需要客服帮助时,将聊天从虚拟客服处理转为人工客服队列。

如果您的集成使用队列选择虚拟客服,请将虚拟客服配置为将对话转接到目标队列。如果您的服务器直接发起升级,请使用 Apps API 升级端点。

端点

使用以下端点将聊天从虚拟客服上报给人工客服:

POST /apps/api/v1/chats/{chat_id}/escalations

示例请求

以下示例展示了用于升级对话的请求正文:

{
  "reason": "by_end_user_ask",
  "force_escalate": false
}

预期用途

  • 如果目标队列可用,聊天会移向代理处理。

  • 如果队列因非工作时间或容量过大而无法使用,CCAI Platform 可以通过对话流程返回或发送分流选项。

  • 您的集成会向最终用户呈现可用的分流选项。

记录升级转移选择

目标:告知 CCAI Platform 最终用户选择了哪个分流选项。

当 CCAI 平台提供升级分流选项时,请使用升级更新端点记录最终用户的选择。

端点

使用以下端点更新包含分流选项的升级记录:

PATCH /apps/api/v1/chats/{chat_id}/escalations/{escalation_id}

支持的 deflection_channel

  • email - 最终用户选择电子邮件改道选项。

  • virtual_agent - 最终用户选择继续与虚拟客服对话。

  • human_agent - 最终用户选择继续等待人工客服。此值仅适用于超容量分流。

示例请求

以下示例展示了用于记录转接选择的请求正文:

{
  "deflection_channel": "email"
}

仅向此端点发送受支持的 deflection_channel 值。 external_link 不是升级更新端点的有效值;当最终用户点击外部分流链接时,聊天会结束。

预期用途

CCAI Platform 会根据所选选项更新升级记录并转换对话。

结束聊天

目标:在对话结束后关闭聊天。

端点

使用以下端点结束正在进行的对话:

PATCH /apps/api/v1/chats/{chat_id}/end

示例请求

以下示例展示了用于结束对话的请求正文:

{
  "ended_by_user_id": 456
}

预期用途

  • CCAI Platform 结束聊天。

  • 您的网络钩子端点会收到最终的聊天状态事件。

  • 应用将相应对话标记为已完成,并停止接受该对话的新最终用户消息。

高级流程

以下分支是可选的。仅实现适用于您的集成方案的流程。

导入预聊天转写

如果最终用户在您创建 CCAI 平台聊天之前已在您的系统中进行过对话(例如聊天机器人对话),请使用此流程。

在创建聊天时添加转写载荷。转写内容可为智能体提供上下文,这样最终用户就不必重复提供信息。

Apps API 参考文档包含确切的转写架构。

通过队列选择虚拟客服转接对话

如果您的应用将所有新对话发送到入口队列,并让虚拟客服决定最终的目标队列,请使用此流程。

  1. 创建用于选择队列的虚拟客服。

  2. 将虚拟客服分配给入口队列。

  3. 创建对话时包含上下文。

  4. 配置虚拟客服,使其检查上下文并将对话升级到正确的队列。

  5. 如果目标队列不可用,则处理转接选项。

发送照片或视频附件

当最终用户通过客户自有聊天界面发送媒体时,使用此流程。

媒体流包含四个阶段。

阶段 1 - 请求预签名上传网址

使用以下端点请求用于上传照片或视频的预签名网址:

POST /apps/api/v1/chats/{chat_id}/photos/upload
POST /apps/api/v1/chats/{chat_id}/videos/upload

阶段 2 - 将文件上传到返回的存储网址

包含文件以及 CCAI 平台在预签名上传响应中返回的任何字段。

第 3 阶段 - 将上传的文件添加到对话中

使用以下端点将上传的照片或视频添加到聊天中:

POST /apps/api/v1/chats/{chat_id}/photos
POST /apps/api/v1/chats/{chat_id}/videos

存储 CCAI 平台返回的 media_id。Chat 消息载荷通过媒体 ID 引用媒体。

第 4 步 - 将媒体作为消息发送

使用以下端点将媒体消息发送到聊天中:

POST /apps/api/v1/chats/{chat_id}/message

示例请求

以下示例展示了用于发送照片附件的请求正文:

{
  "from_user_id": 456,
  "message": {
    "type": "photo",
    "content": {
      "media_id": 789
    }
  }
}

对于视频消息,请使用 video 消息类型和视频 media_id

在聊天期间发送自定义数据

如果您的集成需要将客户定义的上下文附加到活跃对话,请使用以下端点:

POST /apps/api/v1/chats/{chat_id}/custom_data

Apps API 参考文档定义了确切的载荷形状和预留键行为。

在对话期间更新最终用户身份

当最终用户的身份在聊天开始后发生变化或变得已知时,请使用以下端点:

POST /apps/api/v1/chats/{chat_id}/end_user

例如,当匿名最终用户在活跃聊天期间登录时,如果您的集成需要 CCAI Platform 将聊天与更新后的最终用户身份相关联,则可以使用此端点。

收集客户满意度 (CSAT) 或评分数据

如果您的集成负责提供聊天后评分体验,请使用以下聊天 CSAT 和评分端点:

GET /apps/api/v1/chats/{chat_id}/csat
GET /apps/api/v1/chats/{chat_id}/rating
PATCH /apps/api/v1/chats/{chat_id}/rating

如需了解确切的资格条件和评分载荷,请参阅 Apps API 参考文档。