Submit Bot Application
登录用户提交创建申请,等待平台审核。
- ·必填:`name` (<=100)
- ·可选:`username` (<=64,全局唯一)、`description`、`avatar`、`scopes[]`
- ·默认 `scopes = ["messages:send", "messages:receive"]`
- ·返回:机器人对象(`review_status=pending`)
Bot API docs
From bot review and authentication to webhooks, long polling, sendMessage, rich messages, and the official SDKs.
SoChat Open Platform provides bot identity, message routing, delivery guarantees, and security boundaries. Your bot logic runs in your own service. The platform reliably delivers events to your code and routes responses back to conversations.
Any signed-in user can submit a bot application. A Bot Token is issued only after the platform review to prevent abuse and impersonation.
Your bot logic runs in your own service. SoChat does not host business code and does not require a specific infrastructure.
All Bot APIs are called with a Bot Token and are fully isolated from end-user identities, which makes automation integrations straightforward.
Updates are delivered via an async queue with at-least-once guarantees. Failed deliveries are retried, and you can re-deliver from the delivery log.
Follow these four steps to go from application to your first inbound update.
Sign in to the Developer Console, or create a bot from “My bots” in the SoChat App. Both paths go through the same review queue.
After approval, the platform issues a Bot Token. The plaintext token is shown only once in your Developer Console.
Retrieve your one-time token from the console. Webhook with HTTPS is recommended; if you cannot expose a public endpoint, use getUpdates long polling.
Add the bot to a chat. Send a message/command or @mention to trigger an inbound update. Reply via sendMessage/sendPhoto and you can edit or delete your own messages.
Use the Developer Console or the SoChat App to submit a bot name, username, description, and the intended use case. The status becomes pending.
The platform reviews your identity and use case, and returns the result via review_status with an optional rejection reason.
After approval, the first console visit shows the plaintext token (once). You can then register a webhook, set allowed_updates, and configure IP allowlists.
Add the bot to a DM or group, send a test message, observe inbound updates, and reply via sendMessage to complete the minimum loop.
Prefix `sbot_`. The plaintext token is returned only once after approval; the server stores only a hash. You can rotate or revoke it any time.
Configure HTTPS webhook via `setWebhook` for push delivery, or call `deleteWebhook` to switch to `polling` and pull updates via `getUpdates`. The modes are mutually exclusive and tracked by `Bot.delivery_mode`.
Text plus photos/documents/videos/audios/voices/video notes/animations/stickers/dice/polls/contacts/locations/venues/media groups. After sending you can `editMessage` / `deleteMessage` / `editMessageReplyMarkup`.
InlineKeyboard `callback_data` triggers `callback_query` and should be answered within 5s via `answerCallbackQuery`. Inline Mode is handled via `answerInlineQuery`.
Use `getChat*` to read group information; `setChatTitle / Description / Photo`, pin APIs, and `kickChatMember` / `banChatMember` require the bot to be the owner or admin.
Groups deliver slash commands, @mentions, replies, and mentions by default. Disable via `setMyGroupPrivacy` or the developer console to receive all messages. Bots that are group owners/admins always receive everything. DMs are fully delivered.
Global defaults plus per-bot quotas. When exceeded, you get 429 with `Retry-After` and `X-BotRateLimit-Remaining`.
All Bot APIs share the same contract for base URLs, authentication, request and response envelopes, timestamps, error structures, rate-limit headers, and naming. Each endpoint later in the page only documents its own unique parameters and fields.
HTTPS onlyProduction traffic must go through HTTPS, and every endpoint lives under /api/v1/bots/**.
Bearer tokenBot business APIs use the bot token, while developer management APIs use an account JWT. The two token types stay fully isolated.
application/jsonAll POST and PATCH payloads use UTF-8 JSON. GET parameters are passed through the query string.
Unix timestampAll timestamps are UTC. Update payloads use Unix seconds, while some response fields use ISO strings or millisecond precision depending on the field name.
page / pageSizeList endpoints use page and pageSize consistently and return total alongside the current page information.
update_idWebhook and long-poll deliveries carry a stable update_id. Retries keep the same value, and clients should de-duplicate against it.
success: falseFailed responses use { success:false, code, message }, and the HTTP status maps to the code semantics.
snake_caseThe HTTP layer uses snake_case. Official SDKs map the same behavior into language-native naming styles such as camelCase for Node.js and Java.
Every Bot API response follows the same structure. On success success=true, and the business payload lives in data;On failure success=false,code maps to the error code table.
// Success
{
"success": true,
"data": {
"message_id": "6530ab12c9a0ff00123abc88",
"chat": { "id": "6530ab12c9a0ff00123abc55", "type": "group" },
"from": { "id": "<bot_user_id>", "is_bot": true },
"text": "Hello",
"date": 1735692000
}
}
// Failure
{
"success": false,
"code": "VALIDATION",
"message": "chat_id is required"
}When a bot-level quota is exhausted, the API returns 429 BOT_RATE_LIMITand declares the backoff window through response headers.
| Header | Example | Description |
|---|---|---|
| Retry-After | 1 | Recommended backoff in seconds. Always wait for at least this long before retrying a 429 response. |
| X-BotRateLimit-Remaining | 0 | Remaining sends in the current quota window. A value of 0 means the bot has hit the limit. |
| X-BotRateLimit-Reset | 1735692060 | Unix timestamp in seconds for when the current rate-limit window resets. |
| X-StarIM-Request-Id | <uuid> | Unique request ID. Include it when contacting support so the platform can trace the exact request. |
Endpoints are split into two auth groups: developer endpoints use an account JWT to manage the bots you created, while bot runtime endpoints use a Bot Token (`Authorization: Bearer sbot_<token>`). Every path is prefixed with /api/v1/bots/** . Shared behavior is documented in the API conventions.
Call these with your account session to apply for bots, list them, read the one-time token, and register or remove webhooks.
登录用户提交创建申请,等待平台审核。
列出当前开发者提交的所有机器人,含状态与 Token 前缀。
审核通过后首次读取会返回 `one_time_token`,读后即销毁(7 天有效)。
使用账号 JWT 为自己的机器人配置 Webhook,无需使用 Bot Token。
移除当前机器人的 Webhook 配置,停止接收 Update。
分页查看机器人最近的 Webhook 投递记录,辅助排查失败或死信。
签发新 Token 并立即吊销旧 Token;返回的 `one_time_token` 仅本次响应可读,请妥善保存。
审核通过后调整机器人名称 / 头像 / 描述 / Profile 元信息。
Call these with the bot token itself. Requests use `Authorization: Bearer sbot_<token>`, and endpoints are grouped by capability so you can jump from the table of contents.
查看自身、设置命令菜单与短 / 长描述、好友申请策略、群隐私模式。
基于 Bot Token 解析并Returns机器人基础信息。
设置机器人在主页 / 私聊「ⓘ」面板顶部展示的一句话短描述(≤120)。
与 `setMyShortDescription` 配对,便于客户端回填。
设置机器人在「关于」面板的多行说明。
与 `setMyDescription` 配对。
配置 `/start`、`/help` 等命令;App 内输入 `/` 会拉出下拉提示。可按 `scope` / `language_code` 分组。
与 `setMyCommands` 配对,可按 `scope` / `language_code` 过滤。
删除某个 `scope` + `language_code` 组合下的命令清单。
配置 `auto_accept` / `auto_reject` / `manual`;`manual` 时由机器人通过 `answerFriendRequest` 决定。
与 `setMyFriendRequestMode` 配对。
对标 TG BotFather `/setprivacy`:`enabled=false` 时机器人在群里收到全部普通消息;私聊不受影响。
与 `setMyGroupPrivacy` 配对;开发者控制台也可通过 `PATCH /bots/my/:id` 修改 `group_privacy`。
列出当前机器人收到的 pending 申请;需要先把策略改为 `manual`。
在 `manual` 模式下机器人主动接受 / 拒绝某个好友申请;接受后双方关系自动生效。
For debugging: use `token` in request body to verify validity (public endpoint).
webhook 配置、查询、长轮询 getUpdates。
使用 Bot Token 直接Configure Webhook;与开发者 JWT 接口二选一。调用后机器人投递模式自动切到 `webhook`。
移除当前 Webhook 配置,停止主动 POST 投递;机器人投递模式自动切到 `polling`,可改用 `getUpdates` 拉取。
在 `polling` 模式下拉取等待中的 Update。无公网 IP 也能起 bot;与 webhook 模式互斥(webhook 模式下调用该端点会Returns 409)。
Returns当前 Webhook 配置摘要 + 平台出站 IP,方便客户在防火墙做白名单。
文本、富媒体与扩展消息类型。
向指定会话Send Text消息,对外以机器人系统账号身份发送。
以图片形式发送已上传的文件,`file_id` 来源于 `/files/complete`。
以文档 / 附件形式发送已上传的文件。
以视频形式发送已上传的文件。
以音频 / 语音形式发送已上传的文件。
发送一个包含经纬度和Optional地名的位置消息。
在 `sendLocation` 基础上要求标题;客户端会以「带标题地址卡片」样式展示。
与 `sendAudio` 同样上传后用 `file_id` 发送;客户端按「语音气泡」样式渲染。
圆形短视频;客户端会按 `width = height` 圆形播放器渲染。
通常是 GIF / MP4 短动画;客户端按视频自动循环播放。
`file_id` 来源同其他多媒体;推荐 webp / lottie。
服务端掷点;🎲 / 🎯 / 🏀 / ⚽ / 🎳 / 🎰 等表情各自有不同取值区间。
当前仅支持 `regular` / `quiz` 两种类型;客户端以投票气泡渲染。
客户端以名片气泡展示电话号码与姓名。
将 2..10 条媒体作为一组发送;平台会拆分为多条独立消息存储,仅最后一条带 `reply_markup`。
Broadcast chat action(`typing` / `upload_photo` 等)。Client shows "typing..." for 3..5s.
修改、撤回、转发 / 复制本机器人发送的消息。
仅支持编辑由本机器人(同一 `system_user_id`)发送的文本消息;触发 `edited_message` Update。
仅支持删除由本机器人发送的消息;触发 `message_deleted` Update。
替换某条本机器人消息的 `reply_markup`;不改正文与多媒体。传 `null` 表示清空按钮。
Forward a history message to target chat; bot must be active member in both source / target chats.
Sync with `forwardMessage`, but without "forwarded from" mark.
S3 直传两步法与 getFile 元数据查询。
为即将上传的多媒体文件获取 S3 预签名凭证;多媒体消息第一步。
客户端完成 PUT 上传后回调登记,获取后续 `sendPhoto / sendDocument` 需要的 `file_id`。
Get platform file metadata (including temp download URL) by `file_id`.
InlineKeyboard 应答、Inline Mode 应答。
收到 `callback_query` Webhook 后 5 秒内必须调一次,否则 App 端按钮一直转圈;同一 `callback_query_id` 仅可应答一次(重复 410)。
回应用户输入框中针对机器人的 inline 查询;当前 results 仅支持 `article` + `InputTextMessageContent`。
查询会话、群成员、群管理员。
查询单条会话(私聊或群)的基础信息;机器人必须为该会话的活跃参与者。
查询群内某成员的状态;Returns成员资料、所在角色与权限。
获取群成员人数。
群管理员(含群主)列表。
改群名、群描述、群头像;置顶 / 取消置顶。
Change group title; bot must be owner / admin.
Change group description; bot must be owner / admin.
Change group photo URL; bot must be owner / admin.
Pin a message in chat; writes `Conversation.pinnedMessageId`。
Unpin a single message.
Unpin all messages in chat.
踢人 / 拉黑 / 解封;广播聊天状态。
Broadcast chat action(`typing` / `upload_photo` 等)。Client shows "typing..." for 3..5s.
Kick member from group (no blacklist); bot must be owner / admin.
Kick and add to group blacklist, can attach `reason`.
Remove member from group blacklist (will not automatically re-invite).
在群内列出可调度成员、并以其身份发送文本。默认未开放,需联系开放平台支持申请开通后使用。
List dispatchable members in group. Requires bot to be active member; not open by default, apply via support.
Send text in group as specific dispatchable member. Access requirements same as `listChatVirtualMembers`; text only initially(`type=text`),长度 ≤ 1000;全部调用均会被记录用于合规追溯。
A basic text message example that shows the standard request and response shape.
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"
}'The same upload flow applies to `sendDocument`, `sendVideo`, and `sendAudio`; only the endpoint path changes.
# Step 1: request direct-upload credentials
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: upload the file to S3 with the returned uploadUrl / headers
curl -X PUT "<uploadUrl>" -H 'Content-Type: image/png' --data-binary @screenshot.png
# Step 3: finalize the upload and get a 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: send the photo message (width / height are optional and can be inferred from file metadata)
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": "Deployment screenshot",
"width": 1920,
"height": 1080
}'You can only edit or delete messages sent by the current bot. Successful calls trigger `edited_message` or `message_deleted` updates.
# Edit a text message previously sent by this bot
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": "Deployment status updated: success"
}'
# Delete a message sent by the current bot
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" }'Uses an account JWT and helps you investigate failed deliveries and terminal `dead_letter` items.
# Inspect webhook deliveries from the developer console API (account JWT required)
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 }Provide a `secret_token` so your service can verify signatures. If you need edited or deleted events, declare them explicitly in `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",
"message_read",
"message_delivered",
"callback_query",
"inline_query",
"friend_request",
"my_chat_member",
"chat_member",
"chat_join_request"
],
"allowed_ips": []
}'Updates are delivered asynchronously and never block the primary messaging flow. Besides webhooks, you can also pull them with getUpdates long polling. It is mutually exclusive with webhooks: calling setWebhook switches the bot to webhook mode, while deleteWebhook switches it back to polling mode. In groups, Privacy Mode is enabled by default so only commands, @mentions, replies to the bot, and service events are delivered. Disable via setMyGroupPrivacy or the developer console to receive all messages.
Declare subscriptions explicitly in setWebhook.allowed_updates or getUpdates.allowed_updates . Omitting the field, or passing an empty array, subscribes to everything.
| type | Trigger |
|---|---|
| message | A new message, including text, photo, document, video, audio, voice, video note, animation, sticker, dice, poll, contact, location, and venue. Inspect subtype-specific payloads under fields such as `message.file` or `message.location`. |
| edited_message | Triggered when `editMessage` is called by the same bot or by an allowed client. A single message may be edited multiple times, each with its own `update_id`. |
| message_deleted | Triggered after `deleteMessage` is called by the same bot or by an allowed client. |
| message_read | Emitted when the recipient reads a message sent by this bot. It is delivered only when the original sender is the current bot and is included by default unless you narrow `allowed_updates`. |
| message_delivered | Emitted when a bot message reaches the recipient device. It applies only when the original sender is the current bot. |
| callback_query | Triggered when a user clicks an InlineKeyboard button. You should call `answerCallbackQuery` within five seconds. |
| inline_query | Triggered when a user types `@botname <query>`. It requires `Bot.supports_inline_queries=true`, and the response is sent via `answerInlineQuery`. |
| friend_request | Triggered when a user sends the bot a friend request. It requires `friend_request_mode=manual` and an explicit subscription. |
| my_chat_member | Triggered when this bot’s own membership or role changes in a chat, such as joining, leaving, or being promoted/demoted. Explicit subscription required. |
| chat_member | Triggered when another member’s state changes in the same chat, such as role changes or joins/leaves. Explicit subscription required. |
| chat_join_request | Triggered when a user requests to join a chat where the bot is an administrator. Explicit subscription required. |
my_chat_member / chat_member / chat_join_request are not delivered by default and must be added explicitly to allowed_updates . Other types are included when setWebhook uses the default allowed_updates behavior.
Fixed headers sent by the platform to your webhook endpoint.
| Header | Example | Description |
|---|---|---|
| Content-Type | application/json | Always encoded as UTF-8 JSON. |
| X-StarIM-Signature | sha256=<hex> | Computed as HMAC-SHA256(raw_body, secret_token). Verify it against the raw request bytes instead of a re-serialized body. |
| X-StarIM-Update-Id | <uuid> | Matches `payload.update_id` and should be treated as the idempotency key. |
Your endpoint must return a 2xx response within 15 seconds ; otherwise the delivery is treated as failed and retried on a fixed schedule ( 1min / 5min / 15min / 1h ), for a total of 5 attempts . Failed deliveries eventually land in dead_letter, and can then be re-delivered manually from the console.
Groups deliver only the cases below by default. Disable group privacy or make the bot a group admin to receive everything. Direct messages are not filtered.
| Scenario | Delivered | Description |
|---|---|---|
| Direct messages | All | The bot receives all user messages in a direct conversation. |
| Group: slash commands | Yes | For example `/deploy` or `/help`; the token before the first space must start with `/`. |
| Group: `@username` | Yes | Delivered when the text contains `@<bot_username>` (case-insensitive). |
| Group: reply to a bot message | Yes | Delivered when the user quotes or replies to a message sent by the bot. |
| Group: mention list hit | Yes | Delivered when the mentions list contains the bot `userId` or `username`. |
| Group: other regular messages | No | Regular group chatter is not forwarded to the bot by default. |
| Group: Privacy Mode disabled | All | Call `setMyGroupPrivacy({enabled:false})` or turn it off in the developer console. |
| Group: bot is owner/admin | All | Group admins bypass privacy even when Privacy Mode stays enabled (same as Telegram). |
The sample below walks through common update types including text, photo, edited, deleted, callback query, inline query, read receipt, member state changes, and friend requests.
// 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
}
}The SDK packages the Bot API, HMAC signature verification, update_id deduplication, and command routing behind a single interface, so you can ship webhook bots in just a few lines. Official SDKs are available for Node.js / TypeScript and Java .
@starim-io/bot-sdk npm v0.1.9
Dual ESM + CJS package, bundled type definitions, built-in Express/native HTTP middleware, and a native fetch implementation with zero runtime dependencies.
io.starim:bot-sdk Maven v0.1.5
Uses the JDK built-in HttpClient + jdk.httpserver stack, depends only on Jackson Databind, and works with Spring Boot, Servlet, and Micronaut.
Both official SDKs behave the same way: use Authorization: Bearer <bot_token> to call the Bot API, X-StarIM-Signature for HMAC-SHA256 signature checks, update_id for LRU deduplication, and the same platform retry cadence of 1m/5m/15m/1h. Python, Go, and other languages are not officially maintained yet; contact Open Platform support if you need them.
Node 18+ with dual ESM/CJS distribution.
# Pick any package manager
npm install @starim-io/bot-sdk
pnpm add @starim-io/bot-sdk
yarn add @starim-io/bot-sdkThe high-level StarIMBot client bundles an HTTP server, signature checks, command routing, and deduplication.
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, // e.g. https://api.example.com/v1
})
// Command routing: reply when the user sends /start
bot.command('start', (ctx) => ctx.reply('Hello, I am the SoChat bot'))
// Generic messages: echo back text
bot.on('message', async (ctx) => {
if (ctx.message.text) {
await ctx.reply('You said: ' + ctx.message.text)
}
})
// Start the built-in HTTP server (signature checks / dedup / routing)
await bot.start({ port: 8787, path: '/webhook' })
console.log('Bot is up on :8787/webhook')Reverse-proxy public HTTPS traffic to http://<host>:8787/webhook, then configure the webhook URL in the Developer Console as https://your-domain/webhook
Use StarIMBotClient when you only send proactive messages and do not need webhook handling.
import { StarIMBotClient } from '@starim-io/bot-sdk'
const client = new StarIMBotClient({
token: process.env.STARIM_BOT_TOKEN!,
baseUrl: process.env.STARIM_API_BASE,
})
// Configure the webhook
await client.setWebhook({
url: 'https://example.com/webhook',
secret_token: 'your-secret',
allowed_updates: ['message'],
})
// Send proactive messages
const me = await client.getMe()
await client.sendMessage({
chat_id: '<conversationId>',
text: 'System notice: scheduled maintenance at 22:00 tonight',
})
// Edit or delete messages sent by the current bot
const sent = await client.sendMessage({ chat_id, text: 'Loading...' })
await client.editMessage({ chat_id, message_id: sent.message_id, text: 'Done ✅' })
await client.deleteMessage({ chat_id, message_id: sent.message_id })bot.webhookCallback() returns Express-compatible middleware that verifies signatures, deduplicates, and dispatches events for you.express.raw({ type: 'application/json' }) or the raw request bytes will be lost and the signature check will fail.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')
})
// Important: use express.raw so HMAC verification sees the original bytes
app.post(
'/webhook',
express.raw({ type: 'application/json' }),
bot.webhookCallback(),
)
// Other routes can still use express.json()
app.use(express.json())
app.get('/health', (_req, res) => res.json({ ok: true }))
app.listen(8787)The SDK automates credential issuance -> S3 direct upload -> complete registration -> sendPhoto/sendDocument, so your code only needs a local file path or a 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) Local file path (most common)
await client.sendPhotoFromFile(chatId, './chart.png', { caption: 'Today's PV trend' })
// 2) In-memory buffer
const buf = await readFile('./chart.png')
await sendPhotoFromBuffer(client, chatId, buf, {
fileName: 'chart.png',
fileType: 'image/png',
})
// 3) Remote URL (the SDK downloads -> uploads -> sends)
await sendPhotoFromUrl(client, chatId, 'https://cdn.example.com/p.png', {
fileName: 'p.png',
})API failures throw StarIMApiError; signature failures throw StarIMSignatureError, which should map to HTTP 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) {
// Quota exhausted, retry after the Retry-After delay
}
if (err.code === 'invalid_token') {
// Token expired: refresh it or issue a new one
}
}
throw err
}
// Inside webhook middleware: catch signature errors explicitly
try {
await bot.processWebhook(rawBody, signatureHeader, updateIdHeader)
} catch (err) {
if (err instanceof StarIMSignatureError) {
return res.status(401).end()
}
throw err
}Both SDKs follow the same naming conventions, and response fields map directly to Bot API.
| Capability | Node.js | Java | Other languages | Bot API endpoint |
|---|---|---|---|---|
| Inspect self / verify token | client.getMe() · client.verifyToken() | client.getMe() · client.verifyToken() | Use the HTTP API / contact support | GET /bots/me |
| Send text | client.sendMessage({chat_id,text}) | client.sendMessage(chatId, text) | Use the HTTP API / contact support | POST /bots/sendMessage |
| Send photo / document / video / audio | client.sendPhoto / sendDocument / sendVideo / sendAudio | client.sendPhoto / sendDocument / sendVideo / sendAudio | Use the HTTP API / contact support | POST /bots/send{Photo|Document|Video|Audio} |
| Rich media: voice / videoNote / animation / sticker | client.sendVoice / sendVideoNote / sendAnimation / sendSticker | client.sendVoice / sendVideoNote / sendAnimation / sendSticker | Use the HTTP API / contact support | POST /bots/send{Voice|VideoNote|Animation|Sticker} |
| Dice / poll / contact / venue / media group | client.sendDice · sendPoll · sendContact · sendVenue · sendMediaGroup | client.sendDice · sendPoll · sendContact · sendVenue · sendMediaGroup | Use the HTTP API / contact support | POST /bots/send{Dice|Poll|Contact|Venue|MediaGroup} |
| Send location | client.sendLocation | client.sendLocation | Use the HTTP API / contact support | POST /bots/sendLocation |
| Edit / delete / forward | client.editMessage · deleteMessage · forwardMessage · copyMessage | client.editMessage · deleteMessage · forwardMessage · copyMessage | Use the HTTP API / contact support | POST /bots/{editMessage|deleteMessage|forwardMessage|copyMessage} |
| Inline keyboard edit / answer | client.editMessageReplyMarkup · client.answerCallbackQuery | client.editMessageReplyMarkup · client.answerCallbackQuery | Use the HTTP API / contact support | POST /bots/{editMessageReplyMarkup|answerCallbackQuery} |
| Inline mode answer | client.answerInlineQuery | client.answerInlineQuery | Use the HTTP API / contact support | POST /bots/answerInlineQuery |
| Command menu | client.setMyCommands / getMyCommands / deleteMyCommands | client.setMyCommands / getMyCommands / deleteMyCommands | Use the HTTP API / contact support | POST /bots/{set|get|delete}MyCommands |
| Webhook config / summary | client.setWebhook · deleteWebhook · getWebhookInfo | client.setWebhook · deleteWebhook · getWebhookInfo | Use the HTTP API / contact support | POST/GET /bots/{setWebhook|deleteWebhook|getWebhookInfo} |
| Long polling | client.getUpdates({offset,timeout}) · bot.startPolling() | client.getUpdates(GetUpdatesParams) · bot.startPolling(timeoutSec) | Use the HTTP API / contact support | POST /bots/getUpdates |
| Profile management | client.setMyShortDescription / setMyDescription / getMyShortDescription / getMyDescription | client.setMyShortDescription / setMyDescription / getMyShortDescription / getMyDescription | Use the HTTP API / contact support | POST /bots/{set|get}My{Short,}Description |
| Chat inspection | client.getChat / getChatMember / getChatMemberCount / getChatAdministrators | client.getChat / getChatMember / getChatMemberCount / getChatAdministrators | Use the HTTP API / contact support | GET /bots/getChat* |
| Chat mutation / pinning | client.setChatTitle / setChatDescription / setChatPhoto · pinChatMessage* | client.setChatTitle / setChatDescription / setChatPhoto · pinChatMessage* | Use the HTTP API / contact support | POST /bots/{setChat*|pin*} |
| Moderation / chatAction | client.kickChatMember · banChatMember · unbanChatMember · sendChatAction | client.kickChatMember · banChatMember · unbanChatMember · sendChatAction | Use the HTTP API / contact support | POST /bots/{kick|ban|unban}ChatMember · POST /bots/sendChatAction |
| Friend requests / group privacy | client.getFriendRequests · answerFriendRequest · set/getMyFriendRequestMode · set/getMyGroupPrivacy | client.getFriendRequests · answerFriendRequest · set/getMyFriendRequestMode · set/getMyGroupPrivacy | Use the HTTP API / contact support | POST/GET /bots/*FriendRequest* · /bots/*GroupPrivacy |
| Delivery logs and replay | - Developer Console | - Developer Console | Use the HTTP API / contact support | GET /bots/my/:id/deliveries (account JWT, not Bot Token) |
| Two-step S3 upload / getFile | client.issueUploadCredentials · completeUpload · getFile | client.issueUploadCredentials · completeUpload · getFile | Use the HTTP API / contact support | POST/GET /bots/{files/...|getFile} |
| One-step local upload and send | client.sendPhotoFromFile · sendPhotoFromBuffer · sendPhotoFromUrl | client.sendPhotoFromFile · sendDocumentFromFile · uploadFile | Use the HTTP API / contact support | SDK convenience layer |
| High-level event routing | new StarIMBot(...) · bot.on() · bot.command() · bot.start() | StarIMBot.builder() · bot.on() · bot.command() · bot.start() | Use the HTTP API / contact support | Built-in webhook server / framework agnostic |
For the complete API surface and advanced usage, including custom deduplication, allowed_updates filtering, long-poll offset/timeout tuning, and reply_markup passthrough, refer to the Node.js and Java SDK docs. Delivery logs and retries are handled in the Developer Console. Other languages should integrate against the Bot API HTTP contract or contact Open Platform support.
You can run your bot locally without HTTPS. Call deleteWebhook to switch the bot into polling mode, then start the SDK with startPolling(). It fully supports offset / timeout / allowed_updates parameters, while the platform still handles multi-instance deduplication and instant wake-up. In practice, long polling feels close to webhooks without requiring public ingress.
// Node.js 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('hello from the polling bot'));
await bot.client.deleteWebhook(); // Switch to polling mode
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("hello from the polling bot"));
bot.getClient().deleteWebhook();
new Thread(() -> bot.startPolling(30)).start();setWebhook and getUpdates are mutually exclusive: calling getUpdates while webhook mode is active returns 409 Conflict so the same update is never consumed through two channels.A quick reference for frequently used fields across the Bot API and update payloads. chat_id is the conversation identifier in SoChat.
| Field | Meaning | Notes |
|---|
Fields follow snake_case. Any breaking or notable changes will be recorded in the changelog.
All failed responses return { success: false, code, message }. The HTTP status is aligned with code. The table below lists the most common business errors. For errors not listed here, rely on message and the HTTP status code.
| code | Typical HTTP | Description | Suggested action |
|---|---|---|---|
| code | Typical HTTP | Description | Suggested action |
An overview of capabilities designed for enterprise integrations, including access control, messaging, group governance, quotas, and monitoring.
| Dimension | Capability |
|---|
This section includes minimal HMAC-SHA256 verification, update idempotency, and an Echo Bot example. The signature uses the setWebhook value submitted through secret_token as the key and is calculated from the raw request bytes.
Always verify against the raw request body bytes; use timingSafeEqual to avoid timing attacks.
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)
}const seen = new Set() // 生产环境请换 Redis / DB
function isDuplicateUpdate(updateId) {
if (seen.has(updateId)) return true
seen.add(updateId)
return false
}After signature verification, parse the update and reply with 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.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 tokens stay isolated from user JWTs, the platform enforces review gates, and delivery plus audit logs are redacted and traceable.
Entries are listed in release order and capture newly available capabilities plus incompatible changes. This guide currently targets API version v1; any future breaking change will ship behind a new versioned path and be documented here with migration notes.
Once the application is approved, use the Echo Bot flow to validate sendMessage and the webhook loop end to end. If you get stuck, contact support through the official channel.