API Console
test
Sign in with BES

接入会话服务

会话服务识别来信号码、完成 hi 验证、检查开通,并把渠道身份和所选产品交给 AI service。

环境登录地址带证书调用的接口地址
localhttps://id.local.globalbes.sghttps://id.local.globalbes.sg:19443
testhttps://id.test.globalbes.sghttps://id.test.globalbes.sg:9443
staging待定待定
prod待定待定

Sign in to send requests to see which of these steps are done. Sign in

1. 拿到证书
1.1

生成私钥和证书请求

在哪里做:会话服务服务器

生成会话服务私钥和证书请求,把证书请求送到客户端 CA 签发。

CERT_DIR="$HOME/bes-certs/assistant"
mkdir -p "$CERT_DIR"
SERVICE_ACCOUNT_ID=$(uuidgen | tr A-Z a-z)
openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \
  -subj "/CN=Assistant" \
  -addext "subjectAltName=URI:urn:bes:service:$SERVICE_ACCOUNT_ID" \
  -addext "basicConstraints=critical,CA:FALSE" \
  -addext "keyUsage=critical,digitalSignature" \
  -addext "extendedKeyUsage=clientAuth" \
  -keyout "$CERT_DIR/assistant-service-key.pem" -out "$CERT_DIR/assistant-service.csr"
  • SERVICE_ACCOUNT_ID 每次新生成,写进证书唯一的 URI SAN urn:bes:service:<uuid>;登记平台账号后它就是账号的 service_account_id。
  • 私钥 assistant-service-key.pem 放进 Assistant 部署的密钥存储。

检查:openssl req -in "$HOME/bes-certs/assistant/assistant-service.csr" -noout -verify 成功;生成失败时核对目录写权限和 OpenSSL 版本。

本环境(test)

在该环境的会话服务服务器执行上面的命令,证书目录为 ~/bes-certs/assistant/。

其他环境

local

在该环境的会话服务服务器执行上面的命令,证书目录为 ~/bes-certs/assistant/。

staging

在该环境的会话服务服务器执行上面的命令,证书目录为 ~/bes-certs/assistant/。

prod

在该环境的会话服务服务器执行上面的命令,证书目录为 ~/bes-certs/assistant/。

核对结果

确认生成的证书请求、签发证书或账号输出对应同一个 service_account_id。

1.2

签发证书

在哪里做:客户端 CA 所在的机器

客户端 CA 签发会话服务证书,将证书安装到会话服务和 Control 登记路径。

前提:OpenSSL 3.0 及以上,openssl version 显示 OpenSSL 3.x。Mac 自带的 /usr/bin/openssl 是 LibreSSL,不认 -copy_extensions:先执行 brew install openssl@3,再执行 export PATH="$(brew --prefix openssl@3)/bin:$PATH"。

检查:在 CA 所在机器执行 openssl verify -CAfile "$CA_DIR/client-ca.pem" "$CERT_DIR/assistant-service.pem" 返回 OK;证书用途或有效期有误时重新签发。

本环境(test)

  • CA_DIR:待定,test 环境的客户端 CA 位置未定。
  • 签发前把 assistant-service.csr 拷到客户端 CA 所在机器的 ~/bes-certs/assistant/;签发后把 assistant-service.pem 拷回 Assistant 服务器的同一目录,和私钥放在一起。
CERT_DIR="$HOME/bes-certs/assistant"
mkdir -p "$CERT_DIR"
CA_DIR="待定"
openssl x509 -req -days 825 -sha256 -in "$CERT_DIR/assistant-service.csr" \
  -CA "$CA_DIR/client-ca.pem" -CAkey "$CA_DIR/client-ca-key.pem" \
  -CAserial "$CERT_DIR/assistant-service.srl" -CAcreateserial -copy_extensions copy \
  -out "$CERT_DIR/assistant-service.pem"
其他环境

local

本地的客户端 CA 在本机,证书请求不用拷,在本机执行:

CERT_DIR="$HOME/bes-certs/assistant"
mkdir -p "$CERT_DIR"
CA_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/bes/local/stack/secrets/control"
openssl x509 -req -days 825 -sha256 -in "$CERT_DIR/assistant-service.csr" \
  -CA "$CA_DIR/client-ca.pem" -CAkey "$CA_DIR/client-ca-key.pem" \
  -CAserial "$CERT_DIR/assistant-service.srl" -CAcreateserial -copy_extensions copy \
  -out "$CERT_DIR/assistant-service.pem"

