Engineering

為什麼 IG/FB 的留言轉私訊會失敗?

ViralArc 小編 2026年8月26日 読了 2 分

為什麼 IG/FB 的留言轉私訊會失敗?

如果你做過留言轉私訊,大概遇過這個情況:自動化設定得好好的,關鍵字也對上了,公開回覆確實留在貼文下面,但有些人就是沒有收到私訊。

更難受的是它看起來毫無規律。同一篇貼文、同一段文案、同一個小時之內,A 收到了、B 沒收到。你反覆檢查設定、換掉文案、把連結拿掉再試一次——問題照樣間歇出現。

ViralArc 每天處理大量 Instagram 與 Facebook 的留言 webhook,這個問題我們躲不掉,也不能只用「平台有時候就是這樣」交代過去。於是我們把每一次投遞的結果變成可以分析的紀錄,累積數日之後回頭做統計,再對照國外開發者社群與各家平台的公開整理。這篇文章講我們找到的答案。

想快速解決?先照這個順序檢查

如果你現在正被這個問題卡住,這四步能處理掉大部分情況。

第一步,確認不是你的設定問題。 有一個很容易漏掉的項目:帳號擁有者可能在 Instagram 的「設定與隱私 → 訊息與限時動態回覆」裡關掉了第三方應用的訊息存取權限,這會讓所有自動私訊直接失效。

第二步,確認留言還在窗口內。 官方的限制是七天,而且是從留言發生的那一刻起算,跟你的系統什麼時候處理到它無關。若是直播的留言,那麼只能在直播進行中回覆,直播結束就永久失效。同一則留言也只能私訊一次,重複觸發不會送出第二封。

第三步,如果只有一部分人收不到,多半是對方的設定。 Instagram 的訊息邀請控制在「設定與隱私 → 訊息與限時動態回覆 → 訊息控制」,使用者可以針對「其他 Instagram 用戶」這類來源選擇直接進聊天、進訊息邀請匣、或完全不接收邀請。選了最後一項的人,不管你用任何工具,對方都送不到。

還有一種情況特別棘手:青少年帳號。Instagram 自 2024 年起把未滿 16 歲的使用者放進最嚴格的訊息設定,只有已追蹤或已建立連結的人可以傳訊給他們,而且未滿 16 歲要放寬設定必須經過家長同意。品牌帳號想私訊一個沒追蹤你的青少年,訊息不會送達。

實務上能做的,是把「先追蹤」設計進活動流程(追蹤之後就形成連結關係),並在文案裡呼籲大家「先追蹤」。

第四步,如果是整批都收不到,那跟單則留言無關。 這種情況通常是帳號層級的限制或事件根本沒送到你的系統,繼續檢查單一設定沒有意義;如果之前曾經可以,現在突然整批收不到,那或許等個幾天就好了。

一個簡單的判準:同一篇貼文底下如果有人收到、有人沒收到,你的設定就是對的,差異在收件人那一側。這時候與其繼續 debug,不如把心力放在測量上——接下來就是我們測量的過程。

失敗分成四層

把自己的資料和公開資料合起來看,留言轉私訊的失敗大致落在四層,由外而內:

  1. 留言根本沒送到你的系統——事件遺失,你的紀錄裡看不到。
  2. 文件寫明的硬限制——超過七天、重複回覆、直播已結束。這一層靠正確實作可以完全避免。
  3. 收件人的資格狀態——最強的預測因子,會隨時間改變。
  4. 貼文與受眾組成——真實存在、每天穩定重現。

多數人只在第二層找答案,因為那是唯一寫在文件上的;然而,Meta 的系統有著各種限制,就像一個看不清的黑盒子一樣。以下,讓我們逐層拆開,看看可能的問題會在哪裡。

第一層:留言根本沒送到你這裡

先講最容易被忽略的一層,因為它會影響後面所有數字的解讀。

留言轉私訊的觸發完全依賴 Meta 的留言 webhook。如果事件沒送到,什麼都不會發生。而根據公開整理,Meta 的留言 webhook 自 2025 年底以來投遞並不穩定,並且對所有自動化平台一視同仁。

這一層在自己的系統裡是隱形的:沒有事件,就沒有紀錄,連分母都進不去。所以後面所有失敗率,分母都只包含「已經收到 webhook 的留言」;真正的端到端漏失還要再加上這一層。想量它,只能反過來定期去平台拉留言清單,跟自己處理過的紀錄比對,差集就是漏失。

