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.sitengrok 之類的工具,在開發過程中檢查傳入的 webhook payload。

事件類型

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

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


資料結構 Internal Link

The only structure sent via webhooks is the WebhookComment object, outlined in TypeScript below.

WebhookComment 物件結構

「Create」事件結構

The "create" event request body is a WebhookComment object.

「Update」事件結構

The "update" event request body is a WebhookComment object.

「Delete」事件結構

The "delete" event request body is a WebhookComment object.

變更於 Nov 14th 2023
先前的 "delete" 事件請求主體僅包含評論 id。現在會在刪除時包含完整的評論內容。

Every key is always present in the body. When the comment has no value for a field the body carries null (or false for booleans and [] for lists), so the shape of a delivery never varies from one comment to the next.

WebhookComment 物件
Copy CopyRun External Link
1
2interface WebhookComment {
3 /** The id of the comment. **/
4 id: string
5 /** The id or URL that identifies the comment thread. Normalized. **/
6 urlId: string
7 /** The URL that points to where the comment was left. **/
8 url: string | null
9 /** The user id that left the comment. If SSO, prefixed with tenant id. **/
10 userId: string | null
11 /** The email of the user left the comment. **/
12 commenterEmail: string | null
13 /** The name of the user that shows in the comment widget. With SSO, can be displayName. **/
14 commenterName: string
15 /** Raw comment text. **/
16 comment: string
17 /** Comment text after parsing. **/
18 commentHTML: string
19 /** Comment external id. **/
20 externalId: string | null
21 /** The id of the parent comment. **/
22 parentId: string | null
23 /** The UTC date when the comment was left. **/
24 date: UTC_ISO_DateString
25 /** Combined karma (up - down) of votes. **/
26 votes: number
27 votesUp: number
28 votesDown: number
29 /** True if the user was logged in when they commented, or their verified the comment, or if they verified their session when the comment was left. **/
30 verified: boolean
31 /** The UTC date when the comment was verified. **/
32 verifiedDate: UTC_ISO_DateString | null
33 /** If a moderator marked the comment reviewed. **/
34 reviewed: boolean
35 /** The location, or base64 encoding, of the avatar. Will only be base64 if that was the value passed with SSO. **/
36 avatarSrc: string | null
37 /** Was the comment manually or automatically marked as spam? **/
38 isSpam: boolean
39 /** Was the comment automatically marked as spam? **/
40 aiDeterminedSpam: boolean
41 /** Are there images in the comment? **/
42 hasImages: boolean
43 /** The page number the comment is on for the "Most Relevant" sort direction. **/
44 pageNumber: number | null
45 /** The page number the comment is on for the "Oldest First" sort direction. **/
46 pageNumberOF: number | null
47 /** The page number the comment is on for the "Newest First" sort direction. **/
48 pageNumberNF: number | null
49 /** Was the comment approved automatically or manually? **/
50 approved: boolean
51 /** The locale code (format: en_us) of the user when the comment was written. **/
52 locale: string | null
53 /** The @mentions written in the comment that were successfully parsed. Empty when there are none. **/
54 mentions: CommentUserMention[]
55 /** The domain the comment is from. **/
56 domain: string | null
57 /** The moderation group ids associated with this comment. Empty when there are none. **/
58 moderationGroupIds: string[]
59}
60

When users are tagged in a comment, the information is stored in a list called mentions. Each object in that list has the following structure.

Webhook 提及 物件
Copy CopyRun External Link
1
2interface CommentUserMention {
3 /** The user id. For SSO users, this will have your tenant id prefixed. **/
4 id: string
5 /** The final @mention tag text, including the @ symbol. **/
6 tag: string
7 /** The original @mention tag text, including the @ symbol. **/
8 rawTag: string
9 /** What type of user was tagged. user = FastComments.com account. sso = SSOUser. **/
10 type: 'user'|'sso'
11 /** If the user opts out of notifications, this will still be set to true. **/
12 sent: boolean
13}
14

HTTP 方法

You can configure the HTTP method for each webhook event type in the admin panel:

  • Create Event: POST or PUT (default: PUT)
  • Update Event: POST or PUT (default: PUT)
  • Delete Event: DELETE, POST, or PUT (default: DELETE)

Since all requests contain an ID, Create and Update operations are idempotent by default (PUT). Repeating the same Create or Update request should not create duplicate objects on your side.

請求標頭

Each webhook request includes the following headers:

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

See Security & API Tokens for information on verifying the HMAC signature.

安全性與 API 令牌 Internal Link

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

Headers Sent

HeaderDescription
token您的 API Secret(為了向後相容)
X-FastComments-TimestampUnix 時間戳(秒),表示請求簽名的時間
X-FastComments-Signature載荷的 HMAC-SHA256 簽名

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

Signature Format: sha256=<hex-encoded-signature>

How the signature is computed:

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

Example Verification (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}`;
}

Example Verification (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}"

Example Verification (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);
}

Legacy Authentication

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


透過 API 管理 Webhook Internal Link

Webhooks 也可以透過 REST API 進行管理。這就是像 Zapier 這樣的整合在不觸碰儀表板的情況下訂閱評論事件的方式,且它遵循 REST Hooks 模式:訂閱、接收事件、取消訂閱。

API 訂閱與儀表板中設定的 Webhooks 同時存在。評論事件會傳送給每一個符合其 domain 的 webhook,各自作為獨立的傳遞,無論 webhook 是以何種方式建立。

Authentication

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

Subscribe

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。
eventcomment-createdcomment-updatedcomment-deleted
domain您帳號設定中的一個 domain。預設為 *,會接收所有 domain 的事件。
methodPOST(預設)、PUTDELETE

回應中會包含此訂閱資訊:

{
    "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、相同的事件與 domain 再次訂閱時,會回傳已存在的訂閱,而不是建立重複項目,讓客戶端可以安全地重試。每個租戶最多可有 50 個 API 訂閱。

List

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

返回該租戶的所有 webhook,包括儀表板中管理的("source": "dashboard")。可使用 eventdomainsource 進行過濾。

Unsubscribe

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

刪除訂閱同時會丟棄仍在佇列中的任何事件。只有透過 API 建立的訂閱才能以此方式刪除;儀表板的 webhook,或是帳號中不存在的 ID,會回傳 404 並帶有 not-found 錯誤碼。儀表板的 webhook 可在 Webhooks 頁面上編輯。

Payloads and signing

傳遞使用與儀表板 webhook 相同的 payload(請參閱 Data Structures),並以相同的 HMAC 方式簽名(請參閱 Security & API Tokens)。API 訂閱永不會收到舊版的 token 標頭,請改為驗證 X-FastComments-Signature 標頭。

Sample payloads

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
        }
    ]
}

Responding with 410 Gone

如果 API 訂閱的端點回應 HTTP 410 Gone,FastComments 會將其視為取消訂閱:訂閱會被刪除,佇列中的事件也會一起清除,且不會再嘗試傳遞。儀表板中設定的 webhook 永不會自動刪除;對它們而言 410 只是普通的失敗。其他任何失敗狀態都會重試,最終會停用該 webhook,詳情請參閱 How it Works & Handling Retries。

Dashboard

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

結論

以上即為我們的 Webhooks 文件。

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

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