staging

  • CA_DIR:待定,staging 环境的客户端 CA 位置未定。
  • 签发前把 assistant-service.csr 拷到客户端 CA 所在机器的 ~/bes-certs/assistant/;签发后把 assistant-service.pem 拷回 Assistant 服务器的同一目录,和私钥放在一起。
CERT_DIR="$HOME/bes-certs/assistant"
mkdir -p "$CERT_DIR"
CA_DIR="待定"
openssl x509 -req -days 825 -sha256 -in "$CERT_DIR/assistant-service.csr" \
  -CA "$CA_DIR/client-ca.pem" -CAkey "$CA_DIR/client-ca-key.pem" \
  -CAserial "$CERT_DIR/assistant-service.srl" -CAcreateserial -copy_extensions copy \
  -out "$CERT_DIR/assistant-service.pem"

prod

  • CA_DIR:待定,prod 环境的客户端 CA 位置未定。
  • 签发前把 assistant-service.csr 拷到客户端 CA 所在机器的 ~/bes-certs/assistant/;签发后把 assistant-service.pem 拷回 Assistant 服务器的同一目录,和私钥放在一起。
CERT_DIR="$HOME/bes-certs/assistant"
mkdir -p "$CERT_DIR"
CA_DIR="待定"
openssl x509 -req -days 825 -sha256 -in "$CERT_DIR/assistant-service.csr" \
  -CA "$CA_DIR/client-ca.pem" -CAkey "$CA_DIR/client-ca-key.pem" \
  -CAserial "$CERT_DIR/assistant-service.srl" -CAcreateserial -copy_extensions copy \
  -out "$CERT_DIR/assistant-service.pem"
核对结果

确认生成的证书请求、签发证书或账号输出对应同一个 service_account_id。

1.3

登记平台账号

在哪里做:Control 服务器

登记会话服务的平台账号,后续用该证书调用 Control。

账号角色为 conversation-gateway,命令输出应包含 role=conversation-gateway;重复执行保留同一账号。显示名使用 Assistant。

本环境(test)

  • 在 Control 服务器上执行 bes-control 的方式:待定,test 环境的 Control 部署方式未定。
  • assistant-service.pem 拷到 Control 服务器上的路径:待定。
bes-control seed-trusted-caller --role conversation-gateway --certificate "待定/assistant-service.pem" --display-name Assistant
其他环境

local

本地 Control 在容器 control-api 里,先把证书拷进 ~/.local/share/bes/local/stack/secrets/control/,每次执行时它被复制成容器里的 /run/bes/control/assistant-service.pem。在本机执行:

BES_INFRA="$HOME/Projects/bes-infra"   # bes-infra 仓的路径
cp "$HOME/bes-certs/assistant/assistant-service.pem" "${XDG_DATA_HOME:-$HOME/.local/share}/bes/local/stack/secrets/control/assistant-service.pem"
"$BES_INFRA/envs/local/dev" stack compose exec -T control-api /bin/sh /opt/bes/stack-entrypoint.sh \
  bes-control seed-trusted-caller --role conversation-gateway --certificate /run/bes/control/assistant-service.pem --display-name Assistant

staging

  • 在 Control 服务器上执行 bes-control 的方式:待定,staging 环境的 Control 部署方式未定。
  • assistant-service.pem 拷到 Control 服务器上的路径:待定。
bes-control seed-trusted-caller --role conversation-gateway --certificate "待定/assistant-service.pem" --display-name Assistant

prod

  • 在 Control 服务器上执行 bes-control 的方式:待定,prod 环境的 Control 部署方式未定。
  • assistant-service.pem 拷到 Control 服务器上的路径:待定。
bes-control seed-trusted-caller --role conversation-gateway --certificate "待定/assistant-service.pem" --display-name Assistant --confirm-production
核对结果

确认生成的证书请求、签发证书或账号输出对应同一个 service_account_id。

做完这一部分,手里有:取得会话服务证书,并登记持有 conversation-gateway 角色的平台账号。

2. 处理一对一会话
2.1

查来信地址属于谁

在哪里做:会话服务代码

按来信 WhatsApp 号码查人,取得 verified_at 和可选的产品、租户、项目。

GETIdentity Resolve

程序里发这个请求(id 用前面步骤返回的):

带:证书

请求:

GET https://id.test.globalbes.sg:9443/internal/control/v1/identities/resolve?channel_type=whatsapp&channel_identifier=6591234567

