設定代理人事件的 Webhook
當代理程式的工作完成或失敗、代理程式等待核准,或文件網站發佈時,在您自己的 https 端點上接收已簽署的 POST。包含事件、酬載、簽章驗證、重試與自動停用功能。
Webhook 會在發生特定事件(例如代理人的工作完成或代理人等待核准)時,立即將已簽署的 POST 傳送至您的端點,因此您的系統無需進行輪詢。本文涵蓋如何新增端點、事件說明、酬載、驗證簽章,以及傳送失敗時的處理方式。
開始之前
- Webhook 適用於 Pro、Business 和 Custom 方案(「Webhook 適用於 Pro 及以上方案。」)。
- 僅工作區擁有者與管理員可管理 Webhook。 其他成員會看到「僅擁有者與管理員可管理 Webhook」。
- 您需要一個公開的 https 位址,並能在 10 秒內以
2xx狀態碼回應。 - 每個工作區最多可擁有 10 個 Webhook。
新增 Webhook
開啟 Webhook 分頁
開啟工作區選單,選擇 AI agents & API,然後開啟 Webhooks。點擊 新增 Webhook。

每個 Webhook 都會顯示其事件、上次傳送時間以及密鑰的結尾。 輸入端點
- 端點 URL:「一個公開的 HTTPS 位址。」例如
https://example.com/clevera/webhooks。 - 描述:選填,最多 200 個字元,例如「Slack 中繼」。
- 端點 URL:「一個公開的 HTTPS 位址。」例如
選擇事件
勾選要接收的事件,或選擇 所有事件(包含新事件)。標記為 即將推出 的事件目前已接受設定,但「目前尚無任何項目會傳送此事件」。

