指南

什麼是 Webhook?App 如何互相通知

Webhook 是某項服務在某件事發生時,自動傳送到你 App 的訊息。當客戶的付款成功時,Stripe 會把一則 Webhook 送到你 App 中的某個位址,說明「付款成功,以下是詳細資料」,然後你的 App 就會做出反應:將訂單標記為已付款、寄出收據、解鎖帳號。簡單來說:API 是你的 App 向另一項服務提出問題;Webhook 則是另一項服務呼叫你的 App,告訴它發生了什麼事。

用 AI 建站時,為什麼它很重要

一旦你的 App 開始收款、接收來自其他工具的表單提交,或連接 GitHub、Shopify 或行事曆等服務,你就會遇到 Webhook。它們通常是得知某件事已發生的正確方式,但也是 AI 建置的 App 最常見出錯的地方:

  • 端點會接受任何請求,因此任何人都可以偽造「付款成功」事件。
  • App 在結帳跳轉頁面上就授予存取權限,而不是在 Webhook 上授權。結果是提早關閉分頁的人付了錢卻拿不到內容,而找到成功頁網址的人則能免費取得。
  • 同一個事件送達兩次,客戶因此收到兩筆訂單或兩封郵件。

了解 Webhook 的運作方式,讓你能在一開始就要求必要的保護。

日常生活的比喻:送貨簡訊

你可以用兩種方式知道包裹是否已送達。你可以每十分鐘走到前門查看(這叫做輪詢(polling):你的 App 反覆呼叫 API 詢問「有新的東西嗎?」)。或者快遞員在送達後傳一則簡訊給你(這就是 Webhook)。

簡訊顯然比較好,前提是你能信任它。如果任何人都能傳「包裹已送達」的訊息給你,你會希望有辦法確認它真的來自快遞公司。而且如果簡訊來的時候你的手機沒開機,你會希望快遞公司稍後再試一次。這兩個問題(驗證與重試)正是 Webhook 難處理的大部分原因。

Webhook 的運作方式,一步一步來

  1. 你在 App 的後端建立一個端點。 也就是接受 POST 請求的網址,例如 https://yourapp.com/webhooks/stripe。
  2. 你把該網址向服務商登記,並選擇要接收哪些事件(例如「結帳完成」和「訂閱取消」)。
  3. 服務商會提供一組簽章密鑰。 你要把它當作機密資料儲存在伺服器上,請參閱環境變數與機密資料。
  4. 事件發生。 服務商會向你的網址發出 HTTP 請求,內容是描述該事件的 JSON,並附上簽章標頭。
  5. 你的 App 驗證簽章,記錄事件 ID,快速回傳成功代碼(200),然後才開始處理工作。
  6. 如果你的 App 沒有回傳成功,許多服務商會稍後再試。

簽章

簽章可以證明請求確實來自服務商,且內容沒有被竄改。例如 Stripe 會使用簽章密鑰,對請求內容與時間戳記計算 HMAC(一種帶密鑰的指紋,演算法為 SHA-256),並放在 Stripe-Signature 標頭中傳送。你的 App 用自己持有的密鑰做相同的計算;若結果相符,就表示請求是真實的。由於時間戳記也經過簽章,你可以拒絕別人先前記錄下來、之後重播的舊請求。根據 Stripe 截至 2026 年 9 月的文件,其函式庫預設的容忍時間為五分鐘。

一個實務細節:簽章是根據**原始(raw)**請求內容計算的。如果你的框架在驗證前就解析並重新排列了 JSON,檢查就會失敗。這是非常常見的錯誤。

重試、重複與順序

各服務商的做法不同,請自行查閱其文件。截至 2026 年 9 月:

行為StripeGitHub
自動重試有,正式模式下最多三天,間隔逐漸拉長沒有;你需要手動或透過腳本重新傳送失敗的傳遞
回應時間盡快回傳 2xx,在任何耗時工作之前超過 10 秒才回應的傳遞會被視為失敗

Stripe 的文件也說明,事件不保證會按照發生的順序抵達,而且同一個事件偶爾可能送達不止一次,因此建議記錄事件 ID。

這個一般原則適用於任何服務商:事件可能延遲抵達、重複抵達,也可能亂序抵達。你的處理程式應具備冪等性(idempotent),也就是處理同一個事件兩次,與只處理一次的效果相同。

實例:解鎖付費課程

你販售一門線上課程。學員按下購買,在 Stripe 的託管結帳頁面付款,然後被導回你的網站。

脆弱的做法: 「感謝」頁面解鎖課程。如果學員在跳轉途中關閉瀏覽器,他們已付款卻沒有存取權限。如果有人猜到感謝頁的網址,他們不用付款就能取得存取權限。

使用 Webhook 的做法:

  1. Stripe 確認付款後,會把 checkout.session.completed 事件送到 /webhooks/stripe。
  2. 你的後端使用簽章密鑰驗證簽章。如果驗證失敗,就回傳 400,且不做任何事。
  3. 它檢查這個事件 ID 是否已經處理過。如果是,就回傳 200 並停止。
  4. 它儲存事件 ID,回傳 200,然後授予學員存取權限並寄出歡迎郵件。
  5. 感謝頁面只顯示「已收到付款,您的課程即將出現」,並檢查學員的存取狀態。

