> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coze.cn/llms.txt
> Use this file to discover all available pages before exploring further.

开发者服务器收到回调事件后，应校验签名、从回调消息中提取信息，进行业务处理后，返回处理结果给扣子服务端。
## 处理消息回调 {#4afe5f8b}
### 步骤一：校验回调消息签名 {#f9e1f8d4}
为了检验回调事件未被第三方篡改，扣子编程提供验证回调消息签名的方法。将请求头中的 `X-Coze-Nonce`、`X-Coze-Timestamp` 参数和回调 Token、回调消息 Body 进行拼接、编码、哈希计算，并校验最终的值是否和请求头中的签名字段 `X-Coze-Signature` 完全一致。若完全一致，表示验证通过，此消息为扣子编程官方发送的回调消息。
校验签名的方式如下：

1. 获取参数。
   1. 获取请求头中的 `X-Coze-Timestamp`、`X-Coze-Nonce` 值，分别记为 `timestamp`、`nonce`。
   2. 获取原始请求 Body 的数据，记为 `body`。
   3. 获取配置回调地址时得到的 `token`。
      你可以在**回调管理**页面，在对应的回调应用卡片中，选择**更多** > **编辑**，查看回调 Token。
      
      ::::cols
      @col 50
      ![Image=437x456](https://p9-arcosite.byteimg.com/tos-cn-i-goo7wpa0wc/a716f3afb9fd46629ec581079d8e9483~tplv-goo7wpa0wc-image.image)
      
      
      @col 50
      ![Image=653x572](https://p9-arcosite.byteimg.com/tos-cn-i-goo7wpa0wc/28acfd9d5b844370bc24716c9dff6c3b~tplv-goo7wpa0wc-image.image)
      
      ::::

2. 拼接参数，并编码。
   按照 `timestamp`、`nonce`、`token`、`body` 的顺序拼接这些参数，并对拼接后的字符串进行 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 进行签名校验的示例代码如下：
   ```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)
   } 
   ```


### 步骤二：提取事件信息 {#0b267ada}
校验签名通过后，你可以从回调信息中提取出详细的事件信息。

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

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

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

:::tip 说明
智能体发布回调的超时时间为 10 秒，其余回调的超时时间为 3 秒。
:::
## 回调事件 {#9ff362ae}
在扣子编程中配置回调地址后，扣子编程通知服务器以 POST 请求方法向渠道指定的回调地址发送渠道事件通知回调，数据格式为 JSON，字符编码为 UTF-8，签名算法为 HMAC/SHA1。
### 请求 Header {#5eabb00c}
回调消息的公共请求 Header 中包含以下字段：
<!-- @cols-width: 199,139,500 -->
| | | | \
|**字段** |**类型** |**说明** |
|---|---|---|
| | | | \
|X-Coze-Nonce |String |随机数。 |
| | | | \
|X-Coze-Timestamp |Integer |回调事件的触发时间，Unixtime 时间戳格式，单位为毫秒。 |
| | | | \
|X-Coze-Signature |String |扣子服务端签名。你需要使用`X-Coze-Timestamp`、`X-Coze-Nonce` 等参数校验签名。 |

### 请求 Body {#c1e49769}
消息通知回调的回调结构体 Body 中包括 header 和 event 两个字段。

* Body 中的 header 字段：
   <!-- @cols-width: 119,100,579 -->
   | | | | \
   |**字段** |**类型** |**说明** |
   |---|---|---|
   | | | | \
   |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 结构不同，详细的回调事件参数请参见[智能体发布回调事件](/developer_guides/agent_callback_messages)、[智能体删除回调事件](/developer_guides/agent_delete_callback_messages)、[智能体下架回调事件](/developer_guides/agent_unpublished_callback_messages)、[账单推送回调事件](/developer_guides/billing_callback_message)、[申请插件扩容回调事件](/developer_guides/benefit_plugin_scale_requested)、[插件扩容到期回调事件](/developer_guides/benefit_plugin_scale_expired)。

## 代码示例 {#53b4e803}
通过 Go 进行处理智能体发布回调的完整示例代码如下：
```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 // 拒绝
)
```

## 相关文档 {#1cffe3fa}
[订阅回调](/dev_how_to_guides/add_callback)
[智能体发布回调事件](/developer_guides/agent_callback_messages)
[智能体删除回调事件](/developer_guides/agent_delete_callback_messages)
[智能体下架回调事件](/developer_guides/agent_unpublished_callback_messages)
[账单推送回调事件](/developer_guides/billing_callback_message)
[申请插件扩容回调事件](/developer_guides/benefit_plugin_scale_requested)
[插件扩容到期回调事件](/developer_guides/benefit_plugin_scale_expired)

