FastComments.com

Webhook

使用 FastComments,可以在評論被新增、更新或從我們的系統中移除時呼叫一個 API 端點。

我們透過 HTTP/HTTPS 的非同步 webhooks 來達成這個目的。

什麼是 Webhook Internal Link

Webhook 是一種機制,或是兩個系統之間的整合,其中的 "producer" (FastComments) 觸發一個事件 而 "consumer" (您) 則透過 API 呼叫來接收並處理該事件。

支援的事件與資源 Internal Link

FastComments 僅支援對 Comment 資源的 webhook。

我們支援對評論的建立、刪除以及更新的 webhook。

這些在我們系統中被視為獨立的事件,因而具有不同的語意 並且有不同的 webhook 事件結構。

任意數量的端點都可以從儀表板或透過 API 訂閱相同的事件 (請參閱 透過 API 管理 Webhooks)。每個 webhook 都會獨立傳送。

測試 Internal Link

新的和編輯 webhook 頁面都有一個 Send Test Payload 按鈕,會將請求發送到表單中目前的 URL,無論是否已儲存。Create 和 Update 事件會傳送一個虛擬的 WebhookComment 物件,而測試 Delete 時會傳送僅包含 ID 的虛擬請求主體。

驗證有效負載

測試 webhook 整合時,請確認傳入的請求包含以下標頭:

  1. X-FastComments-Timestamp - Unix 時間戳記(秒)
  2. X-FastComments-Signature - HMAC-SHA256 簽名

在引入簽名機制之前建立的 webhook 也會收到包含您 API 密鑰的 token 標頭。新 webhook 則不會。

使用 HMAC 簽名驗證以確保 payload 為真實的。

測試工具

您可以使用像是 webhook.site 或 ngrok 之類的工具,在開發過程中檢查傳入的 webhook payload。

事件類型

  • Create Event:當新評論被建立時觸發。
  • Update Event:當評論被編輯時觸發。
  • Delete Event:當評論被刪除時觸發。

每個 webhook 皆綁定單一事件與單一 HTTP 方法(POST、PUT 或 DELETE)。每個事件在請求主體中包含完整的評論資料(請參閱 Data Structures 了解 payload 格式)。

資料結構 Internal Link

透過 webhook 傳送的唯一結構是 WebhookComment 物件,以下以 TypeScript 說明。

WebhookComment 物件結構

「Create」事件結構

「create」事件的請求主體是一個 WebhookComment 物件。

「Update」事件結構

「update」事件的請求主體是一個 WebhookComment 物件。

「Delete」事件結構

「delete」事件的請求主體是一個 WebhookComment 物件。

2023 年 11 月 14 日的變更
先前「delete」事件的請求主體僅包含留言 id。現在則包含刪除當下的完整留言內容。

每個鍵值一律都會出現在請求主體中。當留言的某個欄位沒有值時,主體會帶有 null (布林值為 false,列表為 []),因此每次傳送的資料形狀不會因留言不同而改變。

