提交机器人申请
登录用户提交创建申请,等待 Admin 审核。
- ·必填:`name` (<=100)
- ·可选:`username` (<=64,全局唯一)、`description`、`avatar`、`enterprise_id`、`scopes[]`
- ·默认 `scopes = ["messages:send", "messages:receive"]`
- ·返回:机器人对象(`review_status=pending`)
机器人开放平台 · 开发者文档
使用 Bot Token 调用 REST API、接收 Webhook Update。P1 已扩展到多媒体消息(图片 / 文件 / 视频 / 语音 / 位置)、消息编辑与删除、双层速率配额、投递监控与死信重投;心智对齐 Telegram Bot API,并强化企业租户与审计。
Authorization: Bearer sbot_<token> 当前版本: P1 · v0.9SoChat 开放平台让企业在自有基础设施中部署机器人服务,平台负责身份、路由、投递与安全边界,业务逻辑仍由客户掌控。
任何符合条件的用户都可提交机器人创建申请,但必须在平台 Admin 审核通过后才能拿到 Token。
客户代码运行在客户基础设施内,SoChat 不提供机器人代码托管或 Serverless 运行时。
开发者请求统一用 Bot Token 调用,和 App 用户 JWT、Admin JWT 完全分离。
Webhook 更新通过异步队列投递,失败可重试,不阻塞消息主链路。
从提交申请到收到第一条 Update,按下面四步完成最短链路。
直接在本页「开发者中心」登录后提交,或在 SoChat App 内进入「我的机器人」填写信息。
平台 Admin 在机器人管理后台通过或拒绝,通过后生成系统账号与 Bot Token。
回到本页「开发者中心」选中机器人,保存一次性 Token 并登记 HTTPS 回调与 secret_token。
把机器人加入群聊,群主 @bot 或 / 命令即可收到 Update;用 sendMessage / sendPhoto 等接口回消息,支持编辑与撤回。
在本页「开发者中心」或 SoChat App 内「我的机器人」登录后提交名称、用户名、描述与用途说明。
Admin 在后台机器人管理执行通过 / 拒绝,审核动作写入审计日志(`BOT.APPROVE` / `BOT.REJECT` 等)。
审核通过后回到「开发者中心」即可读取一次性 Token,并直接登记 HTTPS Webhook 与 `secret_token`。
群主在「添加成员」中搜索机器人拉入群,命令或 @bot 触发 Update;`sendMessage` 回消息。
前缀 `sbot_`,仅首次返回明文;之后库内只存哈希。
平台向客户 HTTPS 端点投递 JSON;带 `X-SoChat-Signature` 与 `X-SoChat-Update-Id`,失败重试并落投递日志。
支持 `sendMessage` 文本以及 `sendPhoto / sendDocument / sendVideo / sendAudio / sendLocation` 多媒体;发送后可 `editMessage`、`deleteMessage`。
群内仅 `/` 命令、`@username`、回复机器人消息或 @mention 会投递。
Bot + 企业双层配额;超限返回 429 并带 `Retry-After`、`X-BotRateLimit-Remaining`。
分两组鉴权:开发者端使用账号 JWT 管理自己申请的机器人;调用 Bot 业务接口使用机器人 Bot Token (Authorization: Bearer sbot_<token>)。所有路径以 /api/v1/bots/** 为前缀。
以账号登录态调用,用于申请机器人、查看列表、读取一次性 Token、登记或删除 Webhook。
登录用户提交创建申请,等待 Admin 审核。
列出当前开发者提交的所有机器人,含状态与 Token 前缀。
审核通过后首次读取会返回 `one_time_token`,读后即销毁(7 天有效)。
使用账号 JWT 为自己的机器人配置 Webhook,无需使用 Bot Token。
移除当前机器人的 Webhook 配置,停止接收 Update。
分页查看机器人最近的 Webhook 投递记录,辅助排查失败或死信。
使用机器人自己的 Token 调用。请求头 Authorization: Bearer sbot_<token>。
基于 Bot Token 解析并返回机器人基础信息。
向指定会话发送文本消息,对外以机器人系统账号身份发送。
以图片形式发送已上传的文件,`file_id` 来源于 `/files/complete`。
以文档 / 附件形式发送已上传的文件。
以视频形式发送已上传的文件。
以音频 / 语音形式发送已上传的文件。
发送一个包含经纬度和可选地名的位置消息。
仅支持编辑由本机器人(同一 `system_user_id`)发送的文本消息;触发 `edited_message` Update。
仅支持删除由本机器人发送的消息;触发 `message_deleted` Update。
为即将上传的多媒体文件获取 S3 预签名凭证;多媒体消息第一步。
客户端完成 PUT 上传后回调登记,获取后续 `sendPhoto / sendDocument` 需要的 `file_id`。
使用 Bot Token 直接配置 Webhook;与开发者 JWT 接口二选一。
移除当前 Webhook 配置,停止接收 Update。
调试用:使用请求体中的 `token` 参数校验有效性(公开端点)。
文本消息;内部映射为 SoChat 会话消息。
curl -X POST 'https://www.sochatlive.com/api/v1/bots/sendMessage' \
-H 'Authorization: Bearer sbot_<token>' \
-H 'Content-Type: application/json' \
-d '{
"chat_id": "6530ab...9c1",
"text": "Hello from SoChat bot"
}'sendDocument / sendVideo / sendAudio 同构,仅接口路径不同。
# Step 1: 申请 S3 直传凭证
curl -X POST 'https://www.sochatlive.com/api/v1/bots/files/upload-credentials' \
-H 'Authorization: Bearer sbot_<token>' \
-H 'Content-Type: application/json' \
-d '{
"fileName": "screenshot.png",
"fileSize": 204800,
"fileType": "image/png"
}'
# => { "uploadUrl": "...", "key": "bots/xxx.png", "headers": {...} }
# Step 2: 使用返回的 uploadUrl / headers,客户端直传 S3
curl -X PUT "<uploadUrl>" -H 'Content-Type: image/png' --data-binary @screenshot.png
# Step 3: 登记上传结果,拿到 file_id
curl -X POST 'https://www.sochatlive.com/api/v1/bots/files/complete' \
-H 'Authorization: Bearer sbot_<token>' \
-H 'Content-Type: application/json' \
-d '{
"key": "bots/xxx.png",
"fileName": "screenshot.png",
"fileSize": 204800,
"fileType": "image/png"
}'
# => { "file_id": "6530ab...f2", "url": "...", ... }
# Step 4: 发送图片消息(width / height 可选;不传时由 File 文档元数据自动补齐)
curl -X POST 'https://www.sochatlive.com/api/v1/bots/sendPhoto' \
-H 'Authorization: Bearer sbot_<token>' \
-H 'Content-Type: application/json' \
-d '{
"chat_id": "6530ab...9c1",
"file_id": "6530ab...f2",
"caption": "部署成功截图",
"width": 1920,
"height": 1080
}'仅限编辑 / 删除本机器人发送的消息,触发 edited_message / message_deleted Update。
# 编辑本机器人发送过的文本消息
curl -X POST 'https://www.sochatlive.com/api/v1/bots/editMessage' \
-H 'Authorization: Bearer sbot_<token>' \
-H 'Content-Type: application/json' \
-d '{
"message_id": "6530ab12c9a0ff00123abc88",
"text": "部署状态已更新:成功"
}'
# 撤回一条自己发送的消息
curl -X POST 'https://www.sochatlive.com/api/v1/bots/deleteMessage' \
-H 'Authorization: Bearer sbot_<token>' \
-H 'Content-Type: application/json' \
-d '{ "message_id": "6530ab12c9a0ff00123abc88" }'使用账号 JWT 调用;用于排查投递失败与 dead_letter。
# 开发者自助查看 Webhook 投递日志(需账号 JWT)
curl -G 'https://www.sochatlive.com/api/v1/bots/my/<bot_id>/deliveries' \
-H 'Authorization: Bearer <jwt>' \
--data-urlencode 'page=1' \
--data-urlencode 'pageSize=20' \
--data-urlencode 'status=dead_letter'
# => { "items": [ { "update_id": "...", "status": "dead_letter", "attempts": 5, "dead_letter_at": "...", "last_error": "..." } ], "total": 3 }建议填写 secret_token 便于验签;如果需要接收编辑 / 删除事件,请在 allowed_updates 显式声明。
curl -X POST 'https://www.sochatlive.com/api/v1/bots/setWebhook' \
-H 'Authorization: Bearer sbot_<token>' \
-H 'Content-Type: application/json' \
-d '{
"url": "https://example.com/webhook/sochat",
"secret_token": "replace-with-your-secret",
"allowed_updates": ["message", "edited_message", "message_deleted"],
"allowed_ips": []
}' Update 由平台异步投递,不阻塞消息主链路。当前支持 message(新消息)、edited_message(消息被编辑)、message_deleted(消息被撤回)三种类型;群聊默认 Privacy Mode 开启,仅推送命令、@机器人、回复机器人与服务类事件等。
平台 → 客户 Webhook 的固定请求头。
| 名称 | 示例值 | 说明 |
|---|---|---|
| Content-Type | application/json | 始终 UTF-8 JSON 编码。 |
| X-SoChat-Signature | sha256=<hex> | HMAC-SHA256(raw_body, secret_token);请使用原始请求字节校验。 |
| X-SoChat-Update-Id | <uuid> | 与 payload.update_id 相同;作为幂等键。 |
平台要求客户端在 15 秒 内返回 2xx;否则视为失败并按固定节奏(1min / 5min / 15min / 1h)重试,首次加 4 次共 5 次尝试,仍失败则落为 dead_letter,Admin 可在控制台手动重投。
群聊默认仅投递以下情形;私聊不做过滤。
| 场景 | 是否投递 | 说明 |
|---|---|---|
| 私聊 | 全部投递 | 机器人在私聊里可收到用户发送的全部消息。 |
| 群聊:`/` 命令 | 投递 | 如 `/deploy`、`/help`;空格前的 token 要以 `/` 开头。 |
| 群聊:`@username` | 投递 | 文本中出现 `@<bot_username>`(大小写不敏感)。 |
| 群聊:回复机器人消息 | 投递 | Quote / Reply 到机器人发送的消息上。 |
| 群聊:mentions 命中 | 投递 | mentions 列表含机器人 `userId` 或 `username`。 |
| 群聊:其它普通消息 | 不投递 | 默认不会把群内普通聊天推给机器人。 |
字段风格贴近 Telegram,便于对照迁移;下面顺序展示文本、图片、消息编辑与撤回四种类型。
// 1) 文本消息(type=message)
{
"update_id": "3fb4e65c-4d6b-4b0d-9d9a-3a1b9c4f0e12",
"type": "message",
"bot_id": "6530ab12c9a0ff00123abc01",
"enterprise_id": null,
"message": {
"message_id": "6530ab12c9a0ff00123abc88",
"from": { "id": "6530ab12c9a0ff00123ab801", "username": "alice", "is_bot": false },
"chat": { "id": "6530ab12c9a0ff00123abc55", "type": "group", "title": "研发协作群" },
"text": "/deploy status",
"date": 1735689600
}
}
// 2) 图片消息(type=photo;文件元数据在 message.file;width / height 等元数据若已知会一并带出)
{
"update_id": "5ac9d1d4-3f9e-4a17-8dd6-7b0a1a3a8a01",
"type": "photo",
"bot_id": "6530ab12c9a0ff00123abc01",
"enterprise_id": null,
"message": {
"message_id": "6530ab12c9a0ff00123abc91",
"from": { "id": "6530ab12c9a0ff00123ab801", "username": "alice", "is_bot": false },
"chat": { "id": "6530ab12c9a0ff00123abc55", "type": "group", "title": "研发协作群" },
"text": "看下这张截图 @bot",
"file": {
"file_id": "6530ab12c9a0ff00123abcf2",
"file_name": "screenshot.png",
"mime_type": "image/png",
"file_size": 204800,
"url": "https://s3.example.com/bots/6530.../screenshot.png",
"thumbnail_url": "https://s3.example.com/bots/6530.../screenshot_thumb.png",
"width": 1920,
"height": 1080
},
"date": 1735692000
}
}
// 3) 消息编辑(type=edited_message,由 editMessage 触发)
{
"update_id": "7c33b8f2-7d24-4f4b-8a2c-1f4b2e0e10bd",
"type": "edited_message",
"bot_id": "6530ab12c9a0ff00123abc01",
"enterprise_id": null,
"message": {
"message_id": "6530ab12c9a0ff00123abc88",
"chat": { "id": "6530ab12c9a0ff00123abc55" },
"text": "/deploy status — 已更新",
"date": 1735693500
}
}
// 4) 消息撤回(type=message_deleted,由 deleteMessage 触发)
{
"update_id": "91dd6d01-3c52-4c98-907e-6b8e8a01dd3a",
"type": "message_deleted",
"bot_id": "6530ab12c9a0ff00123abc01",
"enterprise_id": null,
"message": {
"message_id": "6530ab12c9a0ff00123abc88",
"chat": { "id": "6530ab12c9a0ff00123abc55" },
"date": 1735695000
}
} SDK 把 Bot API、HMAC 验签、update_id 去重、命令路由打包成统一接口,几行代码即可完成消息收发与 Webhook 接入。 目前提供 Node.js / TypeScript 与 Java 两种语言。
@sochatlive/bot-sdk
ESM + CJS 双格式 / 自带类型声明 · 内置 Express/原生 HTTP 中间件 · fetch 原生实现,无运行时依赖。
两套 SDK 行为完全一致:Authorization: Bearer <bot_token> 调用 Bot API、X-SoChat-Signature HMAC-SHA256 验签、update_id LRU 去重、平台重试节奏 1m/5m/15m/1h。其他语言(Python、Go)规划中。
Node 18+;ESM/CJS 双格式发布。
# 任选包管理器
npm install @sochatlive/bot-sdk
pnpm add @sochatlive/bot-sdk
yarn add @sochatlive/bot-sdk
# 或在 monorepo 中开发:仓库根
pnpm install
pnpm --filter @sochatlive/bot-sdk run build高阶 SoChatBot 内置 HTTP 服务、验签、命令路由与去重,几行代码上线。
import { SoChatBot } from '@sochatlive/bot-sdk'
const bot = new SoChatBot({
token: process.env.SOCHAT_BOT_TOKEN!,
secretToken: process.env.SOCHAT_WEBHOOK_SECRET!,
baseUrl: process.env.SOCHAT_API_BASE, // 例: https://gateway.example.com/api/v1
})
// 命令路由:当用户发送 /start 时回复
bot.command('start', (ctx) => ctx.reply('你好,我是 SoChat 机器人 👋'))
// 通用消息:回声
bot.on('message', async (ctx) => {
if (ctx.message.text) {
await ctx.reply('你说: ' + ctx.message.text)
}
})
// 启动内置 HTTP 服务(自动验签 / 去重 / 路由分发)
await bot.start({ port: 8787, path: '/webhook' })
console.log('Bot is up on :8787/webhook')将公网 HTTPS 反代到 http://<host>:8787/webhook,在开发者控制台把 Webhook URL 设为反代后的 https://你的域名/webhook。
不接 Webhook、只主动推送时使用 SoChatBotClient。
import { SoChatBotClient } from '@sochatlive/bot-sdk'
const client = new SoChatBotClient({
token: process.env.SOCHAT_BOT_TOKEN!,
baseUrl: process.env.SOCHAT_API_BASE,
})
// 配置 Webhook
await client.setWebhook({
url: 'https://example.com/webhook',
secret_token: 'your-secret',
allowed_updates: ['message'],
})
// 主动推送
const me = await client.getMe()
await client.sendMessage({
chat_id: '<conversationId>',
text: '系统通知:今晚 22:00 例行维护',
})
// 编辑、撤回(仅本机器人发送的消息)
const sent = await client.sendMessage({ chat_id, text: '加载中…' })
await client.editMessage({ chat_id, message_id: sent.message_id, text: '已完成 ✅' })
await client.deleteMessage({ chat_id, message_id: sent.message_id })bot.webhookCallback() 返回 Express 兼容中间件,自动验签、去重、分发事件。express.raw({ type: 'application/json' }),否则原始字节被丢弃,签名无法对齐。import express from 'express'
import { SoChatBot } from '@sochatlive/bot-sdk'
const app = express()
const bot = new SoChatBot({
token: process.env.SOCHAT_BOT_TOKEN!,
secretToken: process.env.SOCHAT_WEBHOOK_SECRET!,
})
bot.on('message', async (ctx) => {
if (ctx.message.text === 'ping') await ctx.reply('pong')
})
// 关键:用 express.raw 保留原始字节用于 HMAC 验签
app.post(
'/webhook',
express.raw({ type: 'application/json' }),
bot.webhookCallback(),
)
// 其他路由可以照常使用 express.json()
app.use(express.json())
app.get('/health', (_req, res) => res.json({ ok: true }))
app.listen(8787)SDK 自动完成 申请凭证 → S3 直传 → complete 登记 → sendPhoto/sendDocument,开发者只需提供本地路径或 Buffer。
import { SoChatBotClient, sendPhotoFromBuffer, sendPhotoFromUrl } from '@sochatlive/bot-sdk'
import { readFile } from 'node:fs/promises'
const client = new SoChatBotClient({
token: process.env.SOCHAT_BOT_TOKEN!,
baseUrl: process.env.SOCHAT_API_BASE,
})
// 1) 本地路径(最常见)
await client.sendPhotoFromFile(chatId, './chart.png', { caption: '今日 PV 趋势' })
// 2) 内存 Buffer
const buf = await readFile('./chart.png')
await sendPhotoFromBuffer(client, chatId, buf, {
fileName: 'chart.png',
fileType: 'image/png',
})
// 3) 远端 URL(SDK 帮你下载 → 上传 → 发送)
await sendPhotoFromUrl(client, chatId, 'https://cdn.example.com/p.png', {
fileName: 'p.png',
})API 错误统一抛 SoChatApiError;签名错误抛 SoChatSignatureError,对应返回 401。
import { SoChatApiError, SoChatSignatureError } from '@sochatlive/bot-sdk'
try {
await client.sendMessage({ chat_id, text: 'hello' })
} catch (err) {
if (err instanceof SoChatApiError) {
// err.statusCode / err.code / err.message
if (err.statusCode === 429) {
// 触发配额,按 Retry-After 延后再试
}
if (err.code === 'invalid_token') {
// Token 失效:刷新 / 重新申请
}
}
throw err
}
// Webhook 中:自定义中间件捕获签名错误
try {
await bot.processWebhook(rawBody, signatureHeader, updateIdHeader)
} catch (err) {
if (err instanceof SoChatSignatureError) {
return res.status(401).end()
}
throw err
}两套 SDK 命名风格保持一致;返回值字段以 Bot API 为准。
| 能力 | Node.js | Java | Bot API 端点 |
|---|---|---|---|
| 获取自身 / 校验 Token | client.getMe() · client.verifyToken() | client.getMe() · client.verifyToken() | GET /bots/me |
| 发送文本 | client.sendMessage({chat_id,text}) | client.sendMessage(chatId, text) | POST /bots/sendMessage |
| 发送图片 / 文件 / 视频 / 语音 | client.sendPhoto / sendDocument / sendVideo / sendAudio | client.sendPhoto / sendDocument / sendVideo / sendAudio | POST /bots/send{Photo|Document|...} |
| 发送位置 | client.sendLocation | client.sendLocation | POST /bots/sendLocation |
| 编辑 / 撤回 | client.editMessage · client.deleteMessage | client.editMessage · client.deleteMessage | POST /bots/{editMessage|deleteMessage} |
| Webhook 配置 | client.setWebhook · client.deleteWebhook | client.setWebhook · client.deleteWebhook | POST /bots/{setWebhook|deleteWebhook} |
| 投递日志 & 重投 | client.getWebhookDeliveries · client.retryDelivery | client.getWebhookDeliveries · client.retryDelivery | GET /bots/my/:id/webhook-deliveries |
| S3 上传两步 | client.issueUploadCredentials · client.completeUpload | client.issueUploadCredentials · client.completeUpload | POST /bots/{issueUploadCredentials|completeUpload} |
| 本地一步上传并发送 | client.sendPhotoFromFile · sendPhotoFromBuffer · sendPhotoFromUrl | client.sendPhotoFromFile · sendDocumentFromFile · uploadFile | 内部组合调用 |
| 高阶事件路由 | new SoChatBot(...) · bot.on() · bot.command() · bot.start() | SoChatBot.builder() · bot.on() · bot.command() · bot.start() | 内置 Webhook server |
完整 API 列表与高级用法(自定义 DedupeStore、allowed_updates 过滤、getWebhookDeliveries 重投等)见两个 SDK 的 README 与 examples/ 目录。
Bot API 与 Update 标识符说明。首期 chat_id 即 SoChat 会话 ID。
| 字段 | 含义 | 说明 |
|---|---|---|
| enterprise_id | 企业 / 租户标识 | 机器人与 Update 均绑定租户;个人场景可为 `null`。 |
| bot_id | 机器人实例 ID | MongoDB ObjectId;与系统账号 `system_user_id` 一对一。 |
| chat_id | 会话标识 | 对应 SoChat `conversationId` (ObjectId),私聊、群聊同一口径。 |
| message_id | 平台消息 ID | 站内消息 ObjectId;`sendMessage` / `editMessage` / `deleteMessage` 均使用该字段。 |
| update_id | Update 唯一 ID | UUID v4;与 `X-SoChat-Update-Id` 相同,重试保持不变。 |
| system_user_id | 机器人系统账号 | 审核通过后由平台自动创建,机器人在会话中以该账号身份发言。 |
| file_id | 机器人文件 ID | 由 `/files/complete` 返回,`sendPhoto / sendDocument / sendVideo / sendAudio` 均使用该字段。 |
| file.width / height | 图片 / 视频尺寸 | 像素;`sendPhoto` / `sendVideo` 可选,或由 File 文档 `metadata.dimensions` 自动补齐。 |
| file.duration | 音视频时长 | 秒;`sendAudio` / `sendVideo` 可选,或由 File 文档 `metadata.duration` 自动补齐。 |
| file.performer / title | 音乐元信息 | `sendAudio` 可选;适合音乐 / 播客;Webhook Update 载荷同步带出。 |
| rate_limits | Bot 级速率配额 | `send_per_sec / send_per_min / send_per_day`;未设置则回退到企业 / 全局默认值。 |
| metadata.isEdited | 编辑标记 | `editMessage` 成功后会写入,客户端据此显示“已编辑”标签。 |
| dead_letter_at | 投递死信时间 | 超过 5 次尝试后落为 `dead_letter`,该字段记录时间点;Admin 可手动重试。 |
内部序列号、附件等由 Bot 服务适配;对外 DTO 稳定,变更见 OpenAPI 说明。
业务错误码;HTTP 状态以网关与正式 OpenAPI 为准。
| code | 典型 HTTP | 说明 | 处理建议 |
|---|---|---|---|
| BOT_TOKEN_REQUIRED | 401 | 请求缺少 `Authorization: Bearer <token>` | 检查请求头或请求体 `token` 字段。 |
| INVALID_BOT_TOKEN | 401 | Token 不存在、已被吊销或与 Bot 不匹配 | 确认 Token 未轮换 / 吊销;使用最新一次签发的明文值。 |
| BOT_NOT_AVAILABLE | 403 | 机器人未审核通过或已停用 (`review_status != approved` 或 `status != enabled`) | 到机器人详情查看审核状态;被停用请联系平台管理员。 |
| VALIDATION | 400 | 参数不合法,如 `chat_id` / `text` 缺失、Webhook URL 非 HTTPS、`file_id` 缺失等 | 按接口字段要求补齐;URL 必须以 `https://` 开头。 |
| FORBIDDEN | 403 | 仅限编辑 / 删除本机器人(同一 `system_user_id`)发送的消息 | 确认 `message_id` 属于当前机器人发送的消息。 |
| RATE_LIMITED | 429 | 触发网关通用路由级速率限制 | 按 `Retry-After` 退避重试;具体配额以网关路由为准。 |
| BOT_RATE_LIMIT | 429 | 触发 Bot / 企业级双层配额(`send_per_sec / min / day`) | 读取响应头 `Retry-After`、`X-BotRateLimit-Remaining`;必要时联系 Admin 调整配额。 |
| WEBHOOK_DELIVER_FAILED | — | 投递日志中的终态;首次 + 4 次重试(1min / 5min / 15min / 1h)仍失败后落为 `dead_letter` | 检查客户服务 2xx 返回、超时(15s)与 HMAC 验签实现;Admin 可手动触发重投。 |
心智接近 TG,下列为企业场景下的有意差异。
| 维度 | Telegram(BotFather) | SoChat 开放平台 |
|---|---|---|
| 谁能申请 | 用户通过 BotFather 创建 Bot | 平台用户提交创建申请,可绑定企业 / 租户上下文 |
| 何时获得 Token | 创建后立即获得 bot_token | 平台 Admin 审核通过后方可首次生成并展示 Token |
| 审核与责任 | 无单独人工审核流程 | Admin 在「机器人管理」中审核,操作入审计日志 |
| 租户与合规 | 无企业租户语义 | `enterprise_id` 贯穿 Bot 与 Update,便于私有化与追溯 |
| 当前能力 | 完整 Bot API 与生态(含按钮回调、富交互) | P0 + P1:文本 / 多媒体消息、编辑撤回、Webhook 死信、双层配额与监控;按钮回调等见 P2 路线图 |
| 速率配额 | 平台统一限流,单侧不可配置 | Bot 级 + 企业级双层配额,支持 Admin 热更新,超限返回 `BOT_RATE_LIMIT` + `Retry-After` |
| 监控与排障 | 无官方监控面板 | 近 24h 投递监控看板、开发者可自助查看 Webhook 投递日志,失败 / dead_letter 可手动重投 |
签名头、算法与正文编码以正式发布 OpenAPI 为准。
使用原始 body 字节;timingSafeEqual 防时序攻击。
import crypto from 'node:crypto'
/**
* @param rawBody - 与平台计算签名时一致的原始字节
* @param signatureHeader - 如 X-SoChat-Signature: sha256=...
* @param secret - setWebhook 时配置的 secret_token
*/
export function verifyWebhookSignature(rawBody, signatureHeader, secret) {
if (!signatureHeader || !secret) return false
const body = typeof rawBody === 'string' ? Buffer.from(rawBody) : rawBody
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(body).digest('hex')
const a = Buffer.from(expected)
const b = Buffer.from(String(signatureHeader).trim())
return a.length === b.length && crypto.timingSafeEqual(a, b)
}update_id 作幂等键;平台重试时不变。const seen = new Set() // 生产环境请换 Redis / DB
function isDuplicateUpdate(updateId) {
if (seen.has(updateId)) return true
seen.add(updateId)
return false
}验签后解析 Update,回发 sendMessage。
// POST /webhook — 验签通过后解析 Update,Echo 文本消息
app.post('/webhook/sochat', express.raw({ type: '*/*' }), async (req, res) => {
const raw = req.body
const sig = req.get('X-SoChat-Signature') || ''
if (!verifyWebhookSignature(raw, sig, process.env.WEBHOOK_SECRET)) {
return res.status(401).end()
}
const update = JSON.parse(raw.toString('utf8'))
if (isDuplicateUpdate(update.update_id)) {
return res.status(200).json({ ok: true })
}
const text = update.message?.text
const chatId = update.message?.chat?.id
if (update.type === 'message' && text && chatId) {
await fetch('https://www.sochatlive.com/api/v1/bots/sendMessage', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.BOT_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({ chat_id: chatId, text }),
})
}
res.status(200).json({ ok: true })
})Bot Token 与用户 JWT 分离;Admin 审核;日志与投递脱敏、可审计。
审核与监管在 Admin 控制台;开发者 Bot API 与 /api/v1/admin/bots/** 分域。
以下路径需使用 Admin 控制台会话(JWT)及对应 RBAC 权限,与开发者 /api/v1/bots/** 分域;具体权限点以种子与控制台为准。
| 方法 | 路径 | 说明 | 权限点 |
|---|---|---|---|
| GET | /api/v1/admin/bots | 机器人列表;筛选 `review_status` / `status` / `enterprise_id` / `search` + 分页 | admin:bot:list |
| GET | /api/v1/admin/bots/:id | 机器人详情:基础信息、审核历史、Webhook 摘要、`token_prefix` | admin:bot:read |
| GET | /api/v1/admin/bots/:id/deliveries | Webhook 投递日志(分页),支持按 `status` 过滤 | admin:bot:read |
| POST | /api/v1/admin/bots/:id/deliveries/:deliveryId/retry | 重试指定投递;入队 `bot:webhook:delivery` | admin:bot:update |
| POST | /api/v1/admin/bots/:id/approve | 审核通过;签发 Bot Token(首次明文返回) | admin:bot:review |
| POST | /api/v1/admin/bots/:id/reject | 审核拒绝,记录 `review_note` | admin:bot:review |
| POST | /api/v1/admin/bots/:id/revoke-token | 吊销当前 Token,旧 Token 立即失效 | admin:bot:revoke |
| POST | /api/v1/admin/bots/:id/toggle-status | 切换 `status` 为 `enabled` / `disabled` | admin:bot:update |
| GET | /api/v1/admin/bots/metrics/summary | 机器人监控汇总(近 24h 投递总量、成功率、队列积压、状态分布) | admin:bot:metrics |
| GET | /api/v1/admin/bots/:id/metrics | 单个机器人最近 24h 投递与 API 调用指标 | admin:bot:metrics |
| GET | /api/v1/admin/bots/:id/quota | 获取机器人当前配额(Bot 级 + 企业级兜底) | admin:bot:quota |
| PUT | /api/v1/admin/bots/:id/quota | 更新机器人 Bot 级配额(`send_per_sec / min / day`) | admin:bot:quota |
| GET | /api/v1/admin/bots/quota/enterprises | 企业机器人配额列表;分页、关键字搜索 | admin:bot:quota |
| PUT | /api/v1/admin/bots/quota/enterprises | 更新企业租户的 Bot 配额兜底值 | admin:bot:quota |