之後,如果訂閱被取消或信用卡扣款失敗,其他 Webhook 事件會通知你的 App 移除或暫停存取權限。這就是訂閱型產品如何與帳單保持同步的方式,請參閱什麼是 SaaS與如何為你的 App 加入付款功能。

關鍵名詞

  • 端點(Endpoint): 你 App 中接收 Webhook 的網址。
  • 事件/酬載(Event/payload): 發生了什麼事,以及描述它的資料(通常是 JSON)。
  • 簽章密鑰(Signing secret): 用來驗證簽章的共享密鑰。
  • 冪等性(Idempotent): 以相同的輸入重複執行,結果仍然相同,因此重複執行是安全的。
  • 輪詢(Polling): 反覆向 API 詢問是否有變化,而不是等待 Webhook。
  • 通道(Tunnel): 一種工具,可以為你的筆電提供暫時的公開網址,讓開發期間 Webhook 能送達。

常見錯誤與迷思

  • 沒有驗證簽章。 端點網址並不是秘密。驗證才是讓請求變得可信的關鍵。
  • 在回應之前做耗時工作。 在回應之前寄信或產生 PDF,可能造成逾時,逾時會觸發重試,重試又造成重複。
  • 假設恰好一次、按順序抵達。 記錄事件 ID,不要依賴抵達的順序。
  • 使用預覽網址。 Webhook 需要穩定的公開位址。私人預覽與 localhost 無法從網際網路存取;請使用你已發布的網址(或在測試時使用通道)。
  • 混用測試與正式密鑰。 服務商通常會為測試模式與正式模式分別發行不同的簽章密鑰。兩組都要儲存,並使用正確的那一組。
  • 對重要決策盲目信任酬載。 對於高風險的動作,在收到事件後,向服務商的 API 取得最新狀態是合理的做法。

向你的 AI 建置工具要求什麼

在伺服器上新增一個 POST /webhooks/stripe 端點。使用原始請求內容與密鑰 STRIPE_WEBHOOK_SECRET 驗證 Stripe 簽章。簽章無效時以 400 拒絕。儲存每個事件 ID,並略過已處理過的事件。快速回傳 200,然後處理 checkout.session.completed:將訂單標記為已付款並寄出收據。記錄事件類型。不要在結帳成功頁面授予存取權限。

接著測試它:從服務商的後台傳送一個測試事件,再傳送同一個事件一次,並送出一個簽章錯誤的請求。只有第一次應該改變任何資料。

Mythex 中的 Webhook

Mythex 沒有獨立的 Webhook 服務;你請 Agent 在 App 自己的後端新增 Webhook 端點,並把服務商指向你已發布的網址,因為 Webhook 需要公開位址。簽章密鑰放進專案的機密設定中,若要在正式 App 上更換密鑰,就需要重新發布。文件中的處理 Webhook範例包含起始提示詞,而商店範本則是接受付款的 App 很好的起點。

常見問題

用白話來說,什麼是 Webhook?

Webhook 是某項服務在某件事發生時(例如付款成功或表單送出),自動傳送到你 App 中某個網址的訊息。你的 App 不必一再詢問是否有變化,而是由另一方主動告訴它。

Webhook 和 API 有什麼區別?

使用 API 時,你的 App 向另一項服務提出請求並取得回應。使用 Webhook 時,則是另一項服務在事件發生時呼叫你的 App。許多整合會同時使用兩者:以 Webhook 得知事件,再透過 API 呼叫取得詳細資料或執行動作。

Webhook 安全嗎?

只要你驗證它們,就可以很安全。設計良好的服務商會使用只有你知道的密鑰為每個 Webhook 簽章,而你的 App 應拒絕任何簽章不符的請求。若沒有這項檢查,任何找到網址的人都可以傳送假事件。

如果 Webhook 送出時我的 App 離線,會發生什麼事?

這取決於服務商。例如 Stripe 在正式模式下,會在最多三天內重試失敗的傳送,而 GitHub 不會自動重試,需要你手動或透過腳本重新傳送。請查閱各服務商的文件,並設計你的處理程式,使其能應付延遲或重複的事件。

Mythex · Pricing · 用 AI 打造任何東西 · 網站與應用程式範本 · Mythex 比較 · AI 應用程式開發詞彙表 · 免費的 AI 應用程式開發工具 · English

English አማርኛ العربية Български বাংলা Bosanski Català Čeština Dansk Deutsch Ελληνικά Español Español (España) Eesti فارسی Suomi Français Français (Canada) ગુજરાતી हिन्दी Hrvatski Magyar Հայերեն Bahasa Indonesia Íslenska Italiano 日本語 ქართული Қазақ тілі ಕನ್ನಡ 한국어 Lietuvių Latviešu Македонски മലയാളം Монгол मराठी Bahasa Melayu မြန်မာ Norsk bokmål Nederlands ਪੰਜਾਬੀ Polski Português (Brasil) Português (Portugal) Română Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 简体中文 繁體中文(香港)