回撥概述
回撥是 IM 服務主動向你的業務服務端發出的 HTTP 請求,用於兩類場景:
- 事件回撥:應用內發生了變化(註冊了使用者、成為好友、群裡加了人、發了一條訊息、通話結束等)之後,把變化通知你的服務端。其中每條訊息的抄送叫訊息抄送。你可以據此同步業務資料、歸檔訊息、統計通話時長。
- 同步回撥:使用者在客戶端發訊息、加好友、加群之前,先詢問你的服務端是否允許。你的服務端可以放行、拒絕,或者改寫訊息的內容,例如只允許同一家公司的員工互相發訊息、給付費群做准入檢查。
你的服務端是接收方:在控制檯配置接收回撥的地址,實現一個 HTTPS 介面即可,不需要呼叫任何介面。
| 要做什麼 | 見 |
|---|---|
| 寫一個能跑的接收端(Python、Go、Node.js) | 驗證回撥請求;用 Go、Python、Node.js 的服務端 SDK 時已經內建 |
查某個事件什麼時候發、data 裡有什麼 | 事件回撥 |
| 在發訊息、加好友、加群之前由你決定放不放行 | 同步回撥 |
| 在控制檯新增地址、選擇事件、開啟同步回撥 | 回撥配置 |
| 瞭解重試、去重、積壓和自動停用 | 本頁:投遞與重試 |
兩種回撥
| 事件回撥 | 同步回撥 | |
|---|---|---|
| 傳送時機 | 變化已經提交之後,通常 1 到 2 秒內 | 操作寫入之前,操作方在等待你的響應 |
| 包括 | 使用者、登入、線上狀態、好友、群組、訊息(訊息抄送、撤回、編輯)、聊天室、音影片、內容安全、檔案的變化,見事件回撥 | 發訊息前、發聊天室訊息前、加好友前、加群前,見同步回撥 |
| 你的響應 | 返回任意 2xx 表示收到,響應內容被忽略,不能撤銷已經發生的變化 | 返回 2xx 和 JSON,決定放行、拒絕或改寫 |
| 失敗時 | 按退避間隔自動重試,最長 24 小時,之後可以手動重新投遞 | 只請求一次,不重試;超時或出錯時按你的設定放行或拒絕 |
| 可靠性 | 至少一次,可能重複,不保證順序。線上狀態、聊天室的訊息抄送和撤回為盡力而為,見盡力而為的事件 | 每次操作請求一次;客戶端重試同一個操作時會再次請求 |
| 訂閱方式 | 每個回撥地址選擇要接收的事件 | 每種同步回撥選擇一個回撥地址並開啟 |
建議按以下順序接入:
- 部署接收端:公網可以訪問的 HTTPS 地址,先實現簽名校驗和下文的 URL 驗證;
- 在控制檯新建回撥地址,儲存只顯示一次的簽名金鑰。儲存後系統立即向地址傳送一次 URL 驗證,透過後地址變為“正常”;
- 用控制檯的“測試傳送”調通簽名校驗和事件的處理;
- 訂閱需要的事件;需要同步回撥的,先以“失敗時放行”開啟,在控制檯觀察一段時間的耗時和失敗次數,確認接收端穩定後再按需要改為“失敗時拒絕”。
回撥地址
每個應用最多可以配置 5 個回撥地址。每個地址有自己的簽名金鑰、訂閱的事件、過濾條件和傳送速度的設定,互不影響:同一個事件被兩個地址訂閱時,兩個地址各自投遞、各自重試。常見的用法是一個地址接收業務事件,一個地址接收量大的訊息抄送並開啟合併,同步回撥再單獨用一個響應快的地址。
| 設定 | 預設值 | 說明 |
|---|---|---|
| 名稱 | — | 1 到 64 個字元,用於在控制檯中區分地址 |
| URL | — | 接收回撥的地址,規則見地址的要求 |
| 訂閱的事件 | — | 1 到 50 項,寫完整的事件型別或 group.* 這樣的字首,見訂閱事件 |
| 過濾條件 | 不過濾 | 不接收由你的服務端引起的事件、不接收刪除賬號連帶的事件等,見過濾條件 |
| 每批事件數 | 1 | 一次請求最多合併的事件數,1 到 100,見一次請求中的多個事件 |
| 超時 | 5000 毫秒 | 等待事件回撥響應的時間,1000 到 10000 毫秒 |
| 併發上限 | 10 | 同時進行的事件回撥請求數,1 到 50 |
| 每秒請求數上限 | 100 | 1 到 1000,不能超過應用的上限(預設每秒 500 個請求,全部地址合計,由平臺設定) |
超過併發和速率上限的事件在佇列中等待,只是晚一些發出,不會丟失。修改設定通常在 1 秒內生效,最遲 30 秒。
地址的狀態
| 狀態 | 含義 | 事件回撥 | 同步回撥 |
|---|---|---|---|
| 待驗證 | 剛新建或剛修改了 URL,還沒有透過 URL 驗證 | 不生成投遞,這期間的事件不會補發 | 不發請求,按失敗處理 |
| 正常 | 透過了 URL 驗證 | 正常投遞 | 正常呼叫 |
| 已暫停 | 你在控制檯暫停了它,適合接收端停機維護 | 照常排隊、不傳送,恢復後繼續傳送 | 照常呼叫 |
| 自動停用 | 持續失敗 24 小時後被系統停用,見持續失敗與自動停用 | 不生成新的投遞,已排隊的不再傳送 | 照常呼叫 |
| 平臺停用 | 地址被平臺停用(通常是指向了不屬於你的第三方),只有平臺可以恢復 | 不生成新的投遞 | 不發請求,按失敗處理 |
地址的狀態主要控制事件回撥。同步回撥繫結的地址處於“待驗證”或“平臺停用”時不發請求,按這種同步回撥的失敗策略處理,見失敗時的處理;“已暫停”和“自動停用”不影響同步回撥。
地址的要求
IM 服務替你向外發出請求,為了防止地址被用來探測平臺的內網或攻擊第三方,回撥地址必須滿足:
- 只允許
https,證書必須有效(不能是自簽名證書、過期證書或與域名不符),TLS 1.2 以上; - 主機必須是公網的域名:不能是 IP 地址(包括十進位制、十六進位制等變體寫法),也不能是
localhost、沒有點的單個名稱、以.local、.internal、.localhost結尾的名稱,以及平臺禁止的域名; - 埠只能是 443 或 1024 到 65535;
- 不能帶使用者名稱和密碼(
user:pass@),不能帶#片段;可以帶查詢引數;最長 1024 個字元; - 儲存時域名解析出的全部 IP 都必須是公網地址;每次傳送請求時,還會檢查實際要連線的 IP,不是公網地址的放棄連線,記為失敗(
blocked_address),因此域名在儲存之後改為解析到內網地址也無法使用; - 不跟隨重定向:返回
3xx一律記為失敗(redirect)。
不允許的地址包括迴環地址、私有網段(10.0.0.0/8、172.16.0.0/12、192.168.0.0/16)、鏈路本地地址(169.254.0.0/16,含雲伺服器的後設資料地址)、運營商級 NAT(100.64.0.0/10)、組播、保留和文件網段,以及 IPv6 中對應的地址。接收端與你的其他服務在同一內網時,請透過公網的 HTTPS 地址(如反向代理、負載均衡)接收回撥。
URL 不符合要求時,儲存返回 400 invalid_argument,details.reason 為 url_not_allowed,details.rule 指出原因:
rule | 原因 |
|---|---|
scheme | 不是 https |
host | 主機是 IP 地址或內網名稱,或 URL 格式不對 |
port | 埠不是 443 或 1024 到 65535 |
userinfo | 帶了使用者名稱和密碼 |
fragment | 帶了 # |
length | 超過 1024 個字元 |
denied_host | 平臺禁止使用的域名 |
disabled_host | 這個主機名上有被平臺停用的回撥地址 |
private_address | 域名解析到了非公網地址 |
resolve_failed | 域名解析失敗 |
masked_query | 查詢引數的值是 ***,見下文 |
查詢引數中的令牌:可以把接收端自己的令牌放在 URL 的查詢引數中(如 ?token=xxx)。控制檯、操作日誌和平臺中顯示的 URL 會把每個查詢引數的值替換為 ***,只保留引數名,所以修改 URL 時必須填寫完整的新 URL,不能把顯示的 URL 原樣提交。
URL 驗證
新建地址和修改 URL 後,地址處於“待驗證”,系統立即向它傳送一次 URL 驗證請求,證明這個地址由你控制。地址必須在 5 秒內返回 2xx,響應體為 JSON 物件,其中 challenge 與請求中的值相同(可以有其他欄位,最多讀取 4 KB)。透過後地址變為“正常”,開始投遞事件,也可以繫結同步回撥。
請求示例(X-IM-Kind 為 verify):
POST /im/callback HTTP/1.1
Host: your-server.example.com
User-Agent: IM-Callback/1
Content-Type: application/json; charset=utf-8
X-IM-Kind: verify
X-IM-Request-Id: 100310966629040128
X-IM-Endpoint-Id: 100310966557736960
X-IM-Timestamp: 1791141598
X-IM-Nonce: c90a695d9294c5f757dd88350e0e3187
X-IM-Signature: v1=2e5cebe9c6c67af85d62c4cc0250579223acc3e89549eabf036d3c523cb08610
{"kind":"verify","request_id":"100310966629040128","app":"6195295143#doc-callback","endpoint_id":"100310966557736960","sent_at":"2026-10-04T19:19:58.132Z","challenge":"J9Kk_hKp_H74ZScznGSzU_-kL8b4r1MTACmzTFsNVP4"}你的接收端校驗簽名後返回:
{ "challenge": "J9Kk_hKp_H74ZScznGSzU_-kL8b4r1MTACmzTFsNVP4" }challenge 每次都不同。驗證失敗時(超時、連線失敗、返回非 2xx、challenge 不對),地址保持“待驗證”,修好接收端後在控制檯點選“驗證”重新傳送。URL 驗證和測試傳送每個應用合計每分鐘最多 20 次,新建地址、修改 URL 後自動發起的驗證也計入;額度用完時地址照常儲存,只是不傳送驗證,稍後再點選“驗證”即可。
出口 IP
平臺可以讓回撥請求從固定的出口 IP 發出,控制檯的“回撥”頁會列出這些 IP,你可以在接收端的防火牆上只允許它們訪問。沒有列出出口 IP 時,請求的來源 IP 不固定,請只依靠簽名確認請求的來源。
請求格式
全部回撥都是 POST 請求,請求體為 UTF-8 編碼的 JSON。
請求頭
| 請求頭 | 說明 |
|---|---|
Content-Type | application/json; charset=utf-8 |
User-Agent | IM-Callback/1 |
X-IM-Kind | 請求的種類:event 事件回撥,hook 同步回撥,verify URL 驗證 |
X-IM-Hook | 同步回撥的型別,如 message.before_send,只在 X-IM-Kind 為 hook 時有 |
X-IM-Request-Id | 請求 ID,每次傳送都不同(重試也不同),與控制檯請求記錄中的相同,用於排查問題 |
X-IM-Endpoint-Id | 回撥地址的 ID |
X-IM-Timestamp | 傳送時的 Unix 時間,單位為秒 |
X-IM-Nonce | 隨機數,32 個十六進位制字元,每次傳送都不同 |
X-IM-Signature | 簽名,v1= 加 64 個十六進位制字元;輪換金鑰的寬限期內為兩個,用逗號分隔,見簽名演算法 |
HTTP 請求頭不區分大小寫,有的框架會把它們顯示為 X-Im-Kind 或 x-im-kind。請求的種類、請求 ID 和地址 ID 在請求體中也有,受簽名保護;請求頭中的副本只用於不解析請求體就能分流,業務判斷請以請求體為準。
請求體
三種請求的請求體都有以下公共欄位:
| 欄位 | 型別 | 說明 |
|---|---|---|
kind | String | event、hook 或 verify,與請求頭 X-IM-Kind 相同 |
request_id | String | 請求 ID,與請求頭 X-IM-Request-Id 相同 |
app | String | 應用的 AppKey,如 6195295143#demo。多個應用共用一個接收端時據此區分 |
endpoint_id | String | 回撥地址的 ID |
sent_at | String | 傳送時間,見時間格式 |
事件回撥另有 events 陣列,同步回撥另有 hook、timeout_ms、test、data(見同步回撥),URL 驗證另有 challenge。
事件回撥的請求示例,有人加入了群:
POST /im/callback HTTP/1.1
Host: your-server.example.com
User-Agent: IM-Callback/1
Content-Type: application/json; charset=utf-8
X-IM-Kind: event
X-IM-Request-Id: 100311905645625344
X-IM-Endpoint-Id: 100310966557736960
X-IM-Timestamp: 1791141822
X-IM-Nonce: 36e818ae8f4cd9fdaa24db6ca521475d
X-IM-Signature: v1=f7be9e46c15142401297376147fdea08c762c6ca7364...{
"kind": "event",
"request_id": "100311905645625344",
"app": "6195295143#demo",
"endpoint_id": "100310966557736960",
"sent_at": "2026-10-04T19:23:42.011Z",
"events": [
{
"id": "100311903162597376",
"type": "group.members_added",
"occurred_at": "2026-10-04T19:23:41.419Z",
"attempt": 1,
"test": false,
"data": {
"origin": "client",
"group_id": "100311903108071424",
"member_version": 2,
"member_count": 2,
"members": [
{
"username": "carol",
"role": "member",
"joined_via": "create",
"inviter": "alice",
"joined_at": "2026-10-04T19:23:41.409Z"
}
],
"operator": "alice"
}
}
]
}events 中每一項是一個事件:
| 欄位 | 型別 | 說明 |
|---|---|---|
id | String | 事件 ID。同一個事件的每次重試、重新投遞都相同;一個事件被多個地址訂閱時,每個地址收到的事件 ID 也相同。按它去重 |
type | String | 事件型別,如 group.members_added,見事件一覽 |
occurred_at | String | 事件發生的時間 |
attempt | Number | 這個事件是第幾次傳送,從 1 開始;重新投遞後從 1 重新計數 |
test | Boolean | 是否為控制檯的測試傳送。測試事件使用示例資料,接收端可以據此不執行業務操作 |
data | Object | 事件的內容,欄位因事件型別而不同,見事件回撥 |
一次請求中的多個事件
不論是否合併,事件都放在 events 陣列中。地址的“每批事件數”大於 1 時,一次請求最多帶這麼多個已經到期的事件,請求體不超過 1 MB;同一批中的事件不保證按發生的先後排列。合併的請求整體成功或整體失敗:返回非 2xx 時,這一批的全部事件都會重試。第 3 次及之後的傳送不再合併,每個事件單獨傳送,避免一個處理出錯的事件拖住同批的其他事件。
訊息抄送這類量大的事件建議開啟合併。例如一個每分鐘 3 萬條訊息的應用,每秒約 500 個事件,每批 100 個時每秒只需約 5 個請求。
data 的寫法
- 使用者用使用者名稱錶示,不出現內部的使用者 ID;已刪除的使用者為
null。 - ID 都是字串,如
group_id、message_id、call_id。 - 時間為固定三位毫秒的 UTC 時間,如
2026-10-04T19:23:41.409Z。例外是登入會話的時間(user.session_created的created_at、user.sessions_revoked的revoked_before、user.relogin_required的sessions_revoked_before),為六位微秒,如2026-10-04T19:21:11.735215Z,比較先後時不要截斷。 - 訊息的
body、ext按原樣放入,格式見訊息格式。 - 以後會增加欄位和事件型別,接收端必須忽略不認識的欄位和事件型別,不要因此返回錯誤。
- 大小:一個事件的
data不超過 70 KB。萬一超出,會去掉其中的內容和列表類的欄位(如body、ext、members),只保留 ID、使用者名稱、型別、狀態、原因、版本號和時間類的欄位,並加上"truncated": true。收到這樣的事件時,請按其中的 ID 用 OpenAPI 查詢當前的狀態。
origin:事件由誰引起
每個事件的 data 都以 origin 開頭,說明這次變化由誰引起:
origin | 含義 |
|---|---|
client | 使用者在客戶端的操作,如使用者自己發訊息、加好友、退群 |
server | 你的服務端(OpenAPI)或你在控制檯的操作,以及你的稽核人員作出結論的內容安全處置 |
platform | 平臺管理員的操作,如平臺遮蔽了檔案、封禁了使用者 |
system | 系統自動執行的操作,如刪除賬號後清理他的好友和群成員身份、內容安全的自動處置、通話結束後寫入的通話記錄訊息 |
null | 只有 presence.changed(線上狀態)沒有來源 |
由你的服務端引起的變化,結果你本來就知道。可以為地址開啟過濾條件“不接收由租戶服務端引起的事件”,不再接收 origin 為 server 的事件(撤回事件除外),見過濾條件。
響應
| 請求 | 成功的條件 | 讀取的響應體 |
|---|---|---|
| 事件回撥 | 在地址的超時時間(預設 5 秒)之內返回任意 2xx,這一批事件都算投遞成功 | 響應體被忽略,最多讀取 16 KB |
| URL 驗證 | 5 秒之內返回 2xx,響應體中有正確的 challenge | 最多 4 KB,超出算失敗 |
| 同步回撥 | 在等待時間之內返回 2xx 和合法的 JSON,見同步回撥的響應 | 最多 64 KB,超出算失敗 |
其他情況都算失敗:返回 3xx(不跟隨重定向)、4xx、5xx,超時,連線失敗,TLS 握手失敗。簽名校驗不透過時,建議返回 401。
接收事件回撥時請先校驗簽名、按事件 ID 去重並儲存事件,立即返回 2xx,再在你自己的佇列中非同步處理。在回撥請求中執行耗時的業務邏輯會讓請求超時,事件被當作失敗重發,接收端的壓力隨之變大。
投遞與重試
重試
事件回撥失敗後按以下間隔重試,每次的間隔隨機增減 20%,避免大量事件同時重試:
| 第幾次失敗後 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 等待 | 10 秒 | 30 秒 | 1 分鐘 | 5 分鐘 | 15 分鐘 | 30 分鐘 | 1 小時 | 2 小時 | 4 小時 | 8 小時 | 8 小時 |
- 接收端返回
429或503並帶Retry-After響應頭(秒數或 HTTP 日期)時,取它(最長 1 小時)與上表的間隔中較大的一個。接收端維護時可以返回503和Retry-After,讓事件晚一些再來。 - 事件從排隊起 24 小時內仍未成功的轉為投遞失敗,最多傳送 12 次。投遞失敗的事件保留 7 天,可以在控制檯重新投遞。
- 每次重試都是新的請求:請求 ID、時間戳、隨機數和簽名都重新生成,事件 ID 不變,
attempt加 1。
熔斷:一個地址連續失敗 5 次後暫停傳送 30 秒,之後先放行一個請求試探:試探成功則恢復,失敗則暫停的時間加倍,最長 5 分鐘。暫停期間事件照常排隊,不計入傳送次數。接收端整體故障時,不會被大量註定失敗的請求持續衝擊。
重複與亂序
- 至少一次:同一個事件可能收到不止一次(例如接收端其實已經處理、但沒有在超時之內返回)。請按事件
id去重,去重記錄至少保留 24 小時;需要重新投遞幾天前失敗的事件時,保留 7 天更穩妥。 - 不保證順序:重試中的事件可能晚於之後發生的事件到達,同一批中的事件也不按先後排列。請以事件中的版本號(如
friend.added的version、group.members_added的member_version、presence.changed的version)和occurred_at判斷新舊,舊事件不要覆蓋新狀態。 - 事件是發生時的快照:例如
friend.added中的暱稱是成為好友那一刻的,之後修改了暱稱不會更新已經排隊的事件。需要當前狀態時用 OpenAPI 查詢。
盡力而為的事件
線上狀態(presence.changed)、聊天室的訊息抄送(chatroom.message_sent)和撤回(chatroom.message_recalled)量很大,先在服務端的記憶體中攢約 1 秒再批次寫入投遞佇列。服務例項在這期間崩潰或緩衝已滿時,這些事件會丟失,不會補發。寫入投遞佇列之後的投遞、重試與其他事件相同。需要準確的狀態時,請用 OpenAPI 查詢(如線上狀態)。
積壓上限
每個地址待投遞的事件最多 50 萬條。接收端長時間不可用、積壓達到上限後,新的事件不再排隊,直接丟棄,控制檯的統計中計入“丟棄”,並通知租戶的 owner 和 admin(每 24 小時最多一次)。一個每秒 500 個事件的地址宕機約 17 分鐘就會積滿,量大的地址請開啟合併、提高併發。丟失的訊息抄送可以用匯出應用的訊息補齊。
持續失敗與自動停用
- 一個地址從最近一次成功之後開始失敗,持續 1 小時仍沒有一次成功時,系統通知租戶的
owner和全部admin(有已驗證郵箱的發郵件,否則發簡訊),提示 24 小時後將自動停用; - 持續 24 小時仍沒有一次成功、且最近 1 小時內仍有失敗的請求時,地址自動停用:不再為它生成新的投遞,已排隊的不再傳送(到期後轉為投遞失敗),並再次通知。
- 只有事件回撥的失敗計入;同步回撥、URL 驗證和測試傳送的失敗不會讓地址被提醒或停用。暫停期間不傳送,不算作失敗。
修好接收端後,在控制檯點選“恢復”:系統先傳送一次 URL 驗證,透過後地址恢復正常,再重新投遞停用前投遞失敗的事件。停用期間發生的事件沒有排隊,不會補發,需要時用 OpenAPI 或匯出應用的訊息補齊。
投遞記錄與重新投遞
控制檯的回撥地址詳情中可以檢視:
| 記錄 | 內容 | 保留 |
|---|---|---|
| 投遞佇列 | 每個事件對這個地址的投遞:狀態(待投遞、已投遞、失敗)、傳送次數、最近的錯誤和狀態碼;可以按事件型別篩選、按事件 ID 查詢,檢視事件的 data(訊息類事件的 body、ext 不顯示) | 投遞成功的 1 小時,投遞失敗的 7 天 |
| 請求記錄 | 每次發出的請求:種類、結果、狀態碼、耗時、對方的 IP,失敗時還有錯誤原因和響應的開頭部分。URL 驗證、測試傳送、重新投遞和失敗的請求基本全部記錄,成功的請求按抽樣記錄;不記錄事件內容 | 7 天 |
| 統計 | 按小時統計的請求數、失敗數、超時數、耗時、事件從發生到投遞成功的延遲,每種事件的排隊、成功、失敗、丟棄數 | 90 天 |
重新投遞:投遞失敗的事件可以在控制檯逐個,或按失敗的時間段(最長 7 天,可以只選某些事件型別)批次重新投遞,每次最多 1 萬個,沒處理完的再提交一次。重新投遞的事件回到待投遞,傳送次數清零,24 小時的期限重新計算。地址必須是“正常”或“已暫停”(暫停時排隊,恢復後傳送);自動停用的地址要先恢復。
訊息內容的保留:投遞成功後,投遞佇列中的訊息內容隨即清除;投遞失敗的訊息類事件,24 小時後清除其中的 body、ext。之後重新投遞時,系統按訊息 ID 查詢訊息的當前內容:訊息已被撤回、擦除或已過期的,事件中的 body、ext 為 null,content_available 為 false。聊天室的訊息不長期儲存,清除之後無法再查到內容。
不回撥的情況
- 租戶被暫停(包括欠費暫停)、處於登出冷靜期,應用被停用或被平臺暫停服務期間,不生成新的投遞,這期間發生的事件不回撥,恢復後也不補發;已經排隊的暫停傳送,恢復後繼續,排隊超過 24 小時的轉為投遞失敗。應用處於只讀狀態時照常回撥。見應用和租戶的狀態。
- 刪除回撥地址後立即停止投遞,排隊的事件和投遞記錄隨後刪除,不能恢復。
- 應用刪除、租戶登出後停止全部回撥。
頻道回撥
獨立頻道有 9 種 rtc_channel.* 事件,覆蓋頻道的建立、更新、關閉,會話的加入、退出,以及封禁的變化。簽名、重試、合併和去重與本頁相同,欄位見頻道事件。入會票據由你的服務端簽發,所以沒有“加入頻道前”的同步回撥。