第二層:文件寫明的必然失敗

這一層有標準答案,只是很多人沒讀到。留言轉私訊用的是 Meta 官方的「私密回覆」能力:當有人在專業帳號的貼文、Reels、限時動態或直播下留言,平台送出一個帶有留言識別碼的 webhook,應用程式接著可以針對那則留言送出一則私訊。

POST /{ig-user-id}/messages
recipient.comment_id = {comment-id}
message = { 文字或支援的模板 }

官方文件載明的限制,每一條都對應一種必然的失敗:

  • 一則留言只能私密回覆一次。 重複觸發同一則留言不會送出第二封。
  • 必須在留言建立後七天內送出。 超過就永久失效,事後補送也沒有用。
  • 直播的留言回覆必須在直播還在進行時送出。 直播結束後補送,一律失敗。
  • 在對方回覆之前不能再送後續訊息。 這條通道只給你一次發言機會;要等對方回話,才進入一般的 24 小時訊息窗口。

訊息會落在哪裡也由關係決定:對追蹤者進收件匣,對非追蹤者進「訊息邀請」。這個細節在第三層會變得很重要。

七天窗口有個容易踩的坑:時間從留言的建立時間起算,跟 webhook 什麼時候抵達無關。佇列一旦塞住、或重試排程拉得太長,可能在留言看起來還很新的時候就把窗口燒掉了。因此,過期判定要以留言本身的時間戳為準,建議事件被排入列時就先去算好到期時刻。

這裡還有一個容易忽略但很關鍵的細節:這支 API 的收件人是一則留言。整個語意都圍繞著「針對這則留言做出一次回應」打轉,裡面沒有「傳訊息給某個人」這種概念。後面所有現象都從這個設計長出來。

如果你的失敗都能歸到上面幾條,問題其實已經解決了。麻煩的是第三層。

第三層:合法的請求也會被拒

Instagram 在這種情況下最常回的是 code 100 / subcode 2534025。它的中文訊息大意是要你確認「在私密回覆中傳送的資料類型符合規定」。

字面讀起來,這是一個資料格式問題——聽起來像訊息模板結構、欄位型別或連結格式有錯。起初,ViralArc 的我們也是往這個方向調查的,花了時間去檢視模板結構、按鈕數量、網址格式。

但是,實際的資料很快否定了這條路。統計某個帳號底下所有啟用中的留言自動化貼文,結果是:

  • 超過六成的貼文都至少遇過一次這個錯誤。
  • 但沒有任何一篇貼文是全軍覆沒——每一篇遇過失敗的貼文,同時也都成功送出過私訊,而且經常就在同一天。

如果是格式錯誤,結果應該是決定性的:同樣的內容送出去,要嘛一直成功、要嘛一直失敗。成功與失敗在同一篇貼文、同一段文案上交錯出現,代表變數在收件人或留言本身的資格上。

國外開發者社群整理的錯誤碼清單佐證了這個方向。那份整理把 2534025 的成因列成四種:留言超過七天、留言已被刪除、同一則留言已經私訊過、以及留言者的隱私設定擋掉了訊息邀請。前三種在我們的資料裡都排除了——都是新收到的留言、都是第一次處理、留言也都還在。剩下的第四種,正好就是統計指向的方向。那份整理的措辭很直接:這屬於 Instagram 的硬性隱私規則,任何工具都無法覆蓋。

順帶說明一件事,以免以下的數字被誤讀:留言轉私訊是 Meta 官方 API 提供的能力,市面上所有做這件事的產品打的都是同一支端點,受同一套資格判定約束。以下觀察到的分布屬於這支 API 的性質;各家實作的差別只在於有沒有把它量出來、以及在介面上怎麼跟使用者交代。

要回答這個問題,得先有能回答的資料

這是整件事最實際的一課:當時我們的紀錄根本不足以回答問題。錯誤訊息有存,但缺少可以交叉比對的維度,於是第一步變成補資料,修 bug 反而排在後面。

我們替每一次投遞嘗試留下一筆紀錄,內容大致分成三組。

第一組是身分:這次是替哪個帳號、針對哪一則留言、第幾次嘗試。 留言的識別碼在這裡有雙重用途——它既是分析用的欄位,本身也是天然的冪等鍵。既然平台規定一則留言只能私訊一次,「這則留言處理過了沒有」就已經是一個不會重複的判斷依據,不用另外設計一套去重機制。