WebhookComment 物件
Copy CopyRun External Link
1
2interface WebhookComment {
3 /** 留言的 id。 **/
4 id: string
5 /** 標識留言串的 id 或 URL。已標準化。 **/
6 urlId: string
7 /** 指向留言所在位置的 URL。 **/
8 url: string | null
9 /** 留言者的使用者 id。若為 SSO,則以租戶 id 為前綴。 **/
10 userId: string | null
11 /** 留言者的電子郵件。 **/
12 commenterEmail: string | null
13 /** 在留言小工具中顯示的使用者名稱。使用 SSO 時,可以是 displayName。 **/
14 commenterName: string
15 /** 原始留言文字。 **/
16 comment: string
17 /** 解析後的留言文字。 **/
18 commentHTML: string
19 /** 留言的外部 id。 **/
20 externalId: string | null
21 /** 父留言的 id。 **/
22 parentId: string | null
23 /** 留言發表的 UTC 日期。 **/
24 date: UTC_ISO_DateString
25 /** 投票的合計業力(讚 - 倒讚)。 **/
26 votes: number
27 votesUp: number
28 votesDown: number
29 /** 若使用者在留言時已登入、或已驗證留言、或在留言時已驗證其工作階段,則為 true。 **/
30 verified: boolean
31 /** 留言被驗證的 UTC 日期。 **/
32 verifiedDate: UTC_ISO_DateString | null
33 /** 若版主已將留言標記為已審閱。 **/
34 reviewed: boolean
35 /** 頭像的位置或 base64 編碼。僅在 SSO 傳入的值為 base64 時才會是 base64。 **/
36 avatarSrc: string | null
37 /** 留言是被手動還是自動標記為垃圾訊息? **/
38 isSpam: boolean
39 /** 留言是否被自動標記為垃圾訊息? **/
40 aiDeterminedSpam: boolean
41 /** 留言中是否有圖片? **/
42 hasImages: boolean
43 /** 留言在「最相關」排序方向下所在的頁碼。 **/
44 pageNumber: number | null
45 /** 留言在「最舊優先」排序方向下所在的頁碼。 **/
46 pageNumberOF: number | null
47 /** 留言在「最新優先」排序方向下所在的頁碼。 **/
48 pageNumberNF: number | null
49 /** 留言是被自動還是手動核准? **/
50 approved: boolean
51 /** 撰寫留言時使用者的語系代碼(格式:en_us)。 **/
52 locale: string | null
53 /** 留言中成功解析出的 @mentions。若無則為空。 **/
54 mentions: CommentUserMention[]
55 /** 留言所屬的網域。 **/
56 domain: string | null
57 /** 與此留言相關聯的審核群組 id。若無則為空。 **/
58 moderationGroupIds: string[]
59 /** 此留言中留言者名稱旁顯示的徽章,以事件建立當下為準。若無則為空。 **/
60 badges: CommentUserBadgeInfo[]
61}
62

當使用者在留言中被標記時,相關資訊會儲存在名為 mentions 的列表中。該列表中的每個物件 具有以下結構。

Webhook Mentions 物件
Copy CopyRun External Link
1
2interface CommentUserMention {
3 /** 使用者 id。對於 SSO 使用者,此值會以您的租戶 id 為前綴。 **/
4 id: string
5 /** 最終的 @mention 標籤文字,包含 @ 符號。 **/
6 tag: string
7 /** 原始的 @mention 標籤文字,包含 @ 符號。 **/
8 rawTag: string
9 /** 被標記的使用者類型。user = FastComments.com 帳號。sso = SSOUser。 **/
10 type: 'user'|'sso'
11 /** 若使用者選擇退出通知,此值仍會設為 true。 **/
12 sent: boolean
13}
14

badges 列表中的每個項目具有以下結構。徽章類型請參閱徽章指南。

Webhook Badge 物件
Copy CopyRun External Link
1
2interface CommentUserBadgeInfo {
3 /** 授予此使用者的徽章 id。 **/
4 id: string
5 /** 徽章類型。 **/
6 type: number
7 /** 徽章描述,滑鼠懸停在徽章上時顯示。 **/
8 description: string
9 /** 徽章上顯示的文字。 **/
10 displayLabel?: string | null
11 /** 徽章顯示的圖片(若使用的話)。 **/
12 displaySrc?: string | null
13 backgroundColor?: string | null
14 borderColor?: string | null
15 textColor?: string | null
16 /** 套用於徽章的額外 CSS 類別。 **/
17 cssClass?: string | null
18}
19

HTTP 方法

您可以在管理面板中為每種 webhook 事件類型設定 HTTP 方法:

  • Create 事件:POST 或 PUT(預設:PUT)
  • Update 事件:POST 或 PUT(預設:PUT)
  • Delete 事件:DELETE、POST 或 PUT(預設:DELETE)

由於所有請求都包含一個 ID,Create 與 Update 操作預設即為冪等(PUT)。重複相同的 Create 或 Update 請求不應在您那邊建立重複的物件。

請求標頭

每個 webhook 請求都包含以下標頭:

標頭說明
Content-Typeapplication/json
token您的 API Secret
X-FastComments-Timestamp請求簽署時的 Unix 時間戳(秒)
X-FastComments-SignatureHMAC-SHA256 簽名(sha256=<hex>)

有關驗證 HMAC 簽名的資訊,請參閱安全性與 API 權杖。

安全性與 API 令牌 Internal Link

FastComments 的 webhook 請求包含多種驗證機制以確保安全。

傳送的標頭

標頭說明
token您的 API Secret(為了向下相容)
X-FastComments-Timestamp請求簽署時的 Unix 時間戳(秒)
X-FastComments-Signature負載的 HMAC-SHA256 簽名

HMAC 簽名驗證(建議)

我們強烈建議驗證 HMAC 簽名,以確保 webhook 負載是真實的且未被竄改。

簽名格式: sha256=<hex-encoded-signature>

簽名的計算方式:

  1. 串接:timestamp + "." + JSON_payload_body
  2. 以您的 API Secret 作為金鑰計算 HMAC-SHA256
  3. 將結果以十六進位編碼

驗證範例(Node.js)

const crypto = require('crypto');

function verifyWebhookSignature(req, apiSecret) {
    const timestamp = req.headers['x-fastcomments-timestamp'];
    const signature = req.headers['x-fastcomments-signature'];

    if (!timestamp || !signature) {
        return false;
    }

    // 驗證時間戳是否為近期(5 分鐘內)
    const now = Math.floor(Date.now() / 1000);
    if (Math.abs(now - parseInt(timestamp, 10)) > 300) {
        return false;  // 防止重放攻擊
    }

    // 驗證簽名
    const payload = JSON.stringify(req.body);
    const expectedSignature = crypto
        .createHmac('sha256', apiSecret)
        .update(`${timestamp}.${payload}`)
        .digest('hex');

    return signature === `sha256=${expectedSignature}`;
}

驗證範例(Python)

import hmac
import hashlib
import time
import json

def verify_webhook_signature(headers, body, api_secret):
    timestamp = headers.get('X-FastComments-Timestamp')
    signature = headers.get('X-FastComments-Signature')

    if not timestamp or not signature:
        return False

    # 驗證時間戳是否為近期
    now = int(time.time())
    if abs(now - int(timestamp)) > 300:
        return False

    # 驗證簽名
    payload = json.dumps(body, separators=(',', ':'))
    message = f"{timestamp}.{payload}"
    expected = hmac.new(
        api_secret.encode(),
        message.encode(),
        hashlib.sha256
    ).hexdigest()

    return signature == f"sha256={expected}"

驗證範例(PHP)

function verifyWebhookSignature($headers, $body, $apiSecret) {
    $timestamp = $headers['X-FastComments-Timestamp'] ?? null;
    $signature = $headers['X-FastComments-Signature'] ?? null;

    if (!$timestamp || !$signature) {
        return false;
    }

    // 驗證時間戳是否為近期(5 分鐘內)
    $now = time();
    if (abs($now - intval($timestamp)) > 300) {
        return false;
    }

    // 驗證簽名
    $payload = json_encode($body, JSON_UNESCAPED_SLASHES);
    $message = $timestamp . '.' . $payload;
    $expectedSignature = 'sha256=' . hash_hmac('sha256', $message, $apiSecret);

    return hash_equals($expectedSignature, $signature);
}

舊版驗證方式

包含您 API Secret 的 token 標頭仍會傳送以維持向下相容。然而,我們建議遷移至 HMAC 驗證以提升安全性,因為它可以防止重放攻擊。

透過 API 管理 Webhook Internal Link

Webhooks 也可以透過 REST API 管理。像 Zapier 這類整合服務就是透過這種方式訂閱留言事件,而無需操作儀表板,並且遵循 REST Hooks 模式:訂閱、接收事件、取消訂閱。

API 訂閱與儀表板中設定的 webhooks 並存。留言事件會傳送給每個符合其網域的 webhook,每個都是獨立的傳送,無論該 webhook 是以何種方式建立的。

驗證