標記為「即將推出」的事件目前已接受設定,但尚未傳送。 建立並儲存密鑰
點擊 建立 Webhook。簽署密鑰(開頭為
whsec_)只會顯示一次:「請立即複製:您將無法再次看到它。請使用它來檢查 Clevera-Signature 標頭。」請像儲存密碼一樣妥善保存,然後點擊 我已儲存它。傳送測試事件
開啟 Webhook 的 ⋯ 選單並選擇 傳送測試事件。Clevera 會傳送一次已簽署的
ping事件(不會重試)。「測試事件已傳送」表示它已離開 Clevera;請檢查 傳送紀錄 以查看您端點的回應。
事件
| 事件 | 傳送時機 |
|---|---|
job.started |
代理人啟動的工作開始執行時。 |
job.needs_input |
工作等待人員處理時(例如核准)。包含連結與請求過期時間。 |
job.succeeded |
工作完成時。 |
job.failed |
工作失敗時。包含錯誤代碼。 |
job.canceled |
工作被取消,或其核准被拒絕或過期時。 |
approval.requested |
代理人要求在 代理人請求 中進行核准時。 |
approval.decided |
有人核准或拒絕了請求,或請求已過期時。 |
site.published |
文件網站已發佈時。 |
site.build_failed |
文件網站發佈失敗時。 |
ping |
您點擊了 傳送測試事件。無需訂閱。 |
這些事件標記為 即將推出,目前尚未傳送:job.progress、video.ready、article.ready、research.completed、insights.weekly。
工作事件涵蓋代理人啟動的工作(透過 API 權杖或已連接的應用程式),不包含使用者在應用程式中啟動的影片。
Clevera 傳送的內容
每次傳送都是一個包含 JSON 本文與以下標頭的 POST:
| 標頭 | 值 |
|---|---|
Content-Type |
application/json |
User-Agent |
Clevera-Webhooks/1.0 |
Clevera-Event |
事件名稱,例如 job.succeeded |
Clevera-Delivery |
傳送的唯一 ID。重新傳送會重複使用相同的 ID,因此請用它來忽略重複項目。 |
Clevera-Signature |
t=<unix time>,v1=<signature> |
酬載包含 ID、連結、狀態與數字,絕不包含內容本身。若要讀取完成的影片或文章,請使用權杖呼叫 API。
{
"id": "6f1c2d3e-4b5a-4c6d-8e9f-0a1b2c3d4e5f",
"event": "job.succeeded",
"created_at": "2026-10-20T14:03:11Z",
"data": {
"job": {
"id": "job_8k2m4n6p8r0t2v4x",
"kind": "video.create",
"status": "succeeded",
"error_code": null,
"subject": { "type": "video", "id": "vid_example" },
"credits_spent": 64
},
"needs_input": null,
"workspace": { "id": "tm_example" }
}
}
{
"id": "0b9a8c7d-6e5f-4a3b-9c2d-1e0f9a8b7c6d",
"event": "approval.requested",
"created_at": "2026-10-20T14:01:02Z",
"data": {
"approval": {
"id": "apr_example",
"kind": "spend",
"status": "pending",
"credits_high": 80,
"expires_at": "2026-10-21T14:01:02Z",
"decided_at": null,
"url": "https://app.clevera.ai/agents?tab=requests&team=tm_example"
},
"job": { "id": "job_8k2m4n6p8r0t2v4x", "kind": "video.create" },
"workspace": { "id": "tm_example" }
}
}
驗證簽章
在信任每次傳送之前請先進行檢查。簽章是 HMAC-SHA256,使用您的簽署密鑰,對時間戳記、一個點號以及原始請求本文進行加密:<t>.<body>。
- 從
Clevera-Signature標頭讀取t與v1。 - 計算
HMAC-SHA256(secret, t + "." + rawBody)的十六進位值。 - 以固定時間比較它與
v1。 - 拒絕
t超過 5 分鐘的傳送,以防止擷取的請求被重放。
import crypto from 'node:crypto';
export function verifyClevera(rawBody, header, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(header.split(',').map((p) => p.trim().split('=')));
const t = Number(parts.t);
if (!parts.v1 || !Number.isInteger(t)) return false;
if (Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return expected.length === parts.v1.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
import hashlib, hmac, time
def verify_clevera(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
parts = dict(p.strip().split("=", 1) for p in header.split(","))
t, v1 = parts.get("t", ""), parts.get("v1", "")
if not t.isdigit() or not v1 or abs(time.time() - int(t)) > tolerance:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, v1)
function verifyClevera(string $rawBody, string $header, string $secret, int $tolerance = 300): bool
{
parse_str(str_replace(',', '&', $header), $parts);
$t = $parts['t'] ?? '';
$v1 = $parts['v1'] ?? '';
if (! ctype_digit($t) || $v1 === '' || abs(time() - (int) $t) > $tolerance) {
return false;
}
return hash_equals(hash_hmac('sha256', $t.'.'.$rawBody, $secret), $v1);
}
請使用接收到的原始本文。若先解析並重新編碼 JSON,會改變位元組,導致簽章不符。
重試與失敗
當您的端點以 2xx 狀態碼回應時,即視為傳送成功。任何其他回應都會導致嘗試失敗:
- 任何其他狀態碼,包括重新導向(不跟隨
3xx); - 10 秒內無回應(5 秒內需連線);
- 位址無法再解析至公用網際網路。
Clevera 會在 1 分鐘、5 分鐘、30 分鐘、2 小時與 6 小時後重試失敗的傳送,總共 5 次。系統不會讀取您端點的回應本文。
當連續 15 次傳送嘗試皆失敗時,該 Webhook 將會被關閉:「已連續 15 次傳送失敗,因此關閉。請修正端點,然後開啟它。」擁有者與管理員會收到「Webhook 至 host 已關閉」的通知:「其傳送持續失敗。請修正端點,然後重新開啟它。」在關閉期間發生的事件不會傳送。測試事件不計入此限制。
管理 Webhook
點擊 Webhook 上的 傳送紀錄 以查看其日誌,或開啟其 ⋯ 選單以使用其他功能:
| 選項 | 功能 |
|---|---|
| 編輯 | 變更 URL、事件或描述。 |
| 傳送紀錄 | 最近 100 次傳送及其狀態(成功、失敗、傳送中)、嘗試次數與 HTTP 代碼。重新傳送可再次傳送單一事件。傳送紀錄保留 30 天。 |
| 傳送測試事件 | 傳送一個已簽署的 ping。 |
| 重新產生密鑰 | 「傳送將立即使用新密鑰簽署。請更新您的端點以進行檢查。」沒有重疊期,因此請在操作後立即更新您的端點。 |
| 關閉 / 開啟 | 暫停與恢復傳送。 |
| 刪除 | 「我們將停止向其傳送事件並刪除其傳送紀錄。此動作無法復原。」 |
如果工作區離開 Pro 方案
Webhook 會保留在清單中,且仍可編輯與刪除,但不會傳送任何內容。等待重試的傳送將以 plan_locked 失敗告終,且 傳送測試事件 與 重新傳送 會顯示「Webhook 不在您的方案中」。升級回 Pro 方案後將恢復傳送;不會刪除任何內容。
疑難排解
| 訊息 | 原因 | 修正 |
|---|---|---|
| 「Webhook 不在您的方案中。」 | 工作區不在 Pro、Business 或 Custom 方案。 | 升級至 Pro。 |
| 「請使用 https:// 位址。」 | URL 開頭為 http://。 |
透過 HTTPS 提供端點。 |
| 「請使用主機名稱,而非 IP 位址。」 | URL 使用 IP 位址。 | 使用網域名稱。 |
| 「此位址在公用網際網路上無法存取。」 | 主機解析為私人或本機位址。 | 使用公開端點或中繼。 |
| 「請從位址中移除使用者名稱與密碼。」 | URL 包含 user:password@。 |
移除它們;改為檢查簽章。 |
| 「請輸入有效的網址。」 | URL 無效。 | 檢查是否有拼字錯誤。 |
| 「請至少選擇一個事件。」 | 未勾選事件。 | 至少勾選一個事件。 |
| 「此工作區已有 10 個 Webhook。」 | 達到工作區限制。 | 刪除一個不使用的 Webhook。 |
| 「請先重新開啟 Webhook。」 | 您嘗試在已關閉的 Webhook 上重新傳送。 | 開啟它,然後重新傳送。 |
| 「此傳送仍在傳送中。」 | 傳送仍在重試中。 | 等待其完成。 |
| 傳送以 HTTP 代碼失敗 | 您的端點回應錯誤。 | 檢查端點日誌,確保其能快速回傳 2xx;慢速工作請在回應後執行。 |
| 簽章永遠不符 | 本文在檢查前被變更,或密鑰已過期。 | 針對原始本文進行驗證;若不確定,請重新產生密鑰。 |