第二組是結果,而且要分兩層記。 表層是平台回了什麼:錯誤碼與子碼要分開存成數值欄位,成功時則把平台給的訊息 ID 留下來當送達證明。這裡有個容易踩的坑——很多人只存一段錯誤訊息字串就交差,但平台的本地化訊息會隨語系和時間改寫,同一個問題在不同語系下長得完全不一樣,事後根本沒辦法分群統計。真正穩定的識別是那組數字。

裡層則是我們自己的判斷:這次結果能不能再送一次。我們分成「已送達」「終局拒絕」「結果不明」「可安全重試」幾類(後來又從中拆出「憑證失效」,因為那一類要停掉整個帳號的工作)。這個分類軸和「這是什麼錯」不一樣——後者是平台的視角,前者才是程式當下必須立刻回答的問題,所以要在寫入時就判斷完並存下來。

第三組是當下的情境條件,也就是後來真正解謎的部分。 包括這個收件人先前的紀錄(首次接觸、曾經成功、曾經失敗)、留言發生多久了、貼文本身多舊、送出的是純文字還是按鈕模板、帶了幾個按鈕、連結指向哪一類網域,以及訊息內容的指紋。

這一組有兩個決定事後看來特別關鍵。收件人歷史要在寫入當下就算好,不要留到事後再回頭 join——當時只是覺得順手,後來證明這是整份分析裡最有價值的一欄。連結和內容都不存原文:追蹤網址帶有活動與使用者參數,屬於敏感資料,但我們仍然需要回答「有沒有連結會不會影響失敗率」這類問題,所以只留網域類別與內容雜湊,既能分群,也不會留下不該留的東西。

補完這些欄位、累積數日之後,答案才開始浮現。

數字把矛頭指向收件人:過往的私訊歷史是最強的預測因子

把每一次投遞按「這個收件人先前的紀錄」分組,結果是整份追查裡最清楚的訊號:

該收件人先前的紀錄後續失敗率
首次接觸約 22%
曾經成功收到過約 2%
曾經失敗、且從未成功過約 55%

差距超過一個數量級。同一段文案、同一個帳號、同一支 API,只因為對方先前是否曾經成功收到過,後續成功機率就天差地遠。

把這張表和上一節的隱私設定放在一起看,形狀就對上了:「曾經成功收到過」的人,等於已經證明自己的設定允許陌生帳號的訊息邀請,之後自然幾乎都還送得到;「曾經失敗過」的那一組有將近一半會恢復,則對應到設定被改動、或雙方之間建立了追蹤關係。

實務上有一個容易做錯的推論:不要因為一次失敗就永久排除一個收件人。把他們加進黑名單,等於主動丟掉大量本來送得到的訊息。同一個人這次送不到,過幾天可能就送得到了。

第四層:不同貼文的失敗率,可以差到七倍

收件人歷史解釋了很多,卻沒有解釋全部。把失敗率按貼文拆開:

同一帳號底下,各貼文的私訊失敗率
貼文 A  ~33%  █████████████████
貼文 B  ~28%  ██████████████
貼文 C  ~17%  █████████
貼文 D  ~16%  ████████
貼文 E   ~9%  █████
貼文 F   ~8%  ████
貼文 G   ~5%  ██

第一個反應當然是「這只是抽樣噪音」。兩件事否定了這個解釋:信賴區間不重疊——最高那一組和最低那幾組的 95% Wilson 區間完全分開,這樣的差距已經超出二項抽樣能解釋的範圍;而且每天都重現——失敗率最高的那篇貼文,連續六天的日失敗率都落在兩成四到四成之間,沒有一天回到帳號平均。

同時,這些貼文的自動化設定幾乎完全一致:同樣的觸發模式、同樣的固定回覆文案、沒有關鍵字篩選、沒有 AI 過濾、同樣的媒體類型、留言功能都正常開啟。設定相同、結果卻穩定地不同,變數只能在設定之外。

更關鍵的是:即使只看首次接觸的收件人(也就是排除掉上一節的歷史效應),不同貼文之間的失敗率仍然從個位數到將近四成都有。

這一點我們自己的資料解釋不了,但外部資訊提供了一個很合理的機制:青少年帳號。Instagram 把未滿 16 歲的使用者預設放進最嚴格的訊息設定,非追蹤者無法傳訊,而且放寬設定需要家長同意。這代表青少年比例高的受眾,會有一個結構性的、無法靠設定調整消除的失敗率底線。

