AnyHook 文件
Webhook 中繼服務(webhook relay)。把發送端指向 AnyHook,就有自動重試、完整的事件紀錄、統一的簽章驗證和一鍵重播。不需要任何 SDK。
快速入門
AnyHook 不需要 SDK,也不需要安裝套件:把發送端儀表板裡的 Webhook 網址換掉,你的處理程式就會繼續收到同樣的請求。有一件事要事先規劃:重試和重播帶的是發送端原始的時間戳記,所以發送端的簽章會過期時(Stripe、Slack,以及 /providers 上標有容許時間差的其他服務商),就改為檢查 AnyHook-Signature 來接受這些請求。
最快的方式(不需帳號):執行 curl -X POST https://anyhook.net/api/v1/quickstart,立刻取得一個可用的接收網址(inbound URL)和 API 金鑰,適合 AI agent 和快速測試。這個端點如果 7 天內沒有透過回傳的 claim_url 認領,就會到期。把發送端指向回傳的 inbound_url,再用 GET /api/v1/events 和 GET /api/v1/events/{id} 讀回事件;回應中的 next_steps 列出了每個指令。
- 1建立帳號前往 anyhook.net/sign-up 建立免費帳號。不需信用卡。
- 2建立 App一個 App 就是一個接收網址,對應一個 Webhook 來源。新帳號一開始就會看到建立表單:替它取個名字(例如「Stripe Production」),也可以選填你自己的伺服器網址,AnyHook 收到每個事件後會轉送到那裡。留空的話,App 會記錄所有事件,但不轉送。目的地隨時都能新增,Free 方案每個 App 最多 3 個。
- 3把發送端指向 AnyHookApp 頁面會顯示你的接收網址,以及常見發送端要在哪裡填入它(例如 Stripe → Developers → Webhooks)。把它貼到那裡,取代原本的 Webhook 網址:
https://in.anyhook.net/{user-slug}/{app-slug}這個頁面會持續監看:第一個請求幾秒內就會出現在上面。還沒有東西可以送?在同一頁按下 Send a test event(傳送測試事件)。
接收網址格式
每個 App 都有一個專屬的接收網址:
https://in.anyhook.net/{user-slug}/{app-slug}user-slug:你的帳號識別碼,可以在儀表板的設定中看到。app-slug:每個 App 各不相同,依 App 名稱自動產生,也可以修改。
接收端點接受任何 POST 請求,也會回應設定時的驗證交握:帶有 hub.mode=subscribe 的 GET(Meta 的 hub.challenge 檢查)會自動原樣回傳,即使 App 還沒設定好也一樣。其他方法一律回 405。網頁和一般的 HTML 表單也能 POST 到這裡,詳見程式碼處理程式(code handler)。事件一寫入持久佇列,AnyHook 就回傳 HTTP 200,大約 1 秒,不受目的地端點的速度影響,所以發送端完全不必等它。
application/json、application/x-www-form-urlencoded、XML 或純文字,AnyHook 會逐位元組原樣保存,並帶著原本的 Content-Type 轉送。請求主體以文字形式儲存,所以二進位 payload 或 multipart 表單裡的檔案,無法完整送達。支援的來源
AnyHook 會從傳入的標頭自動判斷發送端,並在邊緣節點驗證 payload 的簽章。要啟用驗證,建立 App 時選擇你的 Webhook 服務商,並貼上服務商的 Webhook 簽章密鑰(例如 Stripe 的 whsec_...)。
| 來源 | 簽章標頭 | 演算法 |
|---|---|---|
| Stripe | Stripe-Signature | HMAC-SHA256 + 時間戳記 |
| GitHub | X-Hub-Signature-256 | HMAC-SHA256 |
| Meta (Instagram / Facebook / Threads) | X-Hub-Signature-256 | HMAC-SHA256 |
| Shopify | X-Shopify-Hmac-Sha256 | HMAC-SHA256 (Base64) |
| Discord | X-Signature-Ed25519 | Ed25519 |
| Slack | X-Slack-Signature | HMAC-SHA256 + 時間戳記 |
| LINE | x-line-signature | HMAC-SHA256 (Base64) |
| Xero | x-xero-signature | HMAC-SHA256 (Base64) |
| Linear | Linear-Signature | HMAC-SHA256 |
| Sentry | Sentry-Hook-Signature | HMAC-SHA256 |
| Vercel | x-vercel-signature | HMAC-SHA1 |
| Paddle | Paddle-Signature | HMAC-SHA256 + 時間戳記 |
| SendGrid | X-Twilio-Email-Event-Webhook-Signature | ECDSA P-256 |
| Twilio | X-Twilio-Signature | HMAC-SHA1 |
| Intercom | X-Hub-Signature | HMAC-SHA1 |
| HubSpot | X-HubSpot-Signature-v3 | HMAC-SHA256 (v3) / SHA-256 (v1/v2) |
| Standard Webhooks (Svix, OpenAI, Replicate) | webhook-signature / Svix-Signature | HMAC-SHA256 + 時間戳記 |
| Clerk | Svix-Signature | HMAC-SHA256(透過 Svix) |
| Resend | Svix-Signature | HMAC-SHA256(透過 Svix) |
| Lemon Squeezy | X-Signature | HMAC-SHA256 |
| WooCommerce | X-WC-Webhook-Signature | HMAC-SHA256 (Base64) |
| PayPal | PAYPAL-TRANSMISSION-SIG | RSA-SHA256 |
| Generic/自訂 | 無 | 不驗證 |
沒通過檢查的請求會在邊緣節點拒收,不會抵達你的端點。App 頁面會計算數量,每天的前 5 個會以 rejected 狀態連同標頭和主體保存在事件紀錄裡,讓你看得到原因。
Meta:Instagram、Facebook、Threads
這三者共用同一套 Graph API Webhook 規格,所以一個 App 就能涵蓋。Meta 用 X-Hub-Signature-256 簽章,和 GitHub 用的標頭相同;AnyHook 靠 X-Github-Event 區分兩者,GitHub 一定會送這個標頭,Meta 從來不送。
Meta 要等 callback 網址回應帶有 hub.mode=subscribe 的 GET,把 hub.challenge 原樣當作整個回應主體回傳,才會儲存這個網址。AnyHook 會替你回應,所以接收網址第一次就能儲存成功。
| 在 AnyHook | 在 Meta 的 App Dashboard |
|---|---|
| Source:選 Meta | App settings → Basic |
| App Secret | 從這裡複製到 AnyHook |
| Verify Token(選填) | Webhooks → Verify token:輸入同一個字串 |
| 你的接收網址 | Webhooks → Callback URL:貼上它 |
Meta 會在 36 小時內以逐漸降低的頻率重試失敗的傳遞,之後就放棄。因為 AnyHook 會先儲存事件,再嘗試傳遞到你的端點,所以你這邊部署或服務中斷時,不會用掉這段時間。
傳遞的運作方式
AnyHook 採用以佇列為基礎的非同步架構,讓 Webhook 發送端和你的後端彼此解耦:
- 邊緣節點接收:Cloudflare Workers 接收 POST、驗證簽章,事件持久儲存之後就回 200,大約 1 秒。
- 佇列:事件發布到持久的訊息佇列(Upstash QStash)。這時發送端的 HTTP 連線已經關閉。
- 轉送:QStash 把事件交給 AnyHook 的轉送程式,再由它把原始 payload POST 到你的目的地端點。
- 記錄:每個步驟都會記下來,包括接收的標頭和主體、傳遞狀態、端點回應的狀態碼和主體,以及延遲時間。
413 Payload Too Large 拒收,絕不會無聲無息地丟棄,也絕不計費。App 頁面會計算數量。429,AnyHook 會退避後重試。傳遞保證
直接把約定講清楚,因為對保證含糊其辭的中繼服務,會把這份含糊推給你的程式碼:
- 至少一次,不是恰好一次。重試就是 AnyHook 的本業,而回應遺失之後的重試,和一次全新的傳遞根本無法區分。偶爾你會收到同一個事件兩次;沒有任何中繼服務能保證不會這樣,宣稱做得到的,說的只是一切順利時的情況。你的處理程式必須冪等,去重的鍵就是我們在
X-AnyHook-Delivery轉送的事件 ID。 - 不保證順序。因為有重試和並行傳遞,較晚的事件可能比還在重試中的較早事件先送達。大多數發送端在我們的上游也不承諾順序。依 payload 裡的欄位排序(版本號、
updated_at),絕不要依抵達時間。 - 不要用簽章去重。
AnyHook-Signature每次嘗試都會以新的時間戳記重新簽章,這是刻意的,讓重試永遠不會超出你的容許時間差。同一個事件的兩次傳遞會帶著不同的簽章;不變的是事件的id。 - 收下就代表已持久儲存。邊緣節點要等事件儲存之後才回 200,而且在改回 503 之前,佇列後面還有兩個備援儲存。接收網址回 503,代表什麼都沒儲存,這時發送端自己的重試就是安全網;這正是我們寧可回 503、也不假裝成功的原因。
重試排程
目的地回傳可重試的錯誤或逾時時,AnyHook 會以指數退避自動重試:
| 第幾次嘗試 | 延遲 | 方案 |
|---|---|---|
| 1 | 立即 | 所有方案 |
| 2 | 5 分鐘後 | 所有方案 |
| 3 | 30 分鐘後 | 所有方案 |
| 4 | 3 小時後 | Pro、Scale、Enterprise |
| 5 | 18 小時後 | Pro、Scale、Enterprise |
| 6-10 | 每次間隔 24 小時 | Scale、Enterprise |
HTTP 202 Accepted,再非同步處理。AnyHook 收到 202 就會把這次傳遞標為成功。哪些回應會觸發重試?
不是所有錯誤都可以重試。AnyHook 會分類端點的回應,避免把嘗試次數或運算額度浪費在再試也解決不了的錯誤上:
| 狀態碼 | 處理方式 | 原因 |
|---|---|---|
| 2xx | 成功:傳遞完成 | 你的端點已接受這個事件。 |
| 3xx | 不再重試:立即標為失敗 | 不會跟隨重新導向;把目的地設成最終網址。 |
| 408 Request Timeout | 退避後重試 | 暫時性逾時,下次嘗試可能成功。 |
| 429 Too Many Requests | 退避後重試 | 你的端點正在限制速率,給它一點時間。 |
| 4xx(其他:400、401、403、404、422…) | 不再重試:立即標為失敗 | 用戶端錯誤:請求格式有誤或未經授權。用同一個 payload 重試也沒有用。 |
| 5xx | 退避後重試 | 伺服器錯誤,可能是暫時的。 |
| 網路錯誤/逾時 | 退避後重試 | 無法連上你的端點。 |
400 Bad Request 或 422 Unprocessable Entity 代表你的處理程式拒絕了這個 payload,重試也不會改變結果。立即標為失敗,你能更快收到通知,重試不會用掉運算額度,修好處理程式後還能重播這個事件。每次嘗試都會記錄完整的回應主體和狀態碼。不可重試的 4xx 失敗和可重試的失敗都會計入斷路器,任何類型連續失敗 20 次,斷路器就會跳脫。
失敗通知
AnyHook 只在特定門檻寄出 Email 警示,而不是每個事件都寄,讓你的收件匣不會被淹沒:
- 第 1 次失敗:立即發出警示,附上狀態碼和目的地。
- 連續第 5 次失敗:發出升級警示,附上處理建議。
- 連續第 20 次失敗:斷路器跳脫(見下方),並寄出斷路器警示。
上面每個門檻各寄一封 Email,之後斷路器的每個階段(每次暫停和停用)也各寄一封,絕不會每個事件各寄一封。只要有一次傳遞成功,計數就會歸零。
斷路器
如果 AnyHook 偵測到同一個目的地連續 20 次傳遞失敗(包括不可重試的 4xx),斷路器就會跳脫,並開始逐步升級:
| 第幾次跳脫 | 暫停時間 | 結果 |
|---|---|---|
| 1 | 10 分鐘 | 暫停目的地,寄出 Email |
| 2 | 30 分鐘 | 暫停目的地,寄出 Email |
| 3 | 2 小時 | 暫停目的地,寄出 Email |
| 4 | 24 小時 | 暫停目的地,寄出 Email |
| 5 | 永久 | 停用目的地,寄出 Email |
運作方式
- 照樣接收事件。接收網址始終有效,Stripe、GitHub 等來源的事件照樣接收並儲存。暫停的只有傳遞到目的地這一段。
- 每次暫停結束後,AnyHook 會送出半開啟探測:用下一個事件測試你的目的地。成功的話,斷路器關閉,一切重設。
- 只要成功一次,一切重設:連續失敗次數和跳脫次數都歸零。
- 暫停期間收到的事件會以
circuit_open狀態儲存,目的地恢復後即可重播。 - 每升級一級,你都會收到 Email 通知,內容包括目的地網址、失敗次數和暫停時間。
- 如果目的地已停用(第 5 次跳脫),必須從儀表板手動重新啟用。
這種逐步升級的做法,讓你的伺服器有時間恢復,也替運算額度省下當機的端點會耗掉的那些重試。
事件紀錄
每個抵達接收網址的事件都會被記錄,不論傳遞結果如何。事件紀錄從你把服務商指向 AnyHook 的那一刻開始:在那之前失敗的事件只存在於服務商那裡(Stripe 的情況見如何重播 Webhook)。每筆紀錄包含:
- 完整的接收標頭和主體(和發送端送來的一模一樣)
- 傳遞狀態:
queued→success/retrying/failed/rejected/circuit_open/quota_exceeded。rejected代表 AnyHook 拒收了這個事件(簽章驗證失敗,或目的地網址被封鎖),從未嘗試傳遞,也無法重播。 - 目的地回應的 HTTP 狀態碼和主體(每次嘗試)
- 延遲時間(毫秒)
- 嘗試次數和下次重試時間
- 事件是否為重播(
is_replay: true)
事件保存多久取決於方案。Free 方案:3 天,期間內都能重播,之後刪除。Pro 方案:1 年,前 30 天可以重播,之後轉為唯讀的加密封存。Scale 方案:7 年,前 90 天可以重播,之後轉為唯讀的加密封存。Enterprise 方案:彈性。
is_active: false),它的接收網址會回 202,但什麼都不儲存:服務商不會因此停用端點,事件卻就此消失。需要的流量進來之前,先取消暫停。在儀表板重播
事件紀錄裡的任何事件,都能在儀表板上一鍵重播。重播會把儲存的 payload 逐位元組原樣送到事件當初的目的地;如果之後你改過那個目的地的網址,就送到新網址。
- 前往 Dashboard → Events
- 找到該事件,按一下 Replay(重播)
- 系統會建立一個帶有
is_replay: true的新事件,並連結到原始事件
API 重播(單一事件)
把原始事件的 payload 發布到 QStash 重新傳遞。新事件透過 replay_of 連結到原始事件。
curl -X POST https://anyhook.net/api/v1/events/{event_id}/replay \
-H "Authorization: Bearer {api_key}"成功時回傳 200 和新事件的 ID。事件超過可重播期間時,回傳 410 Gone。
未送達的事件
回傳狀態為 failed、circuit_open 或 quota_exceeded 的事件。
| 參數 | 類型 | 預設值 | 說明 |
|---|---|---|---|
| since | ISO 8601 | 24 小時前 | 只列出這個時間戳記之後建立的事件 |
| limit | integer | 100 | 回傳的事件數上限(1–200) |
curl "https://anyhook.net/api/v1/apps/stripe-prod/events/undelivered?since=2026-04-11T00:00:00Z&limit=50" \
-H "Authorization: Bearer {api_key}"{
"count": 3,
"events": [
{ "id": "evt_abc", "status": "failed", "created_at": "..." },
...
]
}批次重播
重播符合條件的失敗事件,最舊的先重播,每次呼叫不超過上限。已經重播過,或超過可重播期間的事件會略過。
| 請求主體參數 | 類型 | 預設值 | 說明 |
|---|---|---|---|
| since | ISO 8601 | 24 小時前 | 只重播這個時間戳記之後建立的事件 |
| limit | integer | 50 | 重播的事件數上限(1–100) |
curl -X POST https://anyhook.net/api/v1/apps/stripe-prod/replay-failed \
-H "Authorization: Bearer {api_key}" \
-H "Content-Type: application/json" \
-d '{"since": "2026-04-11T00:00:00Z", "limit": 100}'{
"replayed": 3,
"events": [
{ "original_id": "evt_abc", "new_id": "evt_xyz" },
...
]
}本機開發
AnyHook 直接就能搭配本機開發,不需要特殊模式或 CLI。有兩種做法:
1. 透過隧道即時轉送
把 App 的目的地設成指向你本機的隧道網址。任何隧道服務都可以:
# ngrok ngrok http 3000 # → https://abc123.ngrok.io # cloudflared cloudflared tunnel --url http://localhost:3000 # localhost.run ssh -R 80:localhost:3000 nokey@localhost.run
在儀表板把隧道網址填進 App 的目的地。AnyHook 會像轉送到其他端點一樣把 Webhook 轉送過去,重試、紀錄和簽章標頭全都照常運作。
https://in.anyhook.net/you/app)。真實事件會記錄在事件紀錄裡,你也能在隧道和正式環境之間切換目的地,完全不用動 Stripe。2. 先收集,再重播到 localhost
讓正式環境的真實事件累積在 AnyHook 裡,需要時再把任何一個重播到本機環境。不必手動觸發測試事件,直接用真實的 payload 測試,只要不超過每日重播上限,想測幾次都可以。
- 把目的地切換成隧道網址
- 開啟 Dashboard → Events,在任一事件上按一下 Replay
- 或使用 API:
POST /api/v1/events/{id}/replay - 原始 payload 會逐位元組原樣送到目前的目的地
這在為邊界情況除錯時特別有用:在正式環境的紀錄裡找到出問題的事件,在本機重播,反覆修改直到處理程式正確運作。
靜態資料加密
所有 Webhook payload 資料在寫入資料庫之前,都會以 AES-256-GCM 加密。範圍包括接收標頭、請求主體,以及每個目的地的簽章密鑰。
加密使用專屬的金鑰(AES_SECRET_KEY),和資料庫憑證分開存放。資料庫外洩時,沒有加密金鑰就只拿得到密文:沒有明文的 Webhook 資料、沒有簽章密鑰,也沒有客戶資訊。
加密的內容
- Webhook 請求標頭和主體(每個事件)
- 服務商的簽章密鑰(Stripe Webhook 密鑰等)
- 每個目的地的 AnyHook 簽章密鑰
邊緣驗證
AnyHook 在 Cloudflare 的邊緣節點驗證 Webhook 簽章,在事件進入佇列之前完成。App 有了簽章密鑰後,缺少簽章或簽章無效的請求會以 401 拒收,不會用掉你的配額;沒有密鑰時,請求不經驗證就轉送。
AnyHook 依標頭自動判斷簽章類型。建立 App 並選擇來源類型時,AnyHook 會儲存簽章密鑰(儲存時已加密),並針對該服務商使用正確的演算法。
不支援或自訂的來源
如果你的 Webhook 來源不在支援清單上,建立 App 時選擇 Generic 來源。AnyHook 會:
- 接收每個傳入的 POST 並放進佇列,不檢查傳入的簽章。
- 把請求主體逐位元組原樣連同服務商的原始標頭轉送到你的目的地,每次嘗試另外附上新的 AnyHook-Signature。在我們邊緣節點之前由基礎設施加上的標頭(cf-* 之類)會被移除。
- 附上
AnyHook-Signature標頭,讓你的端點仍能驗證請求確實經過 AnyHook。
Generic App 不論設了什麼密鑰,都不檢查傳入的簽章。AnyHook-Signature 證明請求經過 AnyHook,卻無法證明是誰送給 AnyHook;如果你需要這一點,就繼續自己檢查發送端的原始標頭(原封不動轉送)。如果發送端用 Standard Webhooks 簽章(webhook-id、webhook-timestamp、webhook-signature,OpenAI 和 Replicate 都是這樣),就改選那個來源:它會在邊緣節點驗證。
AnyHook-Signature
每個轉送的請求都帶有 AnyHook-Signature 標頭,這是用目的地的簽章密鑰產生、附時間戳記的 HMAC-SHA256 簽章。
AnyHook-Signature: t=1712934567,v1=5257a869...
在你的伺服器上驗證:
// Node.js / TypeScript
import crypto from "crypto";
function verifyAnyHook(header: string, body: string, secret: string): boolean {
const [tPart, vPart] = header.split(",");
const timestamp = Number(tPart?.replace("t=", ""));
// Re-signed on every attempt, so a genuine signature is never older than this.
if (!timestamp || Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
const signature = Buffer.from(vPart?.replace("v1=", "") ?? "", "hex");
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${body}`)
.digest();
// timingSafeEqual throws on a length mismatch, so check that first.
return signature.length === expected.length && crypto.timingSafeEqual(signature, expected);
}簽章密鑰依目的地個別產生,可以在儀表板的 App → Destinations → Signing secret 找到。你隨時都能透過 API 輪替它。
自訂標頭
你可以依目的地,為對外的請求加上自訂標頭。常見用途:
Authorization: Bearer ...:向你的 API 驗證身分x-api-key: ...:API 金鑰驗證- 你的端點需要的任何標頭
自訂標頭的值會加密儲存,也不會出現在事件紀錄裡。在 App → Destinations → Custom headers 設定。
Stripe-Signature 等)會逐位元組原樣轉送,和你的自訂標頭及 AnyHook-Signature 一起送出,所以既有的 SDK 檢查在第一次傳遞時就能通過。重試和重播帶的是服務商原始的時間戳記,而 Stripe 的 SDK 預設超過 5 分鐘就會拒絕:這時改為檢查 AnyHook-Signature,它每次嘗試都會重新簽章。傳遞控制
每個目的地都有三個選用設定,Pro 以上方案皆可使用。在 App → Destinations → Delivery controls 設定,或用 PATCH /api/v1/apps/{slug} 設定目的地物件。Free 帳號會保有付費期間設定的控制,但無法更改。
{
"destinations": [{
"url": "https://api.example.com/hooks/stripe",
"event_types": ["invoice.*", "checkout.session.completed"],
"rate_limit": 20,
"delay_seconds": 5
}]
}事件類型篩選
event_types 是一組比對模式;目的地只會收到類型符合其中之一的事件。* 比對任意長度的字元,所以 invoice.* 涵蓋所有 invoice 事件,checkout.session.completed 則只比對這一種。比對不分大小寫,且針對整個類型名稱。有兩個目的地時,就能依類型分送:帳務事件送往一個服務,其他事件送往另一個。
- 在邊緣節點判斷,時間點在簽章驗證之後、配額計數之前:被篩掉的事件不會儲存、不會傳遞,也不計入配額。
- 發送端會收到
200 {"filtered":true,"event_type":"…"},Stripe 和 Shopify 會在它們自己的 Webhook 紀錄裡顯示這個回應。 - 類型從哪裡讀取,依服務商而定:GitHub、Shopify、WooCommerce 和 Lemon Squeezy 讀標頭;Stripe、Clerk、Resend、Vercel 和 Svix 類型的發送端讀請求主體的
type;Paddle 和 PayPal 讀event_type。這個值和事件紀錄 Type(類型)欄顯示的相同。
type 欄位的一般請求主體、服務商的 ping),不論篩選條件如何,都會傳遞到每個目的地。中繼服務的承諾是不會遺失任何事件;篩選如果吃掉未知類型的事件,就打破了這個承諾。速率限制
rate_limit 把送往目的地的傳遞限制在每秒這個次數(1 到 1,000)。超過上限的事件會在佇列中等候,有餘裕時依序送出;不會遺失任何事件,重試也計入同一個上限。發送端的突發流量可能超過你的伺服器能消化的速度時,就用它:Shopify 大量更新商品、Stripe 月初的一波訂閱續約、GitHub monorepo 一次 push 觸發大量 CI 事件。
批次重播也遵守這個上限,而這正是它最重要的時候:以每秒 20 次重播 500 個失敗事件,就是 25 秒的穩定流量,而不是一波再次失敗的尖峰。
延遲傳遞
delay_seconds 會延後第一次傳遞嘗試(1 秒到 1 天)。常見的原因是發送端的 Webhook 和它自己的 API 互相賽跑:Stripe 的 checkout.session.completed 可能在你的結帳成功頁面寫完訂單之前就送到,延遲五秒,這場競態就不再是問題。重播一律不延遲;第一次嘗試之後的重試排程不變。
Slack / Discord 警示
在 Pro 以上方案,AnyHook 寄出的每封 Email 警示,也會發布到你在 Settings → Notifications 設定一次的 Incoming Webhook:傳遞失敗(第 1 次和連續第 5 次)、斷路器跳脫與停用、拒收量暴增與恢復正常,以及 50% / 90% / 100% 的配額通知。儲存網址時會發布一則測試訊息,讓你確認有送達。Slack、Discord、Mattermost、Rocket.Chat 和 Teams 的 incoming-webhook 連接器都能使用;payload 是 {"text": …}(Discord 則是 content)。關閉「Email on failure」(失敗時寄送 Email)只會停掉 Email,頻道仍會繼續收到警示。
程式碼處理程式
在 Pro 和 Scale 方案中,App 可以在沙箱裡,對收到的每個事件執行你自己的程式碼,不需要自己的伺服器。儲存程式碼後,App 會多一個執行它的目的地,因此重試、重播、斷路器和事件紀錄都和其他目的地一樣運作,每次執行的主控台輸出和回傳值,就是該次傳遞的回應。可以在 App 的 Code(程式碼)區塊撰寫,或使用下方的端點。常見操作的範本在 /code-templates.md,從 Pipedream 搬過來也有專門的指南。
export default async function (event, { env, store }) {
// event: { method, headers, body, raw_body, delivery_id, attempt }
console.log(event.body);
}- 事件。
body是解析後的 JSON、表單欄位(urlencoded,或 multipart 表單的文字欄位)或原始文字,raw_body則是收到時的原始文字。method一律是 POST;查詢字串、路徑和呼叫端的 IP 都不會保存。delivery_id是事件的X-AnyHook-DeliveryID,attempt從 1 開始計數,測試執行時為 0。 - 失敗。拋出錯誤會讓這次傳遞失敗,並依方案的嘗試次數重試(重試排程)。某個事件的執行已經成功時,它的重試不會再執行一次;但如果執行是在某個呼叫已經送出後才失敗,重試就會再執行一次,所以在 API 允許的地方,用
delivery_id當這類呼叫的冪等鍵。 - Pipedream 步驟。
export default defineComponent({ async run({ steps, $ }) {} })也能執行:steps.trigger.event就是上面的事件,$.export和$.flow.exit都能用,每個data_storeprop 都是 App 的儲存區(store),app prop 的$auth.field會讀取密鑰APP_FIELD,其他 prop 則讀取以它命名的密鑰(channelId讀取CHANNEL_ID)。哪些無法沿用,列在 Pipedream 指南裡。 - npm 套件。程式碼 import 的套件會在儲存時安裝,固定版本的寫法和 Pipedream 相同(
import axios from "axios@1.7.9"),最多 20 個套件。需要安裝新套件的儲存可能要花幾分鐘;安裝失敗就什麼都不會儲存。 - 密鑰。以
env.NAME或process.env.NAME讀取,最多 20 個,名稱只能用英文字母、數字和底線,且不能以數字開頭。PATH, HOME, NODE_OPTIONS, NODE_PATH, PWD 和以ANYHOOK_開頭的名稱由執行環境專用,每個值最多 4,000 個字元。值絕不會離開伺服器。 - 儲存區。
store.get、set({ ttl }以秒為單位)、has、delete、keys、entries和clear能在事件之間保存狀態,每個 App 一個儲存區:最多 1,000 個鍵,每個鍵最長 256 個字元,每個值以 JSON 計最多 64,000 位元組。匯入時接受{"entries": {key: value}},會覆寫已存在的鍵,並且不設 TTL。 - 排程。
schedule是五個 cron 欄位,除非加上CRON_TZ=Area/City前綴,否則以 UTC 計。每次觸發都會把一個anyhook.schedule事件 POST 到 App 自己的網址,所以同時算一次執行和一個事件,而且只在程式碼開啟時觸發。App 的來源必須是 generic 或 auto,且不設簽章密鑰:替排程用的程式碼另開一個 App。 - Email。每個 App 也能在
{user-slug}.{app-slug}@anyhook.net收信,信件會變成一個email.received事件,主體包含from、to、subject、message_id、date、size,以及放在raw的 MIME 信件。和排程一樣,需要來源為 generic 或 auto、且不設簽章密鑰的 App;超過方案 payload 上限的信件會被退回。 - 網頁和表單。網頁可以用
fetch()呼叫接收網址:邊緣節點會回應 CORS 預檢請求,每個回應都帶有Access-Control-Allow-Origin: *。一般的 HTML 表單需要method="post"。在表單的 action 加上?redirect=https://your-site.com/thanks,事件儲存後就會以303把訪客導向那裡,前提是這個網址和送出表單的網站是同一個網站;否則訪客會看到一個寫著「Received」(已收到)的簡短頁面。文字欄位會出現在event.body;上傳的檔案則不會留下。 - 上限。程式碼最多 64,000 位元組,每次執行在 Pro 方案最多 105 秒、Scale 方案最多 285 秒。每次執行保存主控台輸出的最後 16,000 個字元。每月執行次數列在方案表裡,每次執行都計入,包括重試、測試執行、重播和排程觸發。沒有超額計費:超過當月執行次數後,送往程式碼的傳遞會回
429,並被保留(circuit_open),不重試,等到下個月或方案升級後即可重播。 - 測試,以及開啟與關閉。
POST .../code/test會用 App 最新儲存的事件、指定的event_id,或你傳入的body執行已儲存的程式碼,並回傳 log、回傳值和執行時間。它算一次執行,不會傳遞任何東西,但程式碼自己發出的請求都會真的送出。只有程式碼開啟時,事件才會執行它。儀表板儲存的新程式碼預設為關閉,而PUT .../code儲存的第一個版本會直接上線,除非你傳入"enabled": false;之後的儲存則維持目前的狀態。如果還有另一個目的地在處理同樣的事件(例如遷移期間),就用"enabled": false儲存,移除那個目的地之後再開啟程式碼。這兩種變更最多需要一分鐘,才會傳到每個邊緣節點。
驗證
所有 API 請求都需要在 Authorization 標頭帶上 Bearer token:
Authorization: Bearer ahk_live_xxxxxxxxxxxxxxxx
在 Dashboard → Settings → API Keys 產生 API 金鑰。金鑰的範圍是你的帳號,可以是完整存取或唯讀(建立時選擇),而且隨時都能撤銷。透過 MCP 登入(anyhook.net/mcp)連線的用戶端,可以在你核准的權限內,用它的 OAuth 存取權杖呼叫同樣的端點。
429 Too Many Requests,並附上 Retry-After 標頭,直到 UTC 00:00。AI agent(MCP)
AnyHook 是位於 https://anyhook.net/mcp 的 MCP 伺服器,agent 可以替你建立 App、讀取事件紀錄、重播失敗的事件,在 Pro 和 Scale 方案還能部署和測試 App 的程式碼(anyhook_code_deploy、anyhook_code_test、anyhook_code_get)。Claude Code、claude.ai、Codex和VS Code 只要網址加上瀏覽器登入就能連線,不必複製金鑰:各用戶端的步驟。
Cursor、Windsurf 和 Claude Desktop 使用金鑰。在 Dashboard → Settings → API Keys 建立金鑰後,畫面會顯示下面這段設定,你的金鑰已經填在裡面:
{
"mcpServers": {
"anyhook": {
"command": "npx",
"args": [
"-y",
"anyhook-mcp"
],
"env": {
"ANYHOOK_API_KEY": "ahk_live_xxxxxxxxxxxxxxxx"
}
}
}
}Cursor:Settings → MCP → Add new MCP server,或 ~/.cursor/mcp.json。Windsurf:~/.codeium/windsurf/mcp_config.json。Claude Desktop:Settings → Developer → Edit Config。程式碼工具需要 anyhook-mcp 0.4.0 以上版本(npx -y 會自動取得),或改用 https://anyhook.net/mcp 的伺服器。
接著對它說:「幫我的 Stripe Webhook 建立一個 AnyHook App,並給我它的網址。」這些內容,agent 也都能在 /llms.txt 讀到。
App
建立 App:
curl -X POST https://anyhook.net/api/v1/apps \
-H "Authorization: Bearer {api_key}" \
-H "Content-Type: application/json" \
-d '{
"name": "Stripe Production",
"source": "stripe",
"destination_url": "https://api.example.com/webhooks"
}'{
"id": "app_xxxxx",
"name": "Stripe Production",
"slug": "stripe-production",
"inbound_url": "https://in.anyhook.net/you/stripe-production",
"destinations": [
{ "url": "https://api.example.com/webhooks", "enabled": true }
]
}連接 Stripe 或 GitHub
提供 Stripe 金鑰或 GitHub token,AnyHook 就會在服務商那裡把 App 的接收網址新增為 Webhook,保存服務商產生的簽章密鑰,並從第一個事件開始檢查簽章。服務商由金鑰判斷。金鑰只用於這幾次呼叫,不會儲存。沒有指定 app 時會建立新的 App;再執行一次會取代原本的設定,而不是新增第二個。
最小權限就夠了:具備 Webhook Endpoints: Write 權限的 Stripe 受限金鑰(restricted key),或對該儲存庫具備 Webhooks: Read and write 權限的 GitHub fine-grained token。
# Stripe: a restricted key, connecting an existing app
curl -X POST https://anyhook.net/api/v1/connect \
-H "Authorization: Bearer {api_key}" \
-H "Content-Type: application/json" \
-d '{ "key": "rk_live_...", "app": "payments" }'
# GitHub: name the repository (or an organization); no app, so one is created
curl -X POST https://anyhook.net/api/v1/connect \
-H "Authorization: Bearer {api_key}" \
-H "Content-Type: application/json" \
-d '{ "key": "github_pat_...", "target": "acme/api" }'Webhook 一建立,GitHub 就會送出一個簽章過的 ping,所以幾秒內就會看到第一個事件。其他服務商:用 POST /api/v1/apps 建立 App,再把它的接收網址貼到服務商的 Webhook 設定裡。
事件
列出事件時可用的查詢參數:
| 參數 | 類型 | 說明 |
|---|---|---|
| app_slug | string | 依 App slug 篩選 |
| status | string | 依狀態篩選(queued、success、retrying、failed、rejected、circuit_open、quota_exceeded) |
| limit | integer | 結果數上限(預設 50,最多 200) |
重播(API)
完整說明和範例見上方的API 重播。
未送達(API)
完整說明和範例見上方的未送達的事件。
批次重播(API)
完整說明和範例見上方的批次重播。
方案上限
| Free | Pro($20/月) | Scale($90/月) | Enterprise | |
|---|---|---|---|---|
| 每月事件數 | 10,000 | 50,000 | 250,000 | 彈性 |
| 每日上限 | 1,000 | 無 | 無 | 無 |
| App 數 | 1 | 10 | 50 | 彈性 |
| 每個 App 的目的地數 | 3 | 20 | 50 | 彈性 |
| 傳遞嘗試次數 | 3 | 5 | 10 | 彈性 |
| 保存期限 | 3 天 | 1 年 | 7 年 | 彈性 |
| 可重播期間 | 3 天 | 30 天 | 90 天 | 最多 365 天 |
| 傳遞逾時 | 60 秒 | 120 秒 | 5 分鐘 | 彈性 |
| payload 上限 | 512 KB | 2 MB | 4 MB | 客製 |
| 超額計費 | 無 | $0.15/1,000 個 | $0.17/1,000 個 | 依合約 |
| 每日重播上限 | 100 | 500 | 1,000 | 不限 |
| 每月程式碼執行次數 | 無 | 3,000 | 12,000 | 彈性 |
| 程式碼執行時間 | 無 | 105 秒 | 285 秒 | 彈性 |
| 運算額度 | 每月 $0.15 | 每月 $3.00 | 每月 $11.00 | 彈性 |
| API 和 MCP 呼叫 | 每天 300 次 | 每天 1,000 次 | 每天 3,000 次 | 彈性 |
用量與配額
AnyHook 以自然月計算用量(例如「2026-04」)。計數器在每個日曆月開始時歸零。
- 每次傳遞算一個:送往兩個目的地的事件算兩個,大型事件則依主體加標頭的大小,每 64 KB 算一個(未滿也算一個)。沒有目的地的 App 收到的事件算一個。被篩選掉的事件和簽章驗證失敗的事件不計。Free 方案的每日上限比較單純:邊緣節點接受的每個請求算一次,不論送往幾個目的地。
- 只算第一次嘗試:重試不計入配額。
- 重播不計:
is_replay: true的事件不列入計費,只有超過配額而被保留(已儲存、不傳遞)的事件,重播時會像新事件一樣算一次。重播會用到運算額度和方案的每日重播上限。 - 運算額度:AnyHook 在每個事件第一次嘗試之外,為你的帳號花的成本,按成本計算:重試、重播、程式碼執行、套件安裝、排程觸發、當機演練,以及等待目的地超過第一秒的時間(重試一個 1 KB 的事件約用掉 $0.00002,執行幾秒鐘的程式碼約 $0.0008)。額度用完後,這些都會停止,直到下個月 1 日(UTC):失敗的傳遞不再重試,重播和程式碼執行會被拒絕,排程暫停,目的地只有一秒可以回應。事件照樣會抵達,也會得到第一次嘗試。
- API 和 MCP 呼叫:用 API 金鑰或 MCP 用戶端發出的每次呼叫算一次,回應超過 32 KB 時,每 32 KB 算一次(未滿也算一次);儀表板的操作不計。超過當天上限後,API 會回 429 並附上
Retry-After,直到 UTC 00:00。GET /api/v1/usage(或透過 MCP 呼叫anyhook_usage)會列出每項上限和已用掉的量。 - 保留,不會丟棄:超過方案內含的事件數(如果設了支出上限,則是超過該上限)之後,每個事件都會以
quota_exceeded狀態記錄下來,但不傳遞。升級或設定支出上限後,就能在儀表板一鍵重播。在支出上限買得到的量之外,最多再保留相當於配額 20% 的事件;超過之後,邊緣節點會以 429 拒收,什麼都不儲存,為失控的來源設下界限。
quota_exceeded 狀態出現在事件紀錄裡,升級後即可重播。超過這個量的事件,和超過每日上限一樣拒收。升級到 Pro 方案就能使用超額計費。超額計費
每月配額用完時,超額計費讓事件繼續傳遞,而不是被保留。只有 Pro 和 Scale 方案提供。
運作方式
- 在 Settings(設定)頁面設定每月支出上限(可選 $5 / $10 / $20 / $50,或選 Off 關閉)。
- 方案的事件配額用完之後,AnyHook 會檢查超額計費是否已啟用,以及支出上限是否還沒用完。
- 如果允許,事件就照常傳遞,並標記為超額用量。
- 下個月初,超額用量會以單一項目列在你的 Stripe 帳單上:Pro 方案每 1,000 個事件 $0.15,Scale 方案每 1,000 個事件 $0.17(按事件計算,無條件進位到分),金額不超過你的支出上限。
個別 App 開關
每個 App 都有超額計費設定:Inherit default(沿用預設)、Enabled(啟用)或 Disabled(停用)。App 預設沿用帳號層級的設定。要個別覆寫,就到 App Settings → Overage Billing 設定。
帳號預設值
在 Settings → Overage Billing,只要支出上限高於 Off,所有設為 Inherit default 的 App(包括既有的 App)都會開啟超額計費;設為 Disabled 的 App 則維持關閉。
quota_exceeded 儲存,等你提高上限或進入下個月後即可重播。超過上限的部分不會計費。超過上限後,最多再保留相當於配額 20% 的事件;其餘的由邊緣節點以 429 拒收,什麼都不儲存。API 金鑰
API 金鑰用來驗證你對 AnyHook REST API 的請求。在 Dashboard → Settings → API Keys 管理。
- 正式環境的金鑰以
ahk_live_開頭。 - 你可以建立多個金鑰並加上標籤(例如「CI/CD」、「Agent」)。
- 金鑰可以立即撤銷,撤銷後的金鑰下次使用時會回傳
401。 - 金鑰只在建立時顯示一次。務必妥善保存。
需要協助?
寫信到 hello@anyhook.net,每封信我們都會回覆。服務的即時狀態可以到 status.anyhook.net 查看。