提交机器人申请
登录用户提交创建申请,等待平台审核。
- ·必填:`name` (<=100)
- ·可选:`username` (<=64,全局唯一)、`description`、`avatar`、`scopes[]`
- ·默认 `scopes = ["messages:send", "messages:receive"]`
- ·返回:机器人对象(`review_status=pending`)
49热聊 开放平台为开发者提供机器人身份、消息路由、投递保障与安全边界。机器人业务逻辑运行在你自己的服务上,平台负责把消息可靠地送到你的代码、并把你的回应送回会话。
任何登录用户都可提交创建申请,机器人在通过开放平台审核后获得 Bot Token,避免滥用与冒名。
机器人业务运行在你自己的服务上,49热聊 不托管业务代码,也不要求你部署在指定基础设施。
所有 Bot API 通过 Bot Token 调用,与终端用户身份完全隔离,便于自动化系统直接接入。
Update 通过异步队列分发,平台保证至少一次送达;失败按固定节奏重试,仍失败可在投递日志中重投。
从提交申请到收到第一条 Update,按下面四步完成最短链路。
在「开发者控制台」登录后提交,或在 49热聊 App 内「我的机器人」中创建;任一入口都会进入同一份审核队列。
开放平台审核通过后会签发 Bot Token,并在你的「开发者控制台」中提供一次性明文读取。
回到「开发者控制台」读取一次性 Token;推荐配置 HTTPS Webhook,没有公网 IP 也可改用 getUpdates 长轮询。
把机器人加入会话,发送 / 命令或 @机器人 触发 Update;调用 sendMessage / sendPhoto 等接口回消息,并可随时编辑或撤回自己发出的消息。
在「开发者控制台」或 49热聊 App 内填写机器人名称、用户名、描述与用途说明并提交,状态进入 pending。
平台核对身份与用途说明后通过或拒绝,结果通过 review_status 在控制台返回,被拒绝时附带原因。
审核通过后第一次进入控制台会展示明文 Token(仅一次);同时可登记 Webhook、配置 allowed_updates 与 IP 白名单。
把机器人拉入私聊或群聊,发送测试消息观察 Update 投递,调用 sendMessage 回消息即可完成最小闭环。
前缀 `sbot_`,明文仅在审核通过后第一次返回;服务端只存哈希,可随时轮换或吊销。
`setWebhook` 配置 HTTPS 后平台主动 POST;或 `deleteWebhook` 切到 `polling`,用 `getUpdates` 长轮询拉取。两者互斥,由 `Bot.delivery_mode` 维护。
文本 + 图片 / 文件 / 视频 / 音频 / 语音 / 视频笔记 / 动图 / 贴纸 / 骰子 / 投票 / 联系人 / 位置 / 地点 / 媒体组;发送后可 `editMessage` / `deleteMessage` / `editMessageReplyMarkup`。
InlineKeyboard `callback_data` 触发 `callback_query`,5s 内调 `answerCallbackQuery`;Inline Mode 通过 `answerInlineQuery` 应答 `inline_query`。
`getChat*` 读取群信息;`setChatTitle / Description / Photo`、`pinChatMessage` 系、`kickChatMember` / `banChatMember` 需机器人在该群为群主或管理员。
群聊仅投递:`/` 开头命令、文本含 `@username`、回复机器人消息或 mentions 命中机器人;私聊全量投递。
全局默认 + 单机器人配额叠加;超限返回 429 并带 `Retry-After` 与 `X-BotRateLimit-Remaining`。
所有 Bot API 共享同一份约定:Base URL、鉴权方式、请求 / 响应封装、时间格式、错误结构、限流头与命名规范。 后续每个端点只描述其特有的参数与返回字段,公共部分以本节为准。
HTTPS Only生产环境通过 HTTPS 访问,端点以 /api/v1/bots/** 为前缀。
Bearer Token业务接口使用机器人 Bot Token,开发者接口使用账号 JWT;两类 Token 完全隔离。
application/json所有 POST / PATCH 请求体均为 UTF-8 JSON;GET 参数走 query string。
Unix Timestamp所有时间戳为 UTC,Update 载荷使用秒级 Unix 时间,部分响应字段使用毫秒,需以字段名区分。
page / pageSize列表型端点统一使用 page / pageSize 参数,响应同时返回 total。
update_idWebhook / 长轮询投递的 Update 携带稳定的 update_id 作为幂等键,重试不变;客户端请按此去重。
success: false失败响应统一返回 { success:false, code, message },HTTP 状态与 code 含义对齐。
snake_caseHTTP 协议层使用 snake_case;官方 SDK 转换为各语言风格(Node.js camelCase,Java camelCase)。
所有 Bot API 的响应都遵循以下结构。成功时 success=true, 业务数据在 data;失败时 success=false, code 对应错误码表。
// 成功
{
"success": true,
"data": {
"message_id": "6530ab12c9a0ff00123abc88",
"chat": { "id": "6530ab12c9a0ff00123abc55", "type": "group" },
"from": { "id": "<bot_user_id>", "is_bot": true },
"text": "Hello",
"date": 1735692000
}
}
// 失败
{
"success": false,
"code": "VALIDATION",
"message": "chat_id is required"
} 达到机器人级配额时返回 429 BOT_RATE_LIMIT,并通过响应头声明退避时长。
| 响应头 | 示例 | 说明 |
|---|---|---|
| Retry-After | 1 | 建议退避秒数;遇到 429 时务必参考此值再重试。 |
| X-BotRateLimit-Remaining | 0 | 当前窗口内剩余可发送次数;为 0 表示已触顶。 |
| X-BotRateLimit-Reset | 1735692060 | 当前限流窗口重置的 Unix 秒时间戳。 |
| X-StarIM-Request-Id | <uuid> | 请求唯一 ID,遇问题反馈时请同时提供该值便于追溯。 |
按鉴权方式分为两组:开发者端用账号 JWT 管理自己申请的机器人;Bot 业务端用机器人 Bot Token (Authorization: Bearer sbot_<token>) 调用业务接口。所有路径以 /api/v1/bots/** 为前缀。公共行为参考API 约定。
以账号登录态调用,用于申请机器人、查看列表、读取一次性 Token、登记或删除 Webhook。
登录用户提交创建申请,等待平台审核。
列出当前开发者提交的所有机器人,含状态与 Token 前缀。
审核通过后首次读取会返回 `one_time_token`,读后即销毁(7 天有效)。
使用账号 JWT 为自己的机器人配置 Webhook,无需使用 Bot Token。
移除当前机器人的 Webhook 配置,停止接收 Update。
分页查看机器人最近的 Webhook 投递记录,辅助排查失败或死信。
签发新 Token 并立即吊销旧 Token;返回的 `one_time_token` 仅本次响应可读,请妥善保存。
审核通过后调整机器人名称 / 头像 / 描述 / Profile 元信息。
使用机器人自己的 Token 调用。请求头 Authorization: Bearer sbot_<token>;端点按主题分组,可点目录跳转。
查看自身、设置命令菜单与短 / 长描述、好友申请策略。
基于 Bot Token 解析并返回机器人基础信息。
设置机器人在主页 / 私聊「ⓘ」面板顶部展示的一句话短描述(≤120)。
与 `setMyShortDescription` 配对,便于客户端回填。
设置机器人在「关于」面板的多行说明。
与 `setMyDescription` 配对。
配置 `/start`、`/help` 等命令;App 内输入 `/` 会拉出下拉提示。可按 `scope` / `language_code` 分组。
与 `setMyCommands` 配对,可按 `scope` / `language_code` 过滤。
删除某个 `scope` + `language_code` 组合下的命令清单。
配置 `auto_accept` / `auto_reject` / `manual`;`manual` 时由机器人通过 `answerFriendRequest` 决定。
与 `setMyFriendRequestMode` 配对。
列出当前机器人收到的 pending 申请;需要先把策略改为 `manual`。
在 `manual` 模式下机器人主动接受 / 拒绝某个好友申请;接受后双方关系自动生效。
调试用:使用请求体中的 `token` 参数校验有效性(公开端点)。
webhook 配置、查询、长轮询 getUpdates。
使用 Bot Token 直接配置 Webhook;与开发者 JWT 接口二选一。调用后机器人投递模式自动切到 `webhook`。
移除当前 Webhook 配置,停止主动 POST 投递;机器人投递模式自动切到 `polling`,可改用 `getUpdates` 拉取。
在 `polling` 模式下拉取等待中的 Update。无公网 IP 也能起 bot;与 webhook 模式互斥(webhook 模式下调用该端点会返回 409)。
返回当前 Webhook 配置摘要 + 平台出站 IP,方便客户在防火墙做白名单。
文本、富媒体与扩展消息类型。
向指定会话发送文本消息,对外以机器人系统账号身份发送。
以图片形式发送已上传的文件,`file_id` 来源于 `/files/complete`。
以文档 / 附件形式发送已上传的文件。
以视频形式发送已上传的文件。
以音频 / 语音形式发送已上传的文件。
发送一个包含经纬度和可选地名的位置消息。
在 `sendLocation` 基础上要求标题;客户端会以「带标题地址卡片」样式展示。
与 `sendAudio` 同样上传后用 `file_id` 发送;客户端按「语音气泡」样式渲染。
圆形短视频;客户端会按 `width = height` 圆形播放器渲染。
通常是 GIF / MP4 短动画;客户端按视频自动循环播放。
`file_id` 来源同其他多媒体;推荐 webp / lottie。
服务端掷点;🎲 / 🎯 / 🏀 / ⚽ / 🎳 / 🎰 等表情各自有不同取值区间。
当前仅支持 `regular` / `quiz` 两种类型;客户端以投票气泡渲染。
客户端以名片气泡展示电话号码与姓名。
将 2..10 条媒体作为一组发送;平台会拆分为多条独立消息存储,仅最后一条带 `reply_markup`。
广播聊天状态(`typing` / `upload_photo` 等)。客户端展示 3..5s 的「正在输入…」。
修改、撤回、转发 / 复制本机器人发送的消息。
仅支持编辑由本机器人(同一 `system_user_id`)发送的文本消息;触发 `edited_message` Update。
仅支持删除由本机器人发送的消息;触发 `message_deleted` Update。
替换某条本机器人消息的 `reply_markup`;不改正文与多媒体。传 `null` 表示清空按钮。
将一条历史消息以「转发」形式发到目标会话;机器人需是源 / 目标双会话的活跃成员。
与 `forwardMessage` 同步对齐,但不带「转发自」标记。
S3 直传两步法与 getFile 元数据查询。
为即将上传的多媒体文件获取 S3 预签名凭证;多媒体消息第一步。
客户端完成 PUT 上传后回调登记,获取后续 `sendPhoto / sendDocument` 需要的 `file_id`。
根据 `file_id` 拿到平台文件元数据(含临时下载 URL)。
InlineKeyboard 应答、Inline Mode 应答。
收到 `callback_query` Webhook 后 5 秒内必须调一次,否则 App 端按钮一直转圈;同一 `callback_query_id` 仅可应答一次(重复 410)。
回应用户输入框中针对机器人的 inline 查询;当前 results 仅支持 `article` + `InputTextMessageContent`。
查询会话、群成员、群管理员。
查询单条会话(私聊或群)的基础信息;机器人必须为该会话的活跃参与者。
查询群内某成员的状态;返回成员资料、所在角色与权限。
获取群成员人数。
群管理员(含群主)列表。
改群名、群描述、群头像;置顶 / 取消置顶。
修改群名称;机器人需为群主 / 管理员。
修改群描述;机器人需为群主 / 管理员。
修改群头像 URL;机器人需为群主 / 管理员。
把某条会话内的消息标记为「置顶」;会写 `Conversation.pinnedMessageId`。
取消单条置顶。
清空该会话置顶。
踢人 / 拉黑 / 解封;广播聊天状态。
广播聊天状态(`typing` / `upload_photo` 等)。客户端展示 3..5s 的「正在输入…」。
把成员移出群(不写黑名单);机器人需为群主 / 群管理员。
移出群并加入群黑名单(先踢后黑),可附 `reason`。
把成员移出群黑名单(不会自动重新拉入群)。
在群内列出可调度成员、并以其身份发送文本。默认未开放,需联系开放平台支持申请开通后使用。
列出群内可被调度的成员。需机器人在群内为活跃成员;该能力默认未对外开放,请通过开放平台支持申请开通后使用。
以指定调度成员身份在群里发送一条文本。准入要求与 `listChatVirtualMembers` 一致;首期仅支持文本(`type=text`),长度 ≤ 1000;全部调用均会被记录用于合规追溯。
文本消息示例;用于展示标准发送方式与返回格式。
curl -X POST 'https://www.49chatapp.com/api/v1/bots/sendMessage' \
-H 'Authorization: Bearer sbot_<token>' \
-H 'Content-Type: application/json' \
-d '{
"chat_id": "6530ab...9c1",
"text": "Hello from 49热聊 bot"
}'sendDocument / sendVideo / sendAudio 同构,仅接口路径不同。
# Step 1: 申请 S3 直传凭证
curl -X POST 'https://www.49chatapp.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.49chatapp.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.49chatapp.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.49chatapp.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.49chatapp.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.49chatapp.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.49chatapp.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",
"message_read",
"message_delivered",
"callback_query",
"inline_query",
"friend_request",
"my_chat_member",
"chat_member",
"chat_join_request"
],
"allowed_ips": []
}' Update 由平台异步投递,不阻塞消息主链路。除 Webhook 之外,也可以用 getUpdates 长轮询拉取(与 Webhook 互斥,调用 setWebhook 自动切到 webhook 模式,调用 deleteWebhook 自动切回 polling 模式)。群聊默认 Privacy Mode 开启,仅推送命令、@机器人、回复机器人与服务类事件等。
在 setWebhook.allowed_updates 或 getUpdates.allowed_updates 中显式声明可订阅类型;不传 / 传空数组等于全部。
| type | 触发条件 |
|---|---|
| message | 新消息:文本 / 图片 / 文件 / 视频 / 音频 / 语音 / 视频笔记 / 动图 / 贴纸 / 骰子 / 投票 / 联系人 / 位置 / 地点;具体子类型见 `message.file` / `message.location` 等字段。 |
| edited_message | 由本机器人或被允许的客户端调用 `editMessage` 后触发;同条消息可多次编辑,仍按 `update_id` 区分。 |
| message_deleted | 由本机器人或被允许的客户端调用 `deleteMessage` 后触发。 |
| message_read | 对方已读机器人消息;仅在原消息发送者为本机器人时投递。须 `allowed_updates` 放行或缺省(默认已含)。 |
| message_delivered | 消息已送达接收方设备;同样要求原消息发送者为本机器人。 |
| callback_query | 用户点击 InlineKeyboard 按钮;5 秒内须调一次 `answerCallbackQuery`。 |
| inline_query | 用户输入 `@botname <query>` 触发;需 `Bot.supports_inline_queries=true`,应答用 `answerInlineQuery`。 |
| friend_request | 用户向机器人发起好友申请;需 `friend_request_mode=manual` 且订阅本类型。 |
| my_chat_member | 本机器人自己在某会话内的角色 / 状态发生变化(入群、退群、被升降权等)。须显式订阅。 |
| chat_member | 该会话其他成员的状态变化(角色升降、入群退群等);须显式订阅。 |
| chat_join_request | 用户申请加入机器人所在的群(机器人需是管理员)。须显式订阅。 |
my_chat_member / chat_member / chat_join_request 默认不投递,需要在 allowed_updates 中显式加入;其它类型在 setWebhook 缺省 allowed_updates 时默认已含。
平台 → 客户 Webhook 的固定请求头。
| 名称 | 示例值 | 说明 |
|---|---|---|
| Content-Type | application/json | 始终 UTF-8 JSON 编码。 |
| X-StarIM-Signature | sha256=<hex> | HMAC-SHA256(raw_body, secret_token);请使用原始请求字节校验。 |
| X-StarIM-Update-Id | <uuid> | 与 payload.update_id 相同;作为幂等键。 |
平台要求客户端在 15 秒 内返回 2xx;否则视为失败并按固定节奏(1min / 5min / 15min / 1h)重试,首次加 4 次共 5 次尝试,仍失败则落为 dead_letter,可在控制台手动重投。
群聊默认仅投递以下情形;私聊不做过滤。
| 场景 | 是否投递 | 说明 |
|---|---|---|
| 私聊 | 全部投递 | 机器人在私聊里可收到用户发送的全部消息。 |
| 群聊:`/` 命令 | 投递 | 如 `/deploy`、`/help`;空格前的 token 要以 `/` 开头。 |
| 群聊:`@username` | 投递 | 文本中出现 `@<bot_username>`(大小写不敏感)。 |
| 群聊:回复机器人消息 | 投递 | Quote / Reply 到机器人发送的消息上。 |
| 群聊:mentions 命中 | 投递 | mentions 列表含机器人 `userId` 或 `username`。 |
| 群聊:其它普通消息 | 不投递 | 默认不会把群内普通聊天推给机器人。 |
下面依次展示常用 Update 类型:文本 / 图片 / 编辑 / 撤回 / 按钮点击 / Inline 查询 / 已读 / 群成员变化 / 好友申请。
// 1) 文本消息(type=message)
{
"update_id": "3fb4e65c-4d6b-4b0d-9d9a-3a1b9c4f0e12",
"type": "message",
"bot_id": "6530ab12c9a0ff00123abc01",
"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",
"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",
"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",
"message": {
"message_id": "6530ab12c9a0ff00123abc88",
"chat": { "id": "6530ab12c9a0ff00123abc55" },
"date": 1735695000
}
}
// 5) 按钮点击(type=callback_query,5 秒内须调 answerCallbackQuery)
{
"update_id": "8b3a1c0c-12cd-4e08-9def-2a7c33b9bd44",
"type": "callback_query",
"bot_id": "6530ab12c9a0ff00123abc01",
"callback_query": {
"id": "6530ab12c9a0ff00123abccb",
"from": { "id": "6530ab12c9a0ff00123ab801", "username": "alice", "is_bot": false },
"message": {
"message_id": "6530ab12c9a0ff00123abc88",
"chat": { "id": "6530ab12c9a0ff00123abc55", "type": "group" }
},
"data": "approve:order:123",
"chat_instance": "6530ab12c9a0ff00123abc55"
}
}
// 6) Inline 查询(type=inline_query,需 supports_inline_queries=true)
{
"update_id": "2f6e6a51-c4ad-4d2f-bb6c-7f0e22115011",
"type": "inline_query",
"bot_id": "6530ab12c9a0ff00123abc01",
"inline_query": {
"id": "6530ab12c9a0ff00123abcd0",
"from": { "id": "6530ab12c9a0ff00123ab801", "username": "alice", "is_bot": false },
"query": "天气 北京",
"offset": "",
"chat_type": "private"
}
}
// 7) 已读 / 已送达(type=message_read / message_delivered,仅原消息发送者为本机器人时触发)
{
"update_id": "a1d92c52-2e94-44b4-be17-44b3ff97b3a4",
"type": "message_read",
"bot_id": "6530ab12c9a0ff00123abc01",
"message": {
"message_id": "6530ab12c9a0ff00123abc88",
"chat": { "id": "6530ab12c9a0ff00123abc55", "type": "private" },
"date": 1735695200
}
}
// 8) 自己角色 / 群成员变化(须显式订阅 my_chat_member / chat_member / chat_join_request)
{
"update_id": "c3e0a99a-3d5e-4f31-89e2-1c4b9c1d20a1",
"type": "my_chat_member",
"bot_id": "6530ab12c9a0ff00123abc01",
"my_chat_member": {
"chat": { "id": "6530ab12c9a0ff00123abc55", "type": "group", "title": "研发协作群" },
"from": { "id": "6530ab12c9a0ff00123ab8aa", "username": "owner", "is_bot": false },
"date": 1735695300,
"old_chat_member": { "user": { "id": "<bot_user_id>", "is_bot": true }, "status": "member" },
"new_chat_member": { "user": { "id": "<bot_user_id>", "is_bot": true }, "status": "administrator" }
}
}
// 9) 好友申请(type=friend_request,需 friend_request_mode=manual)
{
"update_id": "9bcb1f24-0d4b-4c1f-8b40-aaa3a4f3a201",
"type": "friend_request",
"bot_id": "6530ab12c9a0ff00123abc01",
"friend_request": {
"id": "6530ab12c9a0ff00123abccc",
"from": { "id": "6530ab12c9a0ff00123ab801", "username": "alice", "is_bot": false },
"request_message": "我是 ACME 客服,麻烦通过",
"source": "search",
"date": 1735695500
}
} SDK 把 Bot API、HMAC 验签、update_id 去重、命令路由打包成统一接口,几行代码即可完成消息收发与 Webhook 接入。 目前提供 Node.js / TypeScript 与 Java 两种语言。
@starim-io/bot-sdk npm v0.1.6
ESM + CJS 双格式 / 自带类型声明 · 内置 Express/原生 HTTP 中间件 · fetch 原生实现,无运行时依赖。
io.starim:bot-sdk Maven v0.1.4
JDK 自带 HttpClient + jdk.httpserver · 仅依赖 Jackson Databind · 兼容 Spring Boot / Servlet / Micronaut。
两套官方 SDK 行为完全一致:Authorization: Bearer <bot_token> 调用 Bot API、X-StarIM-Signature HMAC-SHA256 验签、update_id LRU 去重、平台重试节奏 1m/5m/15m/1h。Python、Go 等其他语言暂不提供官方 SDK,如需请联系开放平台支持 / 客服。
Node 18+;ESM/CJS 双格式发布。
# 任选包管理器
npm install @starim-io/bot-sdk
pnpm add @starim-io/bot-sdk
yarn add @starim-io/bot-sdk高阶 StarIMBot 内置 HTTP 服务、验签、命令路由与去重,几行代码上线。
import { StarIMBot } from '@starim-io/bot-sdk'
const bot = new StarIMBot({
token: process.env.STARIM_BOT_TOKEN!,
secretToken: process.env.STARIM_WEBHOOK_SECRET!,
baseUrl: process.env.STARIM_API_BASE, // 例: https://api.example.com/v1
})
// 命令路由:当用户发送 /start 时回复
bot.command('start', (ctx) => ctx.reply('你好,我是 49热聊 机器人 👋'))
// 通用消息:回声
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、只主动推送时使用 StarIMBotClient。
import { StarIMBotClient } from '@starim-io/bot-sdk'
const client = new StarIMBotClient({
token: process.env.STARIM_BOT_TOKEN!,
baseUrl: process.env.STARIM_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 { StarIMBot } from '@starim-io/bot-sdk'
const app = express()
const bot = new StarIMBot({
token: process.env.STARIM_BOT_TOKEN!,
secretToken: process.env.STARIM_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 { StarIMBotClient, sendPhotoFromBuffer, sendPhotoFromUrl } from '@starim-io/bot-sdk'
import { readFile } from 'node:fs/promises'
const client = new StarIMBotClient({
token: process.env.STARIM_BOT_TOKEN!,
baseUrl: process.env.STARIM_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 错误统一抛 StarIMApiError;签名错误抛 StarIMSignatureError,对应返回 401。
import { StarIMApiError, StarIMSignatureError } from '@starim-io/bot-sdk'
try {
await client.sendMessage({ chat_id, text: 'hello' })
} catch (err) {
if (err instanceof StarIMApiError) {
// 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 StarIMSignatureError) {
return res.status(401).end()
}
throw err
}两套 SDK 命名风格保持一致;返回值字段以 Bot API 为准。
| 能力 | Node.js | Java | 其他语言 | Bot API 端点 |
|---|---|---|---|---|
| 获取自身 / 校验 Token | client.getMe() · client.verifyToken() | client.getMe() · client.verifyToken() | 按 HTTP API 接入 / 联系客服 | GET /bots/me |
| 发送文本 | client.sendMessage({chat_id,text}) | client.sendMessage(chatId, text) | 按 HTTP API 接入 / 联系客服 | POST /bots/sendMessage |
| 发送图片 / 文件 / 视频 / 音频 | client.sendPhoto / sendDocument / sendVideo / sendAudio | client.sendPhoto / sendDocument / sendVideo / sendAudio | 按 HTTP API 接入 / 联系客服 | POST /bots/send{Photo|Document|Video|Audio} |
| 富消息:voice / videoNote / animation / sticker | client.sendVoice / sendVideoNote / sendAnimation / sendSticker | client.sendVoice / sendVideoNote / sendAnimation / sendSticker | 按 HTTP API 接入 / 联系客服 | POST /bots/send{Voice|VideoNote|Animation|Sticker} |
| 骰子 / 投票 / 联系人 / 地点 / 媒体组 | client.sendDice · sendPoll · sendContact · sendVenue · sendMediaGroup | client.sendDice · sendPoll · sendContact · sendVenue · sendMediaGroup | 按 HTTP API 接入 / 联系客服 | POST /bots/send{Dice|Poll|Contact|Venue|MediaGroup} |
| 发送位置 | client.sendLocation | client.sendLocation | 按 HTTP API 接入 / 联系客服 | POST /bots/sendLocation |
| 编辑 / 撤回 / 转发 | client.editMessage · deleteMessage · forwardMessage · copyMessage | client.editMessage · deleteMessage · forwardMessage · copyMessage | 按 HTTP API 接入 / 联系客服 | POST /bots/{editMessage|deleteMessage|forwardMessage|copyMessage} |
| InlineKeyboard 编辑 / 应答 | client.editMessageReplyMarkup · client.answerCallbackQuery | client.editMessageReplyMarkup · client.answerCallbackQuery | 按 HTTP API 接入 / 联系客服 | POST /bots/{editMessageReplyMarkup|answerCallbackQuery} |
| Inline Mode 应答 | client.answerInlineQuery | client.answerInlineQuery | 按 HTTP API 接入 / 联系客服 | POST /bots/answerInlineQuery |
| 命令菜单 | client.setMyCommands / getMyCommands / deleteMyCommands | client.setMyCommands / getMyCommands / deleteMyCommands | 按 HTTP API 接入 / 联系客服 | POST /bots/{set|get|delete}MyCommands |
| Webhook 配置 / 摘要 | client.setWebhook · deleteWebhook · getWebhookInfo | client.setWebhook · deleteWebhook · getWebhookInfo | 按 HTTP API 接入 / 联系客服 | POST/GET /bots/{setWebhook|deleteWebhook|getWebhookInfo} |
| 长轮询拉取 | client.getUpdates({offset,timeout}) · bot.startPolling() | client.getUpdates(GetUpdatesParams) · bot.startPolling(timeoutSec) | 按 HTTP API 接入 / 联系客服 | POST /bots/getUpdates |
| 资料管理 | client.setMyShortDescription / setMyDescription / getMyShortDescription / getMyDescription | client.setMyShortDescription / setMyDescription / getMyShortDescription / getMyDescription | 按 HTTP API 接入 / 联系客服 | POST /bots/{set|get}My{Short,}Description |
| 群读取 | client.getChat / getChatMember / getChatMemberCount / getChatAdministrators | client.getChat / getChatMember / getChatMemberCount / getChatAdministrators | 按 HTTP API 接入 / 联系客服 | GET /bots/getChat* |
| 群信息修改 / 置顶 | client.setChatTitle / setChatDescription / setChatPhoto · pinChatMessage 系 | client.setChatTitle / setChatDescription / setChatPhoto · pinChatMessage 系 | 按 HTTP API 接入 / 联系客服 | POST /bots/{setChat*|pin*} |
| 群治理 / chatAction | client.kickChatMember · banChatMember · unbanChatMember · sendChatAction | client.kickChatMember · banChatMember · unbanChatMember · sendChatAction | 按 HTTP API 接入 / 联系客服 | POST /bots/{kick|ban|unban}ChatMember · POST /bots/sendChatAction |
| 好友申请 manual | client.getFriendRequests · answerFriendRequest · set/getMyFriendRequestMode | client.getFriendRequests · answerFriendRequest · set/getMyFriendRequestMode | 按 HTTP API 接入 / 联系客服 | POST/GET /bots/*FriendRequest* |
| 投递日志 & 重投 | — (开发者控制台) | — (开发者控制台) | 按 HTTP API 接入 / 联系客服 | GET /bots/my/:id/deliveries(账号 JWT,非 Bot Token) |
| S3 上传两步 / getFile | client.issueUploadCredentials · completeUpload · getFile | client.issueUploadCredentials · completeUpload · getFile | 按 HTTP API 接入 / 联系客服 | POST/GET /bots/{files/...|getFile} |
| 本地一步上传并发送 | client.sendPhotoFromFile · sendPhotoFromBuffer · sendPhotoFromUrl | client.sendPhotoFromFile · sendDocumentFromFile · uploadFile | 按 HTTP API 接入 / 联系客服 | SDK 封装能力 |
| 高阶事件路由 | new StarIMBot(...) · bot.on() · bot.command() · bot.start() | StarIMBot.builder() · bot.on() · bot.command() · bot.start() | 按 HTTP API 接入 / 联系客服 | 内置 Webhook server / 框架解耦 |
完整 API 列表与高级用法(自定义去重、allowed_updates 过滤、长轮询的 offset/timeout 调优、`reply_markup` 透传等)请参考 Node / Java SDK 文档。投递日志查询与重试可通过开发者控制台完成;其他语言请按 Bot API HTTP 契约接入,或联系开放平台支持。
在本机起 bot 没有 HTTPS 也没问题。先 deleteWebhook 切到 polling 模式,再用 SDK 的 startPolling(),完整支持 offset / timeout / allowed_updates 等参数;平台层会做多实例去重与即时唤醒,长轮询体感与 webhook 几乎无差,但完全免去公网门槛。
// Node SDK
import { StarIMBot } from '@starim-io/bot-sdk';
const bot = new StarIMBot({
token: process.env.STARIM_BOT_TOKEN!,
baseUrl: 'https://your-api-host/api/v1',
secretToken: 'unused-in-polling-mode'
});
bot.command('start', ctx => ctx.reply('hi from polling bot'));
await bot.client.deleteWebhook(); // 切到 polling 模式
await bot.startPolling({ timeout: 30 });// Java SDK
StarIMBot bot = StarIMBot.builder()
.token(System.getenv("STARIM_BOT_TOKEN"))
.secretToken("unused-in-polling-mode")
.baseUrl("https://your-api-host/api/v1")
.build();
bot.command("start", ctx -> ctx.reply("hi from polling bot"));
bot.getClient().deleteWebhook();
new Thread(() -> bot.startPolling(30)).start();setWebhook 与 getUpdates 互斥:webhook 模式下调 getUpdates 返 409 Conflict,避免双通道同时消费同一条 Update。 贯穿 Bot API 与 Update 载荷的高频字段含义;其中 chat_id 即 49热聊 中的会话标识。
| 字段 | 含义 | 说明 |
|---|---|---|
| bot_id | 机器人实例 ID | 24 位字符串 ID;与系统账号 `system_user_id` 一对一。 |
| chat_id | 会话标识 | 与平台内 `conversationId` 同源,私聊、群聊同一口径。 |
| message_id | 平台消息 ID | 站内消息 ID;`sendMessage` / `editMessage` / `deleteMessage` 均使用该字段。 |
| update_id | Update 唯一 ID | 入站类 `message / photo / ...` 由 `sha256(botId + messageId + updateType)` 派生(多实例平台只投递一次);`edited_message` / `message_deleted` 等仍用 UUID。重试保持不变。 |
| update_seq | polling 自增序号 | 仅 `getUpdates` 返回;客户端以「上一批最后一条 + 1」作为下次 `offset` 实现 ack。 |
| system_user_id | 机器人系统账号 | 审核通过后由平台自动创建,机器人在会话中以该账号身份发言。 |
| delivery_mode | 投递模式 | `webhook` / `polling`,`setWebhook` / `deleteWebhook` 会自动切换;webhook 模式下调 `getUpdates` 返回 409。 |
| file_id | 机器人文件 ID | 由 `/files/complete` 或 `/getFile` 返回,多媒体接口均使用该字段。 |
| file.width / height | 图片 / 视频尺寸 | 像素;`sendPhoto` / `sendVideo` / `sendVideoNote` / `sendAnimation` / `sendSticker` 可选,或由 File 文档 `metadata.dimensions` 自动补齐。 |
| file.duration | 音视频时长 | 秒;`sendAudio` / `sendVideo` / `sendVoice` / `sendVideoNote` / `sendAnimation` 可选,或由 File 文档 `metadata.duration` 自动补齐。 |
| file.performer / title | 音乐元信息 | `sendAudio` / `sendVoice` 可选;适合音乐 / 播客;Webhook Update 载荷同步带出。 |
| metadata.diceEmoji / diceValue | 骰子点数 | `sendDice` 服务端掷点结果;🎰 1–64,⚽/🏀 1–5,其余默认 1–6。 |
| metadata.poll | 投票结构 | `sendPoll` 写入 `{ question, options, is_anonymous, type, correct_option_id? }`。 |
| metadata.contact | 联系人卡片 | `sendContact` 写入 `{ phone_number, first_name, last_name }`。 |
| metadata.isVenue | 是否地点 | `sendVenue` 写入 `true`,与 `sendLocation` 公用 `latitude` / `longitude` / `name` / `address`。 |
| rate_limits | 机器人级速率配额 | `send_per_sec / send_per_min / send_per_day`;未设置则回退到全局默认。 |
| metadata.isEdited | 编辑标记 | `editMessage` 成功后写入;客户端据此显示"已编辑"标签。 |
| pinned_message_id | 会话置顶 | 通过 `pinChatMessage` / `unpinChatMessage` 维护,`getChat` 响应一并返回。 |
| platform_egress_ips | 平台出站 IP | 由 `getWebhookInfo` 返回,便于在你的防火墙做出站白名单。 |
| dead_letter_at | 投递死信时间 | 5 次尝试均失败后落为 `dead_letter`,该字段记录时间点;可在控制台手动重投。 |
字段命名遵循 snake_case;版本升级如有改动会在版本日志中标注。
所有失败响应统一返回 { success: false, code, message },HTTP 状态与 code 一一对应。下表列出最常见的业务错误,遇到未列出的错误请参照 message 与 HTTP 状态码处理。
| 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` 缺失、`options` 数量不在 2..12 等 | 按接口字段要求补齐;URL 必须以 `https://` 开头。 |
| FORBIDDEN | 403 | 仅限编辑 / 删除本机器人(同一 `system_user_id`)发送的消息;或群治理类未授予机器人群主 / 管理员 | 确认 `message_id` 属于当前机器人发送的消息;或先将机器人提升为群主 / 管理员。 |
| CONFLICT | 409 | 当前为 `webhook` 模式时调 `getUpdates`,或同一 `callback_query_id` 重复 `answerCallbackQuery` | webhook 模式下要先调 `deleteWebhook` 才能用 `getUpdates`;按钮回调只能 answer 一次。 |
| NOT_FOUND | 404 | `chat_id` / `message_id` / `file_id` 等资源不存在 | 检查传入的 ID 与机器人当前可见会话。 |
| RATE_LIMITED | 429 | 触发平台路由级速率限制 | 按 `Retry-After` 退避重试;具体配额以接入文档为准。 |
| BOT_RATE_LIMIT | 429 | 触发机器人级发送配额(`send_per_sec / send_per_min / send_per_day`,叠加全局默认) | 读取响应头 `Retry-After` 与 `X-BotRateLimit-Remaining`;如长期受限,请联系开放平台支持调整阈值。 |
| WEBHOOK_DELIVER_FAILED | — | 投递日志中的终态;首次加 4 次重试(1min / 5min / 15min / 1h)共 5 次仍失败后落为 `dead_letter` | 检查你服务端的 2xx 响应、15 秒超时与 HMAC 验签实现;失败记录可在「开发者控制台」的投递日志中手动重投。 |
面向企业接入场景设计的能力一览,覆盖准入、消息、群治理、配额与监控。
| 维度 | 能力说明 |
|---|---|
| 准入与审核 | 开发者在「开发者控制台」提交创建申请,开放平台审核通过后签发 Bot Token;申请、审批、变更与吊销全程留痕,可在控制台查阅。 |
| 消息类型 | 覆盖 text / photo / document / video / audio / voice / video_note / animation / sticker / dice / poll / contact / location / venue / media_group。 |
| 群治理 | getChat 系 / setChat 系 / pinChat 系、forwardMessage / copyMessage / sendChatAction / getFile 等读写端点;写类操作需机器人为群主或群管理员。 |
| 交互能力 | `callback_query` + `answerCallbackQuery`、`inline_query` + `answerInlineQuery`、`setMyCommands` 命令菜单。 |
| 投递模式 | Webhook 与 Polling 互斥,`setWebhook` / `deleteWebhook` 自动维护 `Bot.delivery_mode`;调用顺序错误返回 `409 Conflict`。 |
| 群成员事件 | `my_chat_member` / `chat_member` / `chat_join_request`,需在 `allowed_updates` 中显式订阅。 |
| 已读 / 已送达 | `message_read` / `message_delivered`,仅当原消息发送者为本机器人时投递。 |
| 速率配额 | 全局默认 + 机器人级配额(`send_per_sec` / `send_per_min` / `send_per_day`);超限返回 `429 BOT_RATE_LIMIT` 与 `Retry-After`。 |
| 监控与排障 | 可在「开发者控制台」查看近 24h 投递与 API 调用指标,Webhook 投递日志按状态分页,失败 / dead_letter 支持手动重投。 |
| 出站 IP | `GET /bots/getWebhookInfo` 返回 `platform_egress_ips`,便于在你的防火墙做出站白名单。 |
HMAC-SHA256 验签、Update 幂等去重与 Echo Bot 的最小实现;签名以 setWebhook 提交的 secret_token 为密钥,对原始请求字节计算。
使用原始 body 字节;timingSafeEqual 防时序攻击。
import crypto from 'node:crypto'
/**
* @param rawBody - 与平台计算签名时一致的原始字节
* @param signatureHeader - 如 X-StarIM-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 作幂等键;平台重试时不变。message / photo / document / video / audio / location)。edited_message / message_deleted 同一条消息可能多次发生,仍按事件本身去重,请用 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-StarIM-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.49chatapp.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 分离;平台审核;日志与投递脱敏、可审计。
按发布顺序记录已上线的能力与不兼容调整。本接入文档对应 API 版本 v1; 未来如有破坏性变更,会在新版本路径下以新版本号发布,并在本日志列出迁移建议。