不同貼文吸引到的受眾年齡分布本來就不一樣——一篇內容偏年輕向的貼文,留言者裡青少年帳號的比例自然更高。這正好能解釋我們看到的兩個特徵:差異穩定(受眾組成不會天天變)、以及首次接觸族群的失敗率在貼文之間差很大(因為那正是受眾組成直接作用的地方)。

必須說清楚:這只是一個和資料相符的假說,我們並沒有證實它——留言者的年齡我們看不到,也不該看得到。但它是目前唯一能同時解釋「穩定」與「集中在特定貼文」的機制。對經營者來說,實務含意很明確:如果你的受眾偏年輕,留言轉私訊的天花板本來就比較低,把「先追蹤」設計進流程會比調整文案有效得多。

三個被資料推翻的解釋

比起找到原因,排除錯誤的原因同樣重要。有三個當時看起來很合理的解釋,最後都被自己的資料推翻。

「是不是連結放太多?」 按訊息中的連結數量分組,表面上有明顯規律:

訊息帶幾個連結表面失敗率排除單一離群貼文後
0 個約 13%
1 個約 13%
2 個約 23%約 15%

兩個連結的失敗率幾乎翻倍,很容易直接下結論。但拆開來看,這一組的失敗高度集中在上一節那篇本來就異常的貼文上;排除之後,兩連結組回到和零連結、一連結幾乎一樣的水準。

聚合數字裡的相關性,可能整個來自一個離群群組。 這是這次追查中最值得記住的方法論教訓——如果沒有先做貼文級的拆分,我們會做出「減少連結數量」這種完全沒有效果的產品決策,還會以為問題解決了。

「是不是我們自己送太快?」 我們對每個帳號有自己的節流保護,所以很自然會懷疑失敗來自逼近上限。實際檢查該帳號當時的忙碌程度與失敗率的關係,方向卻是相反的:帳號越忙,私訊失敗率反而略低。如果是送太快造成的,趨勢應該完全倒過來。

這一點也和外部資訊吻合。第三方整理提到的平台私訊上限落在每小時數百則的量級(官方文件本身沒有載明這個數字),以一般帳號的互動量來說很難碰到。比較容易踩到的是另一套機制:反垃圾訊息系統會判斷發送速度與帳號規模、帳號年齡是否匹配,觸發之後需要暫停一到兩天。換句話說,節流真正要控制的是節奏,總量反而是次要的。

「是不是留言太舊、超過七天了?」 查下去也落空——這些都是新收到的留言,遠在窗口內。不過查這件事的時候,浮現了另一個真實的趨勢:以貼文本身的年齡分組(注意這裡看的是貼文有多舊,跟留言本身多新無關):

新留言發生時,貼文的年齡失敗率
1 小時內約 4%
1–6 小時約 10%
6–24 小時約 16%
1–3 天約 18%
3–7 天約 23%
超過 7 天約 19%

新貼文的留言明顯比舊貼文的留言容易送達。這和第四層的受眾假說可以接上:新貼文的留言主要來自既有追蹤者(本來就是已連結關係,送達率高),舊貼文被演算法重新推送後才會大量觸及陌生受眾,其中就包含各種訊息設定與青少年帳號。

另一個會騙人的錯誤碼

2534025 的字面意思誤導我們往格式方向查,而這個模式在這批錯誤碼裡反覆出現。另一個例子是 2534122,字面是「invalid message id」,看起來像訊息識別碼有問題;公開整理指出它真正的成因是帳號因社群守則被暫時限制在私訊中發送連結,通常 24 到 72 小時後解除。

這件事的工程意義比錯誤本身大:如果一個限制的時間尺度是幾十小時,以秒為單位的重試就完全沒有意義,它唯一的產出是錯誤日誌。重試節奏必須和限制的時間尺度對齊。

同一份整理裡還有一個值得單獨處理的錯誤碼,對應「帳號擁有者在 Instagram 設定裡關閉了第三方應用的訊息存取權限」。這一類跟平台的資格判定無關,純粹是設定問題,值得在介面上單獨辨識出來、直接告訴使用者去哪裡打開——它是少數使用者真的有能力自己解決的失敗,也就是本文開頭第一步要檢查的那一項。

這改變了我們的設計

把四層合起來看,最合理的解讀是:

2534025 是平台的通用終局回應,意思接近「這則留言目前無法作為私密回覆的收件對象」。它跟你送出去的資料格式其實關係不大,只是本地化的錯誤訊息太籠統,指不出真正的資格條件。

