服务端回调
除主动调用 Open API 外,IM Server 还会通过回调(Webhook)把消息、用户、群组、聊天室等事件推送到你的 App Server。回调体系(IM PaaS → 应用服务器 Webhook)契约。含全量消息路由 message.routed、离线推送、撤回、已读、用户/群/聊天室事件。
回调是“至少一次投递”:应用服务器需要校验签名、快速返回 2xx,并基于 eventId 做幂等去重。
投递策略
| 项目 | 策略 |
|---|---|
| HTTP 方法 | POST |
| Content-Type | application/json; charset=utf-8 |
| 单次投递超时(毫秒) | 5,000 毫秒(5 秒) |
| 成功判定 | HTTP 2xx |
| 重试策略 | 最多 3 次,退避间隔 1,000 / 5,000 / 30,000 毫秒;触发条件:non-2xx、timeout、network-error |
| 顺序语义 | 同事件类型内近似有序,不保证全局顺序;eventId 幂等去重由应用服务器负责 |
| 至少一次投递 | 是 |
| 请求体上限(字节) | 262,144 字节(256.0 KiB) |
| 单应用并发投递数 | 8 |
签名校验
每个回调请求都携带签名请求头,签名基于请求时间戳、Nonce 与「原始请求体」计算;请务必使用未做 JSON 解析的原始字节参与计算。
| 请求头 | 说明 |
|---|---|
X-IM-Timestamp | epoch ms |
X-IM-Nonce | 8-64 字符随机串 |
X-IM-Signature | hex(hmac_sha256(secret, timestamp + '.' + nonce + '.' + rawBody)) 小写 |
X-IM-Signature = hex(hmac_sha256(secret, timestamp + '.' + nonce + '.' + rawBody)) 小写- 时间戳允许窗口:±300,000 毫秒(5 分钟)。
- 签名密钥由回调配置接口设置,服务端加密存储,读取时脱敏(只回末四位)。
- 比较签名时使用常量时间比较,避免时序侧信道。
- 生产环境只允许 HTTPS 回调地址。
import { createHmac, timingSafeEqual } from 'node:crypto';
// req.headers:X-IM-Timestamp / X-IM-Nonce / X-IM-Signature
// rawBody:未做 JSON 解析的原始请求体字符串
export function verifyCallback(secret, rawBody, headers) {
const timestamp = headers['x-im-timestamp'];
const nonce = headers['x-im-nonce'];
const signature = headers['x-im-signature'];
if (!timestamp || !nonce || !signature) return false;
if (Math.abs(Date.now() - Number(timestamp)) > 300_000) return false;
const expected = createHmac('sha256', secret)
.update(timestamp + '.' + nonce + '.' + rawBody)
.digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(String(signature));
return a.length === b.length && timingSafeEqual(a, b);
}import hashlib
import hmac
import json
import time
def verify_callback(secret: str, raw_body: bytes, headers: dict) -> bool:
"""headers 中的键名按小写处理;raw_body 必须是未解析的原始字节。"""
timestamp = headers.get("x-im-timestamp")
nonce = headers.get("x-im-nonce")
signature = headers.get("x-im-signature")
if not timestamp or not nonce or not signature:
return False
if abs(int(time.time() * 1000) - int(timestamp)) > 300_000:
return False
payload = f"{timestamp}.{nonce}.".encode("utf-8") + raw_body
expected = hmac.new(secret.encode("utf-8"), payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)回调信封
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
eventType | CallbackEventType | 是 | — |
eventId | string | 是 | 全局唯一,重试不变,用于幂等 |
appkey | string | 是 | — |
timestamp | int64 | 是 | — |
nonce | string | 是 | — |
data | any | 是 | — |
{
"eventType": "message.routed",
"eventId": "evt_5b1c",
"appkey": "demo",
"timestamp": 1750000000000,
"nonce": "9f2a7c1b",
"data": {
"message": {
"messageId": "msg_10001",
"clientMsgId": "client_9f2a7c",
"conversationType": 1,
"targetId": "user2",
"channelId": "channel1",
"senderId": "user1",
"seq": 1024,
"timestamp": 1750000000000,
"content": {
"type": "text",
"text": "你好,IM PaaS"
},
"direction": 1
},
"conversation": {
"conversationType": 1,
"targetId": "user2",
"targetName": "Bob",
"unreadCount": 0,
"mentionedCount": 0,
"lastMessage": {
"messageId": "msg_10001",
"clientMsgId": "client_9f2a7c",
"conversationType": 1,
"targetId": "user2",
"channelId": "channel1",
"senderId": "user1",
"seq": 1024,
"timestamp": 1750000000000,
"content": {
"type": "text",
"text": "你好,IM PaaS"
},
"direction": 1
},
"updatedAt": 1750000000000,
"isPinned": false,
"draft": "明天见",
"notificationLevel": 0,
"groupType": "normal"
},
"senderId": "user1",
"targetId": "user2",
"conversationType": 1,
"delivery": {
"onlineUsers": 1,
"offlineQueued": 0
}
}
}事件列表
| 事件类型 | 起始版本 | 说明 | 触发接口 |
|---|---|---|---|
message.routed | 1.0.0 | 全量消息路由:每一条成功入库的消息(单聊/群/超级群/聊天室/系统通知)都会触发,供应用服务器归档/审核/业务路由。 | message.private.publish、message.group.publish、message.chatroom.publish、message.system.publish |
message.offline | 1.0.0 | 接收方无任何在线会话时触发(配合离线推送通道抽象)。 | message.private.publish、message.group.publish |
message.recalled | 1.0.0 | 消息撤回成功后触发。 | message.recall |
read.receipt | 1.0.0 | 已读回执(按读者聚合;群聊含累计已读人数)。 | —(未由 Open API 接口直接触发) |
user.registered | 1.0.0 | 用户注册成功。 | —(未由 Open API 接口直接触发) |
user.banned | 1.0.0 | 用户封禁/解封。 | user.ban |
group.created | 1.0.0 | 群/超级群创建。 | group.create |
group.updated | 1.0.0 | 群信息/成员/管理员/禁言变更。 | group.update、group.members.add、group.members.remove、group.quit、group.admin.add、group.mute |
group.destroyed | 1.0.0 | 群解散。 | group.dismiss |
chatroom.created | 1.0.0 | 聊天室创建。 | chatroom.create |
chatroom.destroyed | 1.0.0 | 聊天室销毁。 | chatroom.destroy |
chatroom.member_changed | 1.0.0 | 聊天室成员变化(join/leave/kick)。 | —(未由 Open API 接口直接触发) |
user.online_status_changed | 1.1.0 | 用户在线状态变更:WS 建连/断连时触发(等价于融云用户在线状态回调)。 | —(未由 Open API 接口直接触发) |
user.deactivated | 1.1.0 | 用户注销(deactivate)成功后触发,既有 token 立即失效。 | —(未由 Open API 接口直接触发) |
user.reactivated | 1.1.0 | 用户恢复激活后触发。 | —(未由 Open API 接口直接触发) |
user.profile_changed | 1.1.0 | 用户资料变更(name 等)后触发。 | —(未由 Open API 接口直接触发) |
friend.changed | 1.1.0 | 好友关系变更:申请/接受/拒绝/删除。 | —(未由 Open API 接口直接触发) |
chatroom.kv_changed | 1.1.0 | 聊天室属性(KV)变更:设置/删除后触发,便于应用服务器同步。 | —(未由 Open API 接口直接触发) |
message.modified | 1.1.0 | 消息内容被修改(message.modify)后触发。 | —(未由 Open API 接口直接触发) |
message.deleted | 1.1.0 | 历史消息被批量清理(message.history.clean)后触发。 | —(未由 Open API 接口直接触发) |
message.audit_result | 1.1.0 | 消息审核结果回推:管理端对内容审核任务做出裁决(approve/reject)后触发,供应用服务器同步处置结果。 | —(未由 Open API 接口直接触发) |
message.publish.before | 1.2.0 | 发送前同步回调:客户端消息投递前阻塞询问应用服务器(超时 syncTimeoutMs,默认 500ms)。应用服务器返回 {code:0,data:{action:"pass"|"replace"|"block",content?,reason?}},超时/故障按 syncDefaultAction 降级(默认放行)。该事件不会异步重试,每次发送最多一次同步调用。 | —(未由 Open API 接口直接触发) |
事件结构
每个事件的 data 字段结构如下;示例值由 IDL 类型推导,仅用于展示结构。必填列按 IDL 类型中的可选标记(?)推导,未标注的字段在契约中为必填。
message.routed
全量消息路由:每一条成功入库的消息(单聊/群/超级群/聊天室/系统通知)都会触发,供应用服务器归档/审核/业务路由。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
message | Message | 是 | — |
conversation | Conversation | 是 | — |
senderId | string | 是 | — |
targetId | string | 是 | — |
conversationType | ConversationType | 是 | — |
delivery | {onlineUsers:uint32, offlineQueued:uint32} | 是 | — |
{
"eventType": "message.routed",
"eventId": "evt_5b1c",
"appkey": "demo",
"timestamp": 1750000000000,
"nonce": "9f2a7c1b",
"data": {
"message": {
"messageId": "msg_10001",
"clientMsgId": "client_9f2a7c",
"conversationType": 1,
"targetId": "user2",
"channelId": "channel1",
"senderId": "user1",
"seq": 1024,
"timestamp": 1750000000000,
"content": {
"type": "text",
"text": "你好,IM PaaS"
},
"direction": 1
},
"conversation": {
"conversationType": 1,
"targetId": "user2",
"targetName": "Bob",
"unreadCount": 0,
"mentionedCount": 0,
"lastMessage": {
"messageId": "msg_10001",
"clientMsgId": "client_9f2a7c",
"conversationType": 1,
"targetId": "user2",
"channelId": "channel1",
"senderId": "user1",
"seq": 1024,
"timestamp": 1750000000000,
"content": {
"type": "text",
"text": "你好,IM PaaS"
},
"direction": 1
},
"updatedAt": 1750000000000,
"isPinned": false,
"draft": "明天见",
"notificationLevel": 0,
"groupType": "normal"
},
"senderId": "user1",
"targetId": "user2",
"conversationType": 1,
"delivery": {
"onlineUsers": 1,
"offlineQueued": 0
}
}
}触发接口:message.private.publish、message.group.publish、message.chatroom.publish、message.system.publish
message.offline
接收方无任何在线会话时触发(配合离线推送通道抽象)。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
userId | string | 是 | — |
message | Message | 是 | — |
reason | "offline" | 是 | IDL 中以示例值给出(非类型) |
pushTitle | string | 否 | — |
pushContent | string | 否 | — |
{
"eventType": "message.offline",
"eventId": "evt_5b1c",
"appkey": "demo",
"timestamp": 1750000000000,
"nonce": "9f2a7c1b",
"data": {
"userId": "user1",
"message": {
"messageId": "msg_10001",
"clientMsgId": "client_9f2a7c",
"conversationType": 1,
"targetId": "user2",
"channelId": "channel1",
"senderId": "user1",
"seq": 1024,
"timestamp": 1750000000000,
"content": {
"type": "text",
"text": "你好,IM PaaS"
},
"direction": 1
},
"reason": "offline",
"pushTitle": "新消息",
"pushContent": "你有一条新消息"
}
}触发接口:message.private.publish、message.group.publish
message.recalled
消息撤回成功后触发。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
message | Message | 是 | — |
operatorId | string | 是 | — |
originalContent | Content | 是 | 消息内容联合体,按 type 取字段;服务端校验序列化后 <=5KB(1007)。 |
{
"eventType": "message.recalled",
"eventId": "evt_5b1c",
"appkey": "demo",
"timestamp": 1750000000000,
"nonce": "9f2a7c1b",
"data": {
"message": {
"messageId": "msg_10001",
"clientMsgId": "client_9f2a7c",
"conversationType": 1,
"targetId": "user2",
"channelId": "channel1",
"senderId": "user1",
"seq": 1024,
"timestamp": 1750000000000,
"content": {
"type": "text",
"text": "你好,IM PaaS"
},
"direction": 1
},
"operatorId": "user1",
"originalContent": {
"type": "text",
"text": "你好,IM PaaS"
}
}
}触发接口:message.recall
read.receipt
已读回执(按读者聚合;群聊含累计已读人数)。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
receipt | ReadReceipt | 是 | — |
senderId | string | 是 | — |
{
"eventType": "read.receipt",
"eventId": "evt_5b1c",
"appkey": "demo",
"timestamp": 1750000000000,
"nonce": "9f2a7c1b",
"data": {
"receipt": {
"conversationType": 1,
"targetId": "user2",
"readerId": "user2",
"messageIds": [
"msg_10001"
],
"readCounts": {
"user2": 0
},
"totalMembers": 1,
"timestamp": 1750000000000,
"readAt": 0,
"unreadCounts": {
"user2": 0
}
},
"senderId": "user1"
}
}user.registered
用户注册成功。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
user | User | 是 | — |
{
"eventType": "user.registered",
"eventId": "evt_5b1c",
"appkey": "demo",
"timestamp": 1750000000000,
"nonce": "9f2a7c1b",
"data": {
"user": {
"userId": "user1",
"name": "Alice",
"avatarUrl": "https://cdn.example.com/avatar/user1.png",
"createdAt": 1750000000000,
"banned": false
}
}
}user.banned
用户封禁/解封。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
userId | string | 是 | — |
banned | bool | 是 | — |
reason | string | 否 | — |
{
"eventType": "user.banned",
"eventId": "evt_5b1c",
"appkey": "demo",
"timestamp": 1750000000000,
"nonce": "9f2a7c1b",
"data": {
"userId": "user1",
"banned": false,
"reason": "policy_violation"
}
}触发接口:user.ban
group.created
群/超级群创建。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
group | Group | 是 | — |
operatorId | string | 是 | — |
{
"eventType": "group.created",
"eventId": "evt_5b1c",
"appkey": "demo",
"timestamp": 1750000000000,
"nonce": "9f2a7c1b",
"data": {
"group": {
"groupId": "group1",
"name": "产品群",
"groupType": "normal",
"owner": "user1",
"ownerName": "Alice",
"members": [
"user2"
],
"memberCount": 1,
"admins": [
"user1"
],
"muted": {
"user2": 0
},
"notice": "本周五 20:00 全员复盘",
"avatarUrl": "https://cdn.example.com/avatar/user1.png",
"createdAt": 1750000000000,
"maxMembers": 3000
},
"operatorId": "user1"
}
}触发接口:group.create
group.updated
群信息/成员/管理员/禁言变更。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
group | Group | 是 | — |
operatorId | string | 是 | — |
change | string | 是 | — |
detail | object | 否 | — |
{
"eventType": "group.updated",
"eventId": "evt_5b1c",
"appkey": "demo",
"timestamp": 1750000000000,
"nonce": "9f2a7c1b",
"data": {
"group": {
"groupId": "group1",
"name": "产品群",
"groupType": "normal",
"owner": "user1",
"ownerName": "Alice",
"members": [
"user2"
],
"memberCount": 1,
"admins": [
"user1"
],
"muted": {
"user2": 0
},
"notice": "本周五 20:00 全员复盘",
"avatarUrl": "https://cdn.example.com/avatar/user1.png",
"createdAt": 1750000000000,
"maxMembers": 3000
},
"operatorId": "user1",
"change": "string",
"detail": {}
}
}触发接口:group.update、group.members.add、group.members.remove、group.quit、group.admin.add、group.mute
group.destroyed
群解散。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
groupId | string | 是 | — |
operatorId | string | 是 | — |
{
"eventType": "group.destroyed",
"eventId": "evt_5b1c",
"appkey": "demo",
"timestamp": 1750000000000,
"nonce": "9f2a7c1b",
"data": {
"groupId": "group1",
"operatorId": "user1"
}
}触发接口:group.dismiss
chatroom.created
聊天室创建。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
chatroom | Chatroom | 是 | 聊天室:在线态会话,不落离线历史,默认只广播在线成员;保活/销毁由生命周期策略控制。 |
operatorId | string | 否 | — |
{
"eventType": "chatroom.created",
"eventId": "evt_5b1c",
"appkey": "demo",
"timestamp": 1750000000000,
"nonce": "9f2a7c1b",
"data": {
"chatroom": {
"chatroomId": "chatroom1",
"name": "活动聊天室",
"owner": "user1",
"memberCount": 1,
"onlineCount": 1,
"alive": false,
"createdAt": 1750000000000,
"destroyAt": 1750000000000
},
"operatorId": "user1"
}
}触发接口:chatroom.create
chatroom.destroyed
聊天室销毁。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
chatroomId | string | 是 | — |
reason | string | 是 | — |
{
"eventType": "chatroom.destroyed",
"eventId": "evt_5b1c",
"appkey": "demo",
"timestamp": 1750000000000,
"nonce": "9f2a7c1b",
"data": {
"chatroomId": "chatroom1",
"reason": "房间已销毁"
}
}触发接口:chatroom.destroy
chatroom.member_changed
聊天室成员变化(join/leave/kick)。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
chatroomId | string | 是 | — |
kind | ChatroomEventKind | 是 | — |
userId | string | 是 | — |
onlineCount | uint32 | 是 | — |
{
"eventType": "chatroom.member_changed",
"eventId": "evt_5b1c",
"appkey": "demo",
"timestamp": 1750000000000,
"nonce": "9f2a7c1b",
"data": {
"chatroomId": "chatroom1",
"kind": "joined",
"userId": "user1",
"onlineCount": 1
}
}user.online_status_changed
用户在线状态变更:WS 建连/断连时触发(等价于融云用户在线状态回调)。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
userId | string | 是 | — |
online | bool | 是 | — |
onlineCount | uint32 | 是 | — |
device | string | 否 | — |
timestamp | int64 | 是 | — |
{
"eventType": "user.online_status_changed",
"eventId": "evt_5b1c",
"appkey": "demo",
"timestamp": 1750000000000,
"nonce": "9f2a7c1b",
"data": {
"userId": "user1",
"online": false,
"onlineCount": 1,
"device": "string",
"timestamp": 1750000000000
}
}user.deactivated
用户注销(deactivate)成功后触发,既有 token 立即失效。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
userId | string | 是 | — |
deactivatedAt | int64 | 是 | — |
timestamp | int64 | 是 | — |
{
"eventType": "user.deactivated",
"eventId": "evt_5b1c",
"appkey": "demo",
"timestamp": 1750000000000,
"nonce": "9f2a7c1b",
"data": {
"userId": "user1",
"deactivatedAt": 1750000000000,
"timestamp": 1750000000000
}
}user.reactivated
用户恢复激活后触发。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
userId | string | 是 | — |
timestamp | int64 | 是 | — |
{
"eventType": "user.reactivated",
"eventId": "evt_5b1c",
"appkey": "demo",
"timestamp": 1750000000000,
"nonce": "9f2a7c1b",
"data": {
"userId": "user1",
"timestamp": 1750000000000
}
}user.profile_changed
用户资料变更(name 等)后触发。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
userId | string | 是 | — |
changed | object | 是 | — |
timestamp | int64 | 是 | — |
{
"eventType": "user.profile_changed",
"eventId": "evt_5b1c",
"appkey": "demo",
"timestamp": 1750000000000,
"nonce": "9f2a7c1b",
"data": {
"userId": "user1",
"changed": {},
"timestamp": 1750000000000
}
}friend.changed
好友关系变更:申请/接受/拒绝/删除。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
kind | "'request'|'accepted'|'rejected'|'deleted'" | 是 | IDL 中以示例值给出(非类型) |
fromUserId | string | 是 | — |
toUserId | string | 是 | — |
requestId | string | 否 | — |
timestamp | int64 | 是 | — |
{
"eventType": "friend.changed",
"eventId": "evt_5b1c",
"appkey": "demo",
"timestamp": 1750000000000,
"nonce": "9f2a7c1b",
"data": {
"kind": "request",
"fromUserId": "user1",
"toUserId": "user2",
"requestId": "req_7f3a9c",
"timestamp": 1750000000000
}
}chatroom.kv_changed
聊天室属性(KV)变更:设置/删除后触发,便于应用服务器同步。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
chatroomId | string | 是 | — |
kind | "'set'|'remove'" | 是 | IDL 中以示例值给出(非类型) |
changes | object | 是 | — |
timestamp | int64 | 是 | — |
{
"eventType": "chatroom.kv_changed",
"eventId": "evt_5b1c",
"appkey": "demo",
"timestamp": 1750000000000,
"nonce": "9f2a7c1b",
"data": {
"chatroomId": "chatroom1",
"kind": "set",
"changes": {},
"timestamp": 1750000000000
}
}message.modified
消息内容被修改(message.modify)后触发。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
messageId | string | 是 | — |
conversationType | ConversationType | 是 | — |
targetId | string | 是 | — |
senderId | string | 是 | — |
content | Content | 是 | 消息内容联合体,按 type 取字段;服务端校验序列化后 <=5KB(1007)。 |
timestamp | int64 | 是 | — |
{
"eventType": "message.modified",
"eventId": "evt_5b1c",
"appkey": "demo",
"timestamp": 1750000000000,
"nonce": "9f2a7c1b",
"data": {
"messageId": "msg_10001",
"conversationType": 1,
"targetId": "user2",
"senderId": "user1",
"content": {
"type": "text",
"text": "你好,IM PaaS"
},
"timestamp": 1750000000000
}
}message.deleted
历史消息被批量清理(message.history.clean)后触发。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
conversationType | ConversationType | 是 | — |
targetId | string | 是 | — |
scope | "'history'" | 是 | IDL 中以示例值给出(非类型) |
userId | string | 否 | — |
beforeTimestamp | int64 | 否 | — |
removed | uint32 | 是 | — |
timestamp | int64 | 是 | — |
{
"eventType": "message.deleted",
"eventId": "evt_5b1c",
"appkey": "demo",
"timestamp": 1750000000000,
"nonce": "9f2a7c1b",
"data": {
"conversationType": 1,
"targetId": "user2",
"scope": "history",
"userId": "user1",
"beforeTimestamp": 1750000000000,
"removed": 0,
"timestamp": 1750000000000
}
}message.audit_result
消息审核结果回推:管理端对内容审核任务做出裁决(approve/reject)后触发,供应用服务器同步处置结果。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
messageId | string | 是 | — |
conversationType | ConversationType | 是 | — |
targetId | string | 是 | — |
senderId | string | 是 | — |
decision | "'approve'|'reject'" | 是 | IDL 中以示例值给出(非类型) |
reason | string | 否 | — |
operator | string | 否 | — |
taskId | string | 是 | — |
timestamp | int64 | 是 | — |
{
"eventType": "message.audit_result",
"eventId": "evt_5b1c",
"appkey": "demo",
"timestamp": 1750000000000,
"nonce": "9f2a7c1b",
"data": {
"messageId": "msg_10001",
"conversationType": 1,
"targetId": "user2",
"senderId": "user1",
"decision": "approve",
"reason": "offline",
"operator": "string",
"taskId": "task_1",
"timestamp": 1750000000000
}
}message.publish.before
发送前同步回调:客户端消息投递前阻塞询问应用服务器(超时 syncTimeoutMs,默认 500ms)。应用服务器返回 {code:0,data:{action:"pass"|"replace"|"block",content?,reason?}},超时/故障按 syncDefaultAction 降级(默认放行)。该事件不会异步重试,每次发送最多一次同步调用。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
conversationType | int | 是 | — |
targetId | string | 是 | — |
senderId | string | 是 | — |
content | object | 是 | — |
timestamp | int64 | 是 | — |
{
"eventType": "message.publish.before",
"eventId": "evt_5b1c",
"appkey": "demo",
"timestamp": 1750000000000,
"nonce": "9f2a7c1b",
"data": {
"conversationType": 1,
"targetId": "user2",
"senderId": "user1",
"content": {},
"timestamp": 1750000000000
}
}投递日志
每次回调投递都会记录日志,可按时间范围、事件类型、状态查询;失败的投递可以手动重试一次。
deliveryIdeventTypeeventIdurlstatus(pending|success|failed|dropped)attemptsresponseCodeerrorlastAttemptAt
- 查询投递日志:callback.delivery.list(/callback/delivery/list)
- 手动重试:callback.delivery.retry(/callback/delivery/retry)
- 管理 API 也提供对应的投递日志与重试能力(见控制台/运营后台文档)。
测试工具
可在控制台配置回调地址后发送测试事件;也可自行搭建 webhook 接收器验证签名与重试。
回调配置页提供“发送测试事件”按钮,触发 eventType=test.ping(不计入契约事件,仅调试用)
回调配置接口
| 接口 | 方法 | 路径 | 说明 | 幂等 |
|---|---|---|---|---|
| 查询回调配置 | POST | /im/v2/callback/config/get | 查询回调配置(secret 脱敏)。 | 否 |
| 设置回调配置 | POST | /im/v2/callback/config/set | 设置回调配置:URL、事件订阅、签名密钥、重试策略、启停。 | 是 |
| 回调投递日志查询 | POST | /im/v2/callback/delivery/list | 回调投递日志查询(时间范围/事件类型/状态,分页)。 | 否 |
| 手动重试一次失败的回调投递 | POST | /im/v2/callback/delivery/retry | 手动重试一次失败的回调投递。 | 否 |
相关说明
- 鉴权与签名:请求头、签名算法与鉴权失败错误码
- 请求与响应数据格式:JSON 请求体、响应信封与分页
- 状态码与错误码:全部错误码、HTTP 状态与重试建议
- 限频、配额与容量限制:协议限制与分页上限
- API 接口列表:按模块检索全部接口