助手
扣子 AI 帮助与支持
你好,我是 扣子 文档问答助手 🎉 你在阅读当前文档的过程中,无论对文档概念的解释,还是文档内容方面的疑问,都可以随时向我提问,我会全力为你解答
推荐问题
如何快速了解这个空间的核心内容?
有哪些近期更新的重点文档?
我应该从哪些文档开始阅读?
文档反馈
低代码项目
快速开始
智能体
工作流
应用
资源
发布
模型
多人协作
推广与变现

接收并处理回调

更新于: 2026-06-25 19:29:14

开发者服务器收到回调事件后,应校验签名、从回调消息中提取信息,进行业务处理后,返回处理结果给扣子服务端。

处理消息回调

步骤一:校验回调消息签名

为了检验回调事件未被第三方篡改,扣子编程提供验证回调消息签名的方法。将请求头中的 X-Coze-NonceX-Coze-Timestamp 参数和回调 Token、回调消息 Body 进行拼接、编码、哈希计算,并校验最终的值是否和请求头中的签名字段 X-Coze-Signature 完全一致。若完全一致,表示验证通过,此消息为扣子编程官方发送的回调消息。
校验签名的方式如下:

  1. 获取参数。

    1. 获取请求头中的 X-Coze-TimestampX-Coze-Nonce 值,分别记为 timestampnonce

    2. 获取原始请求 Body 的数据,记为 body

    3. 获取配置回调地址时得到的 token
      你可以在回调管理页面,在对应的回调应用卡片中,选择更多 > 编辑,查看回调 Token。

      Image

      Image

  2. 拼接参数,并编码。
    按照 timestampnoncetokenbody 的顺序拼接这些参数,并对拼接后的字符串进行 UTF-8 编码,即 encode('utf-8') 编码,得到字节数组(byte[]),记为 encoded_bytes

  3. 将编码结果进行哈希计算。
    encoded_bytes 进行 SHA-1 哈希计算,得到哈希值,记为 hash_bytes,并将 hash_bytes 转换为 16 进制字符串,记为 sig

  4. 校验签名的一致性。
    校验 sig 与请求头中 X-Coze-Signature 的值是否一致。
    通过 Go 进行签名校验的示例代码如下:

    // 签名
    func GenPostRequestSignature(nonce string, timestamp string, body string, token string) string {
       var b strings.Builder
       b.WriteString(timestamp)
       b.WriteString(nonce)
       b.WriteString(token)
       b.WriteString(body)
       bs := []byte(b.String())
       h := sha1.New()
       h.Write(bs)
       bs = h.Sum(nil)
       return fmt.Sprintf("%x", bs)
    } 
    

步骤二:提取事件信息

校验签名通过后,你可以从回调信息中提取出详细的事件信息。

  1. 从事件 Header 中获取 event_type
  2. 根据不同的 event_type,解析不同的 event 结构。

步骤三:响应回调

根据提取的事件信息,业务侧进行对应的业务处理,并在收到消息后的 3 秒内做出响应,否则扣子编程服务端会判定为响应超时并重发消息,你需要根据 header 中的 event_id 进行去重处理,以避免重复处理同一事件。

  • 对于渠道回调中的智能体发布事件,渠道侧在收到智能体发布事件后,可在渠道侧进行审核,并将审核结果返回给扣子编程服务端,否则智能体发布失败。如果审核流程无法在 10 秒内完成,渠道侧可以先发送一条审核中的响应消息。
  • 其余回调,只需要返回 HTTP 状态码 200 即可。

说明

智能体发布回调的超时时间为 10 秒,其余回调的超时时间为 3 秒。

回调事件

在扣子编程中配置回调地址后,扣子编程通知服务器以 POST 请求方法向渠道指定的回调地址发送渠道事件通知回调,数据格式为 JSON,字符编码为 UTF-8,签名算法为 HMAC/SHA1。

请求 Header

回调消息的公共请求 Header 中包含以下字段:

字段

类型

说明

X-Coze-Nonce

String

随机数。

X-Coze-Timestamp

Integer

回调事件的触发时间,Unixtime 时间戳格式,单位为毫秒。

X-Coze-Signature

String

扣子服务端签名。你需要使用X-Coze-TimestampX-Coze-Nonce 等参数校验签名。

请求 Body

消息通知回调的回调结构体 Body 中包括 header 和 event 两个字段。

  • Body 中的 header 字段:

    字段

    类型

    说明

    event_type

    String

    事件类型,目前支持的事件类型包括:

    • bot.published:智能体发布事件。
    • bot.deleted:智能体删除事件。
    • bot.unpublished:智能体下架事件。
    • benefit.usage:账单推送回调。
    • benefit.plugin.scale.requested:申请插件扩容事件。
    • benefit.plugin.scale.expired:插件扩容到期事件。

    event_id

    String

    事件 ID。当开发者服务器响应超时或处理失败时,扣子编程会重发该事件的消息,你需要根据 event_id 进行去重处理,以避免重复处理同一事件。

    created_at

    Integer

    回调事件的触发时间,Unixtime 时间戳格式,单位为毫秒。

    coze_account_id

    string

    事件所属的扣子账号 ID。例如,在智能体发布事件中,即为该智能体所属空间的所有者的账号 ID。

    api_app_id

    string

    回调应用的 ID。

    connector_id

    string

    订阅渠道回调时,会返回智能体所属渠道的 ID。

  • Body 中的 event 字段:其中封装了消息的具体信息,其格式与 event_type 有关,各种事件类型的 event 结构不同,详细的回调事件参数请参见智能体发布回调事件智能体删除回调事件智能体下架回调事件账单推送回调事件申请插件扩容回调事件插件扩容到期回调事件