換句話說,留言轉私訊是一條一次性、由平台逐則判定資格的機率性通道;「送出/失敗」這個二元模型從一開始就套錯了。一旦接受這個框架,幾個工程決定就跟著改變:

終局錯誤不要重試。 如果失敗來自收件人資格,同樣的請求送第二次、第三次結果不會改變,只會製造大量錯誤日誌,並讓佇列被少數永遠不會成功的項目佔住。錯誤處理的第一件事,是把回應分成「已送達」「終局拒絕」「憑證失效」「結果不明」「可安全重試」幾類,按類別決定行為。錯誤訊息的字面意思在這件事上幫不上忙。

可以重試的那些,節奏要對齊限制本身的時間尺度。 退避間隔要按照那個限制大概多久會解除來設定;面對一個以小時計的帳號層級限制,密集重試除了堆滿日誌之外不會改變任何結果。同時要設嘗試次數上限,讓項目最終能落地成失敗,停止無限期佔著佇列。

過期判定要以留言的時間戳為準。 七天的窗口從留言建立當下就開始跑,跟系統什麼時候處理到它無關。佇列一旦累積,最先失去的就是這段時間;所以入列時就該算好到期時刻,過期的項目直接標記失效。

別讓公開回覆宣稱一件沒發生的事。 我們的流程是先送私訊、再發公開回覆。當私訊終局失敗時,那句「已經私訊給您囉」就不會發出去。平台沒有提供跨兩支 API 的交易,但這個順序至少保證不會在公開場合承諾一則不存在的訊息。這件事還有另一個理由:公開回覆與私訊是兩支獨立的呼叫,本來就會各自失敗,任何一支都不該預設另一支成功了。

結果不明時,停下來比重試安全。 請求送出後如果連線中斷、沒拿到明確回應,對方可能已經收到了。對一個一次性的訊息通道來說,重試的代價是同一個人收到兩則私訊——比少送一則更糟。

定期跟平台對帳,補上看不見的那一層。 只靠 webhook 驅動的系統,永遠不會知道自己漏掉了什麼——沒收到的事件不會出現在任何報表上。要量這一層,得反過來定期去平台拉留言清單,跟自己處理過的紀錄比對,差集就是漏失。

監控的分母要放在「受影響的實體」上。 少數幾筆卡住的項目在錯誤日誌上可以放大成幾百行,讓儀表板看起來像大規模故障。告警要同時呈現受影響的不重複通知數與總嘗試數,兩個數字差很多的時候,通常代表問題出在自己的佇列上,跟平台沒什麼關係。

貼文級的異常偵測要有樣本門檻。 既然貼文之間的差異是真實的,就值得告警。但「五次裡失敗一次」不該觸發任何事——合理的做法是設最小樣本數,用信賴區間下界與帳號基線比較,並同時呈現原始失敗率與排除首次接觸收件人後的調整失敗率。

最後,把它寫進產品說明。 這一條算是產品決定。既然有一定比例的留言註定送不到,而且原因不在使用者的設定上,就應該誠實地讓使用者知道,省得他們反覆檢查自己哪裡設錯。

小結

回頭看,這次追查用到的技術都很基本:把投遞結果寫成結構化紀錄、按幾個維度分組、算信賴區間、拆解離群群組。真正花時間的是換掉一個錯誤的心智模型——從「送出/失敗」換成「平台對每一則留言做一次資格判定」。

有兩個教訓值得單獨記下來。

第一,平台的錯誤訊息會把你帶往錯的方向。那句「確認資料類型符合規定」讓我們花時間去檢查完全正確的訊息格式;真正的答案藏在自己的生產資料裡,而且只有在投遞紀錄先存對了欄位之後才看得見。

第二,自己的資料也有形狀,形狀由「你看得見什麼」決定。我們的統計能證明貼文之間存在穩定差異,卻無法指出成因;青少年帳號這個機制是從外部資料才補上的。同樣地,webhook 沒送到的那一層在自己的日誌裡完全隱形,分母裡根本沒有它們。跨出自己的日誌、去看別人怎麼描述同一支 API,值得正式排進排查流程裡。

所以下次接一支不透明的第三方 API 時,最值得先問自己兩個問題:當它出錯、而我看不懂原因的時候,我手上會有哪些欄位可以查?還有——有哪些失敗,根本不會出現在我的日誌裡?

參考來源

Meta APIInstagram API系統設計可靠性工程