FastComments.com

Webhooks

With FastComments it's possible to invoke an API endpoint whenever a comment gets added, updated, or removed from our system.

We accomplish this with asynchronous webhooks over HTTP/HTTPS.

What are Webhooks Internal Link

A Webhook is a mechanism, or an integration, between two systems where the "producer" (FastComments) fires an event that the "consumer" (You) consumes via an API call.

Supported Events & Resources Internal Link

FastComments supports webhooks for the Comment resource only.

We support webhooks for comment creation, removal, and on update.

Each of these are considered separate events in our system and as such have different semantics and structures for the webhook events.

Any number of endpoints can subscribe to the same event, from the dashboard or through the API (see Managing Webhooks via the API). Each webhook is delivered independently.

Testing 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.

Verifying Payloads

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

  1. X-FastComments-Timestamp - Unix timestamp (seconds)
  2. X-FastComments-Signature - HMAC-SHA256 signature

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.

Testing Tools

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

Event Types

  • Create Event: Triggered when a new comment is created.
  • Update Event: Triggered when a comment is edited.
  • Delete Event: Triggered when a comment is deleted.

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).

Data Structures Internal Link

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

The WebhookComment Object Structure

The "Create" Event Structure

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

The "Update" Event Structure

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

The "Delete" Event Structure

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

Change as of Nov 14th 2023
Previously the "delete" event request body only contained the comment id. It now contains the full comment at the time of deletion.

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.

The WebhookComment Object
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.

The Webhook Mentions Object
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 Methods

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.

Request Headers

Each webhook request includes the following headers:

HeaderDescription
Content-Typeapplication/json
tokenYour API Secret
X-FastComments-TimestampUnix timestamp (seconds) when the request was signed
X-FastComments-SignatureHMAC-SHA256 signature (sha256=<hex>)

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

Security & API Tokens Internal Link

FastComments webhook requests include multiple authentication mechanisms for security.

Headers Sent

HeaderDescription
tokenYour API Secret (for backwards compatibility)
X-FastComments-TimestampUnix timestamp (seconds) when the request was signed
X-FastComments-SignatureHMAC-SHA256 signature of the payload

We strongly recommend verifying the HMAC signature to ensure webhook payloads are authentic and haven't been tampered with.

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

How the signature is computed:

  1. Concatenate: timestamp + "." + JSON_payload_body
  2. Compute HMAC-SHA256 using your API Secret as the key
  3. Hex-encode the result

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

    // Verify timestamp is recent (within 5 minutes)
    const now = Math.floor(Date.now() / 1000);
    if (Math.abs(now - parseInt(timestamp, 10)) > 300) {
        return false;  // Replay attack prevention
    }

    // Verify signature
    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

    # Verify timestamp is recent
    now = int(time.time())
    if abs(now - int(timestamp)) > 300:
        return False

    # Verify signature
    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;
    }

    // Verify timestamp is recent (within 5 minutes)
    $now = time();
    if (abs($now - intval($timestamp)) > 300) {
        return false;
    }

    // Verify signature
    $payload = json_encode($body, JSON_UNESCAPED_SLASHES);
    $message = $timestamp . '.' . $payload;
    $expectedSignature = 'sha256=' . hash_hmac('sha256', $message, $apiSecret);

    return hash_equals($expectedSignature, $signature);
}

Legacy Authentication

The token header containing your API Secret is still sent for backwards compatibility. However, we recommend migrating to HMAC verification for improved security as it protects against replay attacks.

Managing Webhooks via the API Internal Link

Webhooks can also be managed through the REST API. This is how integrations such as Zapier subscribe to comment events without touching the dashboard, and it follows the REST Hooks pattern: subscribe, receive events, unsubscribe.

API subscriptions live alongside the webhooks configured in the dashboard. A comment event is delivered to every webhook that matches its domain, each as its own delivery, whichever way the webhook was created.

Authentication

Every request needs your API Key in the x-api-key header (or the API_KEY query parameter) and your tenant ID in the tenantId query parameter. Both are shown on the API Secret page in the dashboard.

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"
}
FieldRequiredDescription
urlYesAn absolute http or https URL.
eventYescomment-created, comment-updated or comment-deleted.
domainNoA domain from your account configuration. Defaults to *, which receives events for every domain.
methodNoPOST (default), PUT or DELETE.

The response contains the subscription:

{
    "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"
    }
}

Subscribing the same URL to the same event and domain again returns the existing subscription rather than creating a duplicate, so a client can safely retry. Each tenant can have up to 50 API subscriptions.

List

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

Returns every webhook for the tenant, including those managed in the dashboard ("source": "dashboard"). Filter with event, domain or source.

Unsubscribe

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

Deleting a subscription also discards any events still queued for it. Only subscriptions created through the API can be deleted this way; a dashboard webhook, or an id that does not exist on your account, answers 404 with code not-found. Dashboard webhooks are edited on the Webhooks page.

Payloads and signing

Deliveries use the same payload as dashboard webhooks (see Data Structures) and are signed with the same HMAC scheme (see Security & API Tokens). API subscriptions never receive the legacy token header, so verify the X-FastComments-Signature header instead.

Sample payloads

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

Returns the account's most recent comments in exactly the shape a delivery carries, so an integration can show real sample data before the first event arrives. event is optional and only validated, since every event delivers the same comment object. limit defaults to 3 and accepts 1 to 10. Costs 2 API credits.

{
    "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

If an API subscription's endpoint responds with HTTP 410 Gone, FastComments treats that as an unsubscribe: the subscription is deleted along with its queued events, and no further deliveries are attempted. Webhooks configured in the dashboard are never deleted automatically; for them a 410 is an ordinary failure. Any other failure status is retried and eventually disables the webhook, as described in How it Works & Handling Retries.

Dashboard

API subscriptions appear in the Webhooks list with the source API, where an administrator can edit, disable, re-enable or delete them.

In Conclusion

This concludes our Webhooks documentation.

We hope you find the FastComments Webhook integration easy to understand and fast to set up.

If you feel you have identified any gaps in our documentation, let us know below.