接收消息
元信息
yaml
---
name: app_chat_message_create
description: "机器人所在会话收到新消息时触发此事件。"
metadata:
business_domain: "im"
updated-on: "2026-05-28"
tags: "消息与群组"
decrypt-type: "[AES-CBC](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/wps365/server/event-subscription/security-verification)"
scopes: "应用授权:app:kso.chat_message.readwrite"
---
事件描述
机器人所在会话收到新消息时触发此事件。应用可据此实现自动回复、命令处理等逻辑。仅对应用(机器人)已加入的会话触发,未加入的会话不会推送。
事件信息说明
| 属性 | 说明 |
|---|---|
| 事件主题 | kso.app_chat.message |
| 事件行为 | create |
| 业务域 | im |
| 权限要求 | 应用授权:app:kso.chat_message.readwrite(查询和管理会话消息) |
| 解密方式 | AES-CBC |
| 重试策略 | 应用收到 HTTP POST 请求后,需要在 3 秒内以 HTTP 200 状态码响应该请求。否则 WPS 开放平台认为本次推送失败,并以 1 秒、2 秒的间隔重新推送事件,最多重试 2 次,如果 2 次失败后仍未成功,则停止推送该事件,但不影响其他事件的推送 |
事件体(解密前)
全平台统一,所有解密前事件体有且仅有以下参数信息。
| 参数名称 | 参数类型 | 是否必填 | 可选值 | 限制 | 示例 | 说明 |
|---|---|---|---|---|---|---|
topic | string | 是 | kso.app_chat.message | 固定为kso.app_chat.message | kso.app_chat.message | 事件主题 |
operation | string | 是 | create:创建 | 固定为create | create | 收到新消息 |
time | integer | 是 | - | 秒级时间戳 | 1779926400 | 事件发生时间,秒级时间戳 |
nonce | string | 是 | - | 字符长度[1,128] | 71***********7 | IV 向量(解密时使用) |
signature | string | 是 | - | 字符长度[1,512] | w6**********6Q | 消息签名 |
encrypted_data | string | 是 | - | 字符长度[1,2048] | B7**********iA== | 业务事件体加密字段 |
事件体示例(解密前)
json
{
"topic": "kso.app_chat.message",
"operation": "create",
"time": 1779926400,
"nonce": "71***********7",
"signature": "w6**********6Q",
"encrypted_data": "B7**********iA=="
}
业务事件体(encrypted_data解密后)
| 参数名称 | 参数类型 | 是否必填 | 可选值 | 限制 | 示例 | 说明 |
|---|---|---|---|---|---|---|
| company_id | string | 是 | - | - | "demo_company_id" | 企业 ID |
| chat | object | 是 | - | - | 会话信息 | |
| ∟ id | string | 是 | - | - | "demo_chat_id" | 会话 ID |
| ∟ type | string | 是 | - | - | "p2p" | 会话类型 |
| sender | object | 是 | - | - | 消息发送者 | |
| ∟ id | string | 是 | - | - | "demo_sender_id" | 身份 ID |
| ∟ type | string | 是 | user:用户sp:服务主体app:应用unknown:未知 | - | "user" | 身份类型 |
| send_time | integer | 是 | - | 秒级时间戳 | 1779926400 | 消息发送时间戳 |
| message | object | 是 | - | - | 消息内容 | |
| ∟ id | string | 是 | - | - | "demo_message_id" | 消息 ID |
| ∟ type | string | 是 | text:文本rich_text:富文本image:图片file:文件sticker:表情audio:音频video:视频 | - | "text" | 消息类型 |
| ∟ content | object | 是 | - | - | 消息内容,结构随 type 不同而变化 | |
| ∟ mentions | array[object] | 否 | - | - | 消息被 @ 人列表 | |
| ∟∟ id | string | 是 | - | - | "0" | at 操作的实体索引 ID,与消息正文中 <at id={index}> 标记匹配 |
| ∟∟ type | string | 是 | all:所有人user:用户 | - | "user" | at 操作对象类型 |
| ∟∟ identity | object | 否 | - | - | 被 at 的用户信息,at 所有人时为空 | |
| ∟ quote_msg_id | string | 否 | - | - | "demo_quote_msg_id" | 被引用的消息 ID |
业务事件体示例(解密后)
json
{
"company_id": "demo_company_id",
"chat": {
"id": "demo_chat_id",
"type": "p2p"
},
"sender": {
"id": "demo_sender_id",
"type": "user"
},
"send_time": 1779926400,
"message": {
"id": "demo_message_id",
"type": "text",
"content": {
"text": {
"content": "你好<at id=\"0\">机器人</at>"
}
},
"mentions": [
{
"id": "0",
"type": "user",
"identity": {
"type": "sp",
"id": "demo_app_id"
}
}
]
}
}
接收示例
Go
go
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/md5"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
type Event struct {
Topic string `json:"topic"`
Operation string `json:"operation"`
Time int64 `json:"time"`
Nonce string `json:"nonce"`
Signature string `json:"signature"`
EncryptedData string `json:"encrypted_data"`
}
type ChatInfo struct {
ID string `json:"id"`
Type string `json:"type"`
}
type Identity struct {
ID string `json:"id"`
Type string `json:"type"`
}
type Mention struct {
ID string `json:"id"`
Type string `json:"type"`
Identity *Identity `json:"identity,omitempty"`
}
type MessageInfo struct {
ID string `json:"id"`
Type string `json:"type"`
Content any `json:"content"`
Mentions []Mention `json:"mentions,omitempty"`
QuoteMsgID *string `json:"quote_msg_id,omitempty"`
}
type AppChatMessageCreateData struct {
CompanyID string `json:"company_id"`
Chat ChatInfo `json:"chat"`
Sender Identity `json:"sender"`
SendTime int64 `json:"send_time"`
Message MessageInfo `json:"message"`
}
const (
accessKey = "your_access_key"
secretKey = "your_secret_key"
)
func Decrypt(encryptedData, cipherKey, nonce string) (string, error) {
data, err := base64.StdEncoding.DecodeString(encryptedData)
if err != nil {
return "", err
}
rawData, err := AESCBCPKCS7Decrypt(data, []byte(cipherKey), []byte(nonce))
if err != nil {
return "", err
}
return string(rawData), nil
}
func AESCBCPKCS7Decrypt(encryptData, key, nonce []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockSize := block.BlockSize()
if len(encryptData) < blockSize {
return nil, errors.New("cipher text too short")
}
if len(encryptData)%blockSize != 0 {
return nil, errors.New("cipher text is not a multiple of the block size")
}
iv := nonce[:blockSize]
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(encryptData, encryptData)
length := len(encryptData)
unPadding := int(encryptData[length-1])
return encryptData[:(length - unPadding)], nil
}
func Md5Hex(s string) string {
h := md5.New()
h.Write([]byte(s))
return hex.EncodeToString(h.Sum(nil))
}
func HmacSha256(message, secret string) string {
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(message))
return base64.RawURLEncoding.EncodeToString(h.Sum(nil))
}
func main() {
router := gin.Default()
router.POST("/open/receive", func(c *gin.Context) {
event := &Event{}
if err := c.ShouldBindJSON(event); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
content := fmt.Sprintf("%s:%s:%s:%d:%s",
accessKey, event.Topic, event.Nonce, event.Time, event.EncryptedData)
signature := HmacSha256(content, secretKey)
if signature != event.Signature {
c.JSON(http.StatusBadRequest, gin.H{"error": "signature check failed"})
return
}
cipherKey := Md5Hex(secretKey)
decryptedData, err := Decrypt(event.EncryptedData, cipherKey, event.Nonce)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
data := &AppChatMessageCreateData{}
if err = json.Unmarshal([]byte(decryptedData), data); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// TODO 处理业务逻辑
fmt.Printf("事件内容: %+v\n", data)
c.JSON(http.StatusOK, gin.H{"code": 0})
})
_ = router.Run()
}