返回:

HTTP/1.1 200 OK

{
  "found": true,
  "verified_at": null,
  "person_id": "5a9d3c1b-7e24-4f60-b8a1-c03e6d2f9475",
  "contexts": [
    {
      "product_id": "b41d9a70-6c2e-4a0f-8e7d-52a9c3f0d614",
      "tenant_id": "7c0e2a5e-3b1f-4d6a-9a53-1f2f6d0c8a11",
      "project_id": "e2f7a1c4-0d35-4b8e-a6c9-7d18f4b2a903",
      "tenant_name": "Acme Construction",
      "project_name": "Harbour Tower"
    }
  ]
}

录入成员号码时使用 whatsapp 类型、纯数字不带加号,例如 6591234567。收到 Messaging 已验 Meta 签名的一对一消息后,用同一规范号码调用。found: false 时提示登记地址;found: true 时保存 person_id 和 contexts,供后续选择产品范围。

本环境(test)

Control 网关:https://id.test.globalbes.sg:9443;证书目录 ~/bes-certs/assistant/。

其他环境

local

Control 网关:https://id.local.globalbes.sg:19443;证书目录 ~/bes-certs/assistant/。

staging

Control 网关:待定;证书目录 ~/bes-certs/assistant/。

prod

Control 网关:待定;证书目录 ~/bes-certs/assistant/。

错误:

HTTP 错误码 处理
403 ACCESS_DENIED 核对平台账号的 conversation-gateway 角色。
请求示例
curl --fail-with-body --cert "$HOME/bes-certs/assistant/assistant-service.pem" --key "$HOME/bes-certs/assistant/assistant-service-key.pem" --get "https://id.test.globalbes.sg:9443/internal/control/v1/identities/resolve" --data-urlencode 'channel_type=whatsapp' --data-urlencode 'channel_identifier=6591234567'
2.2

完成 hi 验证

在哪里做:会话服务代码

verified_at 为空时请对方回复 hi,收到回复后把入站消息证明交给 Control。

POSTProve inbound channel possession

程序里发这个请求(id 用前面步骤返回的):

带:证书

请求:

POST https://id.test.globalbes.sg:9443/internal/control/v1/channel-identities/prove-inbound
Content-Type: application/json

{
  "request_ref": "inbound-wamid-001",
  "channel_type": "whatsapp",
  "channel_identifier": "6591234567",
  "provider_message_id": "wamid.inbound-001",
  "received_at": "2026-10-06T08:00:00+00:00",
  "conversation": "direct"
}

返回:

HTTP/1.1 200 OK

{
  "channel_identity_id": "6b1f4e9a-8c30-4d72-a5e6-f09c3b7d1e24",
  "verified_at": "2026-10-06T08:00:01+00:00",
  "newly_verified": true
}

verified_at 为空时,通过 Messaging 向该号码发 hi 提示;等待官方 WhatsApp 一对一回复,再传该回复的 provider_message_id 和 received_at。以消息标识生成稳定 request_ref,网络失败时原样重试。成功后保存 verified_at;newly_verified 为 false 同样继续。入站来源证明号码持有,可用于对话和委托;本人完成验证码或首次设密后来源升级为本人完成。 检查结果:再次认人时 verified_at 非空;同一 request_ref 重试返回首次结果。

本环境(test)

Control 网关:https://id.test.globalbes.sg:9443;证书目录 ~/bes-certs/assistant/。

其他环境

local

Control 网关:https://id.local.globalbes.sg:19443;证书目录 ~/bes-certs/assistant/。

staging

Control 网关:待定;证书目录 ~/bes-certs/assistant/。

prod

Control 网关:待定;证书目录 ~/bes-certs/assistant/。

错误:

HTTP 错误码 处理
404 NOT_FOUND 重新认人,确认号码已有 active 绑定且人员 active。
400 INVALID_REQUEST 核对 whatsapp、direct、号码和带时区的 received_at。
409 IDEMPOTENCY_CONFLICT 同一 request_ref 重试保留原参数;不同来信使用不同 request_ref。
请求示例
curl --fail-with-body --cert "$HOME/bes-certs/assistant/assistant-service.pem" --key "$HOME/bes-certs/assistant/assistant-service-key.pem" -H "Content-Type: application/json" "https://id.test.globalbes.sg:9443/internal/control/v1/channel-identities/prove-inbound" --data '{"request_ref": "inbound-wamid-001", "channel_type": "whatsapp", "channel_identifier": "6591234567", "provider_message_id": "wamid.inbound-001", "received_at": "2026-10-06T08:00:00+00:00", "conversation": "direct"}'
2.3