代码示例

通过 Go 进行处理智能体发布回调的完整示例代码如下:

func EventCallback(w http.ResponseWriter, r *http.Request) {
    // 读取请求体
    body, err := ioutil.ReadAll(r.Body)
    if err != nil {
        http.Error(w, "Failed to read request body", http.StatusInternalServerError)
        log.Printf("Failed to read request body: %v", err)
        return
    }
    defer r.Body.Close()
    
    // 记录请求体日志
    log.Printf("EventCallback body %s", string(body))

    reqSignature := r.Header.Get("X-Coze-Signature")
    reqNonce := r.Header.Get("X-Coze-Nonce")
    reqTimestamp := r.Header.Get("X-Coze-Timestamp")

    // 校验签名
     sign := Signature(reqNonce, reqTimestamp, string(body), "your_connector_token")
    log.Printf("EventCallback sign gen: %s", sign)
    log.Printf("EventCallback sign result %t", sign == reqSignature)
    if sign != reqSignature{
        // 签名不通过处理
        http.Error(w, "Signature verification failed", http.StatusUnauthorized)
        log.Printf("Signature verification failed")
        return
    }

    // 反序列事件
    req := &ConnectorCallbackEvent{}
    err := json.Unmarshal(c.GetBodyBytes(), req)
    if err != nil {
       http.Error(w, "Failed to unmarshal ConnectorCallbackEvent", http.StatusInternalServerError)
        log.Printf("Failed to unmarshal ConnectorCallbackEvent: %+v", err)
        return
    }
    
    // 根据不同的 eventType,反序列化为不同的事件体
    var event BotPublishedEvent
    if req.Header.EventType == ConnectorCallbackEventTypeBotPublish {
       err = json.Unmarshal(req.Event, &event)
       if err != nil {
          http.Error(w, "Failed to unmarshal BotPublishedEvent", http.StatusInternalServerError)
            log.Printf("Failed to unmarshal BotPublishedEvent: %+v", err)
            return
       }
    }
    
    // 进行业务处理
    log.Printf("EventCallback BotID %d", event.BotID)
    log.Printf("EventCallback UserID %d", event.UserID)
    log.Printf("EventCallback Description %s", event.Description)
    log.Printf("EventCallback BotName %s", event.BotName)

    // 返回处理结果
    auditByte, _ := json.Marshal(ConnectorCallbackEventResponse{
        Audit: audit,
    })
    log.Printf("EventCallback auditByte: %s", string(auditByte))
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    _, _ = w.Write(auditByte)
}

// 签名
func Signature(nonce string, timestamp string, body string, token string) string {
    var b strings.Builder
    b.WriteString(timestamp)
    b.WriteString(nonce)
    b.WriteString(token)
    b.WriteString(body)
    bs := []byte(b.String())
    h := sha1.New()
    h.Write(bs)
    bs = h.Sum(nil)
    return fmt.Sprintf("%x", bs)
}

// 以下为事件定义:

// 回调的总结构体
type ConnectorCallbackEvent struct {
    Header      ConnectorCallbackEventHeader `json:"header,omitempty"`
    Event       json.RawMessage              `json:"event,omitempty"`
}

// 事件类型
type ConnectorCallbackEventType string
const (
    // 发布类型
    ConnectorCallbackEventTypeBotPublish ConnectorCallbackEventType = "bot.published"
)

// 事件 header
type ConnectorCallbackEventHeader struct {
    EventType  ConnectorCallbackEventType `json:"event_type"`  // 通用事件类型
    EventID    string                     `json:"event_id"`    // 事件 ID
    CreatedAt  int64                      `json:"created_at"`  // 事件发生的UTC时间(毫秒)
}

// 根据 eventType 返回不同的结构
type BotPublishedEvent struct {
    UserID      int64                        `json:"user_id,omitempty"`     // 用户 ID
    BotID       int64                        `json:"bot_id,omitempty"`      // 机器人 ID
    BotName     string                       `json:"bot_name,omitempty"`    // 机器人名称
    Description string                       `json:"description,omitempty"` // 机器人描述
}

// 返回结构
type ConnectorCallbackEventResponse struct {
    Audit Audit `json:"audit,omitempty"` // 审核
    ShareLink string `json:"share_link,omitempty"` // 分享链接
}

type Audit struct{
    AuditStatus AuditStatus  `json:"audit_status,omitempty"`   // 审核状态
    Reason      string      `json:"reason,omitempty"`          // 原因
}

type AuditStatus int32
const (
    AuditStatus_Progress AuditStatus = 1 // 审核中
    AuditStatus_Audited  AuditStatus = 2 // 通过
    AuditStatus_Reject   AuditStatus = 3 // 拒绝
)

相关文档

订阅回调
智能体发布回调事件
智能体删除回调事件
智能体下架回调事件
账单推送回调事件
申请插件扩容回调事件
插件扩容到期回调事件