FastComments.com

웹훅

FastComments를 사용하면 댓글이 시스템에 추가되거나, 업데이트되거나, 제거될 때마다 API 엔드포인트를 호출할 수 있습니다.

우리는 이를 HTTP/HTTPS를 통한 비동기 웹후크로 구현합니다.

웹훅이란 무엇인가 Internal Link


웹훅은 두 시스템 간의 메커니즘 또는 통합으로, "생산자" (FastComments)가 이벤트를 발생시키고 "소비자" (사용자)가 API 호출을 통해 해당 이벤트를 소비합니다.


지원되는 이벤트 및 리소스 Internal Link


FastComments는 Comment 리소스에 대해서만 웹훅을 지원합니다.

우리는 댓글 생성, 삭제 및 업데이트에 대한 웹훅을 지원합니다.

이 각각은 우리 시스템에서 별개의 이벤트로 간주되며, 따라서 서로 다른 의미를 그리고 웹훅 이벤트에 대한 구조를 가집니다.

대시보드 또는 API를 통해 동일한 이벤트에 여러 엔드포인트가 (Managing Webhooks via the API 참조). 각 웹훅은 독립적으로 전달됩니다.


테스트 Internal Link

The new and edit webhook pages have a Send Test Payload button that sends a request to the URL currently in the form, whether or not it has been saved. The Create and Update events send a dummy WebhookComment object, while testing Delete will send a dummy request body with just an ID.

페이로드 검증

When testing your webhook integration, verify the incoming requests include the following headers:

  1. X-FastComments-Timestamp - Unix 타임스탬프(초)
  2. X-FastComments-Signature - HMAC-SHA256 서명

Webhooks created before the signature scheme was introduced also receive a token header containing your API Secret. New webhooks do not.

Use the HMAC signature verification to ensure payloads are authentic.

테스트 도구

You can use tools like webhook.site or ngrok to inspect incoming webhook payloads during development.

이벤트 유형

  • Create Event: 새 댓글이 생성될 때 트리거됩니다.
  • Update Event: 댓글이 편집될 때 트리거됩니다.
  • Delete Event: 댓글이 삭제될 때 트리거됩니다.

Each webhook is tied to one event and one HTTP method (POST, PUT or DELETE). Each event includes the full comment data in the request body (see Data Structures for the payload format).

데이터 구조 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.

20231114일 변경
이전에는 "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 또는 PUT (기본값: PUT)
  • Update Event: POST 또는 PUT (기본값: PUT)
  • Delete Event: DELETE, POST 또는 PUT (기본값: 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:

HeaderDescription
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 웹훅 요청에는 보안을 위해 여러 인증 메커니즘이 포함되어 있습니다.

전송되는 헤더

헤더설명
token귀하의 API Secret(하위 호환성용)
X-FastComments-Timestamp요청이 서명될 때의 Unix 타임스탬프(초)
X-FastComments-Signature페이로드의 HMAC-SHA256 서명

HMAC 서명 검증 (권장)

웹훅 페이로드가 진본이며 변조되지 않았는지 확인하기 위해 HMAC 서명 검증을 강력히 권장합니다.

서명 형식: sha256=<hex-encoded-signature>

서명이 계산되는 방법:

  1. 결합: timestamp + "." + JSON_payload_body
  2. API Secret을 키로 사용하여 HMAC-SHA256을 계산
  3. 결과를 16진수로 인코딩

예시 검증 (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 검증은 재생 공격으로부터 보호하므로 보안 향상을 위해 HMAC 검증으로 마이그레이션할 것을 권장합니다.


API를 통한 웹훅 관리 Internal Link

Webhooks는 REST API를 통해서도 관리할 수 있습니다. 이는 Zapier와 같은 통합이 대시보드에 손대지 않고도 댓글 이벤트를 구독하는 방식이며, REST Hooks 패턴을 따릅니다: 구독, 이벤트 수신, 구독 취소.

API 구독은 대시보드에 설정된 웹훅과 함께 존재합니다. 댓글 이벤트는 해당 도메인과 일치하는 모든 웹훅에 각각 별개의 전달로 전송되며, 웹훅이 생성된 방식에 관계없이 전달됩니다.

인증

모든 요청에는 x-api-key 헤더(또는 API_KEY 쿼리 매개변수)에 API 키를, tenantId 쿼리 매개변수에 테넌트 ID를 포함해야 합니다. 두 값은 대시보드의 API 비밀 페이지에 표시됩니다.

구독

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"
}
FieldRequiredDescription
urlYes절대적인 http 또는 https URL입니다.
eventYescomment-created, comment-updated 또는 comment-deleted.
domainNo계정 설정에서 지정한 도메인입니다. 기본값은 *, 이는 모든 도메인의 이벤트를 수신합니다.
methodNoPOST(기본값), 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

테넌트에 대한 모든 웹훅을 반환하며, 대시보드에서 관리되는 웹훅("source": "dashboard")도 포함됩니다. event, domain 또는 source 로 필터링할 수 있습니다.

구독 취소

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

구독을 삭제하면 해당 구독에 아직 대기 중인 이벤트도 모두 폐기됩니다. 이 방법으로 삭제할 수 있는 것은 API를 통해 생성된 구독뿐이며, 대시보드 웹훅이나 계정에 존재하지 않는 ID는 404와 코드 not-found를 반환합니다. 대시보드 웹훅은 웹훅 페이지에서 편집합니다.

페이로드 및 서명

전달은 대시보드 웹훅과 동일한 페이로드를 사용합니다(데이터 구조 참조) 및 동일한 HMAC 방식으로 서명됩니다(보안 및 API 토큰 참조). 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까지 허용됩니다. API 크레딧 2개가 소모됩니다.

{
    "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는 이를 구독 취소로 간주합니다: 구독이 삭제되고 대기 중인 이벤트도 함께 삭제되며, 이후 전달이 시도되지 않습니다. 대시보드에 설정된 웹훅은 자동으로 삭제되지 않으며, 이 경우 410은 일반적인 실패로 처리됩니다. 다른 실패 상태는 재시도되며 결국 웹훅이 비활성화됩니다. 자세한 내용은 작동 방식 및 재시도 처리 를 참고하십시오.

대시보드

API 구독은 웹훅 목록에 API 소스로 표시되며, 관리자는 이를 편집, 비활성화, 재활성화 또는 삭제할 수 있습니다.



결론

이로써 우리의 Webhooks 문서가 마무리됩니다.

FastComments Webhook 통합이 이해하기 쉽고 빠르게 설정되기를 바랍니다.

문서에서 누락된 부분을 발견하셨다고 생각되면 아래에 알려주세요.