鉴权与签名
接入方调用开放接口使用 AppKey / AppSecret,采用 HMAC-SHA256 签名。不依赖控制台登录态或浏览器 Cookie。
请求头
| 请求头 | 说明 |
|---|---|
OC-App-Key | 当前工作空间的 AppKey |
OC-Timestamp | Unix 毫秒时间戳,与平台时间相差不超过 5 分钟 |
OC-Nonce | 每次请求生成新的随机串,16~64 位,允许字母、数字、-、_ |
OC-Signature | v1= 加 HMAC-SHA256 的小写十六进制结果 |
X-Request-ID | 可选,请求排查标识,最长 64 位 |
AppSecret 只放在接入方服务端,不进入网页、移动端安装包或模型上下文。
签名内容
使用换行符连接以下七行,末尾不加换行:
text
OC-HMAC-SHA256
{大写 HTTP 方法}
{原始请求路径}
{原始查询字符串,不含问号}
{OC-Timestamp}
{OC-Nonce}
{请求体 SHA-256,小写十六进制}没有查询字符串时,第四行保留为空。路径和查询字符串按实际发送内容计算,不额外排序或解码。没有请求体时,对空字节串求摘要。
签名自检
当前已有 GET /api/open/v1/auth/check 和 POST /api/open/v1/auth/check,可用于确认签名链路。开放接口域名由平台在联调时提供。
下面的 Node.js 示例只调用自检接口,不推送业务消息:
js
import { createHash, createHmac, randomBytes } from "node:crypto";
const { OMNICHAT_BASE_URL, OMNICHAT_APP_KEY, OMNICHAT_APP_SECRET } =
process.env;
if (!OMNICHAT_BASE_URL || !OMNICHAT_APP_KEY || !OMNICHAT_APP_SECRET) {
throw new Error("请在服务端配置域名、AppKey 与 AppSecret");
}
const url = new URL("/api/open/v1/auth/check", OMNICHAT_BASE_URL);
if (url.protocol !== "https:") throw new Error("开放接口须使用 HTTPS");
const body = JSON.stringify({ ping: "hello" });
const timestamp = String(Date.now());
const nonce = randomBytes(16).toString("hex");
const digest = createHash("sha256").update(body, "utf8").digest("hex");
const canonical = [
"OC-HMAC-SHA256",
"POST",
url.pathname,
url.search.slice(1),
timestamp,
nonce,
digest,
].join("\n");
const signature = createHmac("sha256", OMNICHAT_APP_SECRET)
.update(canonical, "utf8")
.digest("hex");
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"OC-App-Key": OMNICHAT_APP_KEY,
"OC-Timestamp": timestamp,
"OC-Nonce": nonce,
"OC-Signature": `v1=${signature}`,
},
body,
signal: AbortSignal.timeout(10000),
});
console.log(response.status, await response.text());常见排查方向
- 签名不匹配:检查发送字节是否和计算摘要的请求体相同。
- 请求过期:确认服务器时钟和毫秒时间戳。
- 随机数重复:每次重试更换 nonce,并重新签名。
- 空间不正确:确认使用的凭证属于目标空间,不通过请求体切换空间。
消息回调使用独立密钥,签名格式见消息回调。
错误与限流
业务接口均为 POST JSON;签名自检另支持 GET。请求体最大 1 MB,默认每个 AppKey 每个自然分钟 600 次请求,部署可调。成功响应为 {"code":"OK","message":"success","data":{}};错误如下:
json
{ "code": "CONFLICT", "message": "请检查目前资源状态", "data": null }| HTTP | code | 处理方式 |
|---|---|---|
| 400 | INVALID_ARGUMENT | 修正字段或配置 |
| 401 | AUTH_REQUIRED / INVALID_CREDENTIALS | 检查凭证及签名 |
| 401 | REQUEST_EXPIRED | 校时并重新签名;OC-Server-Time 返回平台毫秒时间 |
| 401 | REQUEST_REPLAYED | 更换 nonce,重新签名;保留业务 eventId |
| 403 | CREDENTIAL_DISABLED / FORBIDDEN | 检查空间、凭证状态及 IP 白名单 |
| 404 | NOT_FOUND | 先同步用户或核对角色识别 |
| 409 | CONFLICT / VERSION_CONFLICT | 查询目前数据,再决定修改或重试 |
| 413 | PAYLOAD_TOO_LARGE | 缩小请求体 |
| 429 | RATE_LIMITED | 退避后重新签名重试 |
| 503 | SERVICE_UNAVAILABLE | 暂时不可用,稍后重试 |
网络超时不代表请求未执行。聊天事件重试保持 eventId;角色创建/发布先查询确认结果,不要盲目重试。X-Request-ID 用于追踪,不提供业务去重。
可下载 Node.js 请求签名与回调验签示例,用法见快速开始。