查开通

在哪里做:会话服务代码

按所选租户、项目检查 ai-service 和 whatsapp-channel 的有效授权,取得可用期限。

GETEntitlement Resolve

程序里发这个请求(id 用前面步骤返回的):

带:证书

请求:

GET https://id.test.globalbes.sg:9443/internal/control/v1/entitlements/resolve?tenant_id=7c0e2a5e-3b1f-4d6a-9a53-1f2f6d0c8a11&project_id=e2f7a1c4-0d35-4b8e-a6c9-7d18f4b2a903&product_id=b41d9a70-6c2e-4a0f-8e7d-52a9c3f0d614

返回:

HTTP/1.1 200 OK

null

从环境产品配置取得 ai-service 和 whatsapp-channel 各自的 product_id(值待定),分别查询。样例 product_id 替换为对应产品的 ID。两项结果都为有效记录时继续;返回 null 时提示开通。缓存记录的 ends_at,并在到期后重新查询。租户级产品省略 project_id。

本环境(test)

Control 网关:https://id.test.globalbes.sg:9443;证书目录 ~/bes-certs/assistant/。

其他环境

local

Control 网关:https://id.local.globalbes.sg:19443;证书目录 ~/bes-certs/assistant/。

staging

Control 网关:待定;证书目录 ~/bes-certs/assistant/。

prod

Control 网关:待定;证书目录 ~/bes-certs/assistant/。

错误:

HTTP 错误码 处理
404 PRODUCT_NOT_FOUND 核对环境中登记的产品 ID。
403 ACCESS_DENIED 核对平台账号的授权解析权限。
检查两项开通
AI_SERVICE_PRODUCT_ID="待定"
WHATSAPP_CHANNEL_PRODUCT_ID="待定"
TENANT_ID="7c0e2a5e-3b1f-4d6a-9a53-1f2f6d0c8a11"
PROJECT_ID="e2f7a1c4-0d35-4b8e-a6c9-7d18f4b2a903"
for PRODUCT_ID in "$AI_SERVICE_PRODUCT_ID" "$WHATSAPP_CHANNEL_PRODUCT_ID"; do
  curl --fail-with-body --cert "$HOME/bes-certs/assistant/assistant-service.pem" \
    --key "$HOME/bes-certs/assistant/assistant-service-key.pem" \
    --get "https://id.test.globalbes.sg:9443/internal/control/v1/entitlements/resolve" \
    --data-urlencode "tenant_id=$TENANT_ID" --data-urlencode "project_id=$PROJECT_ID" \
    --data-urlencode "product_id=$PRODUCT_ID"
done
2.4

交给 AI service

在哪里做:会话服务代码

把规范渠道身份、消息和确认的产品、租户、项目交给 AI service,由 AI 换委托令牌调用产品。

用 BES_ASSISTANT_AI_URL 配置的 AI mTLS 地址调用 POST /internal/ai/v1/conversation-turns。 按 conversation.submit 传 turn_ref、surface、subject、context、session_id、messages、supersedes_turn_ref、trace_id; 渠道 subject 使用 kind=channel_identity、channel_type=whatsapp、identifier 与 direct/group surface; context 是已确认的 product_id、tenant_id、project_id。多项上下文先由会话服务筛选和选择。 不把用户登录令牌、person_id 或 verified_at 当成该请求的额外字段。 相同 turn_ref 重试保留业务参数,响应 ai_turn_id、session_id、replayed;异参返回 CONFLICT.IDEMPOTENCY_KEY。 打断调用 POST /internal/ai/v1/conversation-turns/{turn_ref}/abort;AI 向登记的 POST /internal/assistant/v1/turn-results 回调,completed 带 reply、failed 带 failure。 挂件使用 widget_turn subject,AI 经 turn-token.issue 换取当前回合的窄 scope 委托令牌。

本环境(test)

AI 地址取部署配置 BES_ASSISTANT_AI_URL;真实服务就绪后验收。

其他环境

local

AI 地址取 BES_ASSISTANT_AI_URL,本机可使用 README 中的 AI 替身;回调经 Assistant mTLS 入口。

staging

AI 地址取部署配置 BES_ASSISTANT_AI_URL。

prod

AI 地址取部署配置 BES_ASSISTANT_AI_URL。

2.5

接收事件并清缓存

在哪里做:会话服务代码

接收 Control 事件,持久化去重即返回 2xx,再本地应用与重试。

按事件契约验证签名与 audience,使用 event_id 去重,持久化即 2xx,应用失败本地重试;发送方仅在未持久化时重试投递。

  • channel_access.revoked:丢掉该 channel_identity_id 的地址、认人和会话身份缓存;按 person_id 清除关联范围。
  • person.status_changed:丢掉该 person_id 的地址、认人、授权和会话身份缓存。
  • entitlement.changed:丢掉对应产品、租户、项目的开通结果和包含该范围的 contexts。
  • channel_identity.verified:丢掉该渠道的未验证认人结果和 hi 等待状态,重新解析 verified_at。
  • membership.revoked:丢掉该 person_id 在该 tenant_id 下的 contexts 和当前会话所选范围。
  • role_assignment.changed:丢掉该 person_id 对应租户、项目、产品的 contexts 和权限缓存。

本环境(test)

在 Control 的 BES_CONTROL_EVENT_CONSUMERS 中保留其他消费者,并登记本服务:HTTPS endpoint 待定,audience 为 assistant-api,event_types 使用上面的列表。接收地址部署方式待定。验收时逐种送达事件,确认相应缓存被清除。

其他环境

local

在 Control 的 BES_CONTROL_EVENT_CONSUMERS 中保留其他消费者,并登记本服务:HTTPS endpoint 待定,audience 为 assistant-api,event_types 使用上面的列表。接收地址部署方式待定。验收时逐种送达事件,确认相应缓存被清除。

staging

在 Control 的 BES_CONTROL_EVENT_CONSUMERS 中保留其他消费者,并登记本服务:HTTPS endpoint 待定,audience 为 assistant-api,event_types 使用上面的列表。接收地址部署方式待定。验收时逐种送达事件,确认相应缓存被清除。

prod

在 Control 的 BES_CONTROL_EVENT_CONSUMERS 中保留其他消费者,并登记本服务:HTTPS endpoint 待定,audience 为 assistant-api,event_types 使用上面的列表。接收地址部署方式待定。验收时逐种送达事件,确认相应缓存被清除。

订阅配置
{
  "assistant": {
    "endpoint": "待定",
    "audience": "assistant-api",
    "event_types": [
      "channel_access.revoked",
      "person.status_changed",
      "entitlement.changed",
      "channel_identity.verified",
      "membership.revoked",
      "role_assignment.changed"
    ]
  }
}

做完这一部分,手里有:取得可用于 AI 委托的渠道身份和已确认范围,并保持缓存与 Control 状态一致。

3. 身份上下文名称
3.1

展示上下文名称

在哪里做:会话服务代码

显示 Control 返回的租户与项目名称;租户级项目名为 null。

身份上下文的显示名称来自 identity.resolve:每项包含 tenant_name 与 project_name;租户级 project_name 为 null。示例:{product_id: "<产品 UUID>", tenant_id: "<租户 UUID>", tenant_name: "Acme Construction", project_name: null}。 挂件使用独立不可售卖的 Assistant 产品。机密客户端登记 assistant.widget scope;授权请求带 host_product_id 与 scope=openid assistant.widget,回调取环境配置,本机为 https://assistant.local.globalbes.sg/widget/callback。 prompt=none 缺少 SSO 或有效宿主登录时返回 login_required;先在弹窗登录宿主,再重试挂件静默授权。无需 Assistant 成员关系或开通,也不开放登录页 iframe。 换码事务将 sid 绑定到同一 SSO 的宿主登录及 generation。Assistant 使用服务端令牌和自己的 mTLS 身份调用 tenant-list(product_id=宿主)、validate(product_id=宿主);两处都读取绑定。 validate 的 target 使用产品目录 assistant.chat 对应的 method/path/deployment_version,并携带选定 tenant_id/project_id。挂件本身不缓存结果。 委托交换以用户访问令牌为 subject、宿主产品 UUID 为 audience;act.sub 为 Assistant 服务账号 UUID。产品需登记唯一有效的服务客户端,其 scopes 与用户当前权限限定委托范围。 宿主退出或全 SSO 退出后失败;刷新不改绑定。宿主重新登录后,旧挂件仍失败,必须重新走挂件授权和换码。

做完这一部分,手里有:按 Control 当前上下文展示租户与项目名称。

其他功能
接入 AI service接入 Messaging