每個請求都需要在 x-api-key 標頭(或 API_KEY 查詢參數)中提供您的 API Key,並在 tenantId 查詢參數中提供您的租戶 ID。兩者都顯示在儀表板的 API Secret 頁面上。

訂閱

POST https://fastcomments.com/api/v1/webhooks?tenantId=YOUR_TENANT_ID
x-api-key: YOUR_API_KEY
Content-Type: application/json

{
    "url": "https://hooks.zapier.com/hooks/catch/123/abc",
    "event": "comment-created"
}
欄位必填說明
url是絕對的 http 或 https URL。
event是comment-created、comment-updated 或 comment-deleted。
domain否您帳戶設定中的網域。預設為 *,會接收所有網域的事件。
method否POST(預設)、PUT 或 DELETE。

回應包含該訂閱:

{
    "status": "success",
    "webhook": {
        "id": "66f1c4c1e7a2b3d4f5a6b7c8",
        "url": "https://hooks.zapier.com/hooks/catch/123/abc",
        "event": "comment-created",
        "domain": "*",
        "method": "POST",
        "source": "api",
        "enabled": true,
        "createdAt": "2026-09-08T12:00:00.000Z"
    }
}

將相同的 URL 再次訂閱相同的事件與網域時,會回傳現有的訂閱而非建立重複項,因此用戶端可以安全地重試。每個租戶最多可有 50 個 API 訂閱。

列出

GET https://fastcomments.com/api/v1/webhooks?tenantId=YOUR_TENANT_ID

回傳該租戶的所有 webhook,包括在儀表板中管理的那些("source": "dashboard")。可以使用 event、domain 或 source 進行篩選。

取消訂閱

DELETE https://fastcomments.com/api/v1/webhooks/SUBSCRIPTION_ID?tenantId=YOUR_TENANT_ID

刪除訂閱時,也會一併捨棄仍在佇列中的事件。只有透過 API 建立的訂閱才能以這種方式刪除;儀表板 webhook,或您帳戶中不存在的 id,會回應 404 與代碼 not-found。儀表板 webhook 請在 Webhooks 頁面上編輯。

負載與簽署

傳送內容使用與儀表板 webhook 相同的負載(參閱 Data Structures),並以相同的 HMAC 方案簽署(參閱 Security & API Tokens)。API 訂閱永遠不會收到舊版的 token 標頭,因此請改為驗證 X-FastComments-Signature 標頭。

範例負載

GET https://fastcomments.com/api/v1/webhooks/sample-payloads?tenantId=YOUR_TENANT_ID&event=comment-created&limit=3

以傳送內容的確切格式回傳帳戶最近的留言,讓整合服務可以在第一個事件到達之前顯示真實的範例資料。event 為選填且僅做驗證,因為每個事件都傳送相同的留言物件。limit 預設為 3,接受 1 到 10。花費 2 個 API 點數。

{
    "status": "success",
    "payloads": [
        {
            "id": "66f1c4c1e7a2b3d4f5a6b7c8",
            "urlId": "https://example.com/blog/hello-world",
            "commenterName": "Jane Reader",
            "comment": "Great article!",
            "date": "2026-09-08T12:00:00.000Z",
            "approved": true
        }
    ]
}

以 410 Gone 回應

如果 API 訂閱的端點以 HTTP 410 Gone 回應,FastComments 會將其視為取消訂閱:該訂閱會連同其佇列中的事件一起刪除,並且不再嘗試任何後續傳送。在儀表板中設定的 webhooks 永遠不會被自動刪除;對它們而言,410 只是一般的失敗。任何其他失敗狀態會被重試,並最終停用該 webhook,如 How it Works & Handling Retries 中所述。

儀表板

API 訂閱會以來源 API 顯示在 Webhooks 清單中,管理員可以在那裡編輯、停用、重新啟用或刪除它們。

結論

以上即為我們的 Webhooks 文件。

我們希望您覺得 FastComments 的 Webhook 整合容易理解且快速設定。

如果您發現我們的文件有任何遺漏,請在下方讓我們知道。