FastComments.com

Webhooks

Met FastComments is het mogelijk een API-eindpunt aan te roepen telkens wanneer een reactie aan ons systeem wordt toegevoegd, bijgewerkt of verwijderd.

Wij realiseren dit met asynchrone webhooks via HTTP/HTTPS.

Wat zijn Webhooks Internal Link

Een Webhook is een mechanisme, of een integratie, tussen twee systemen waar de "producent" (FastComments) een gebeurtenis die de "consument" (U) via een API-aanroep verwerkt.

Ondersteunde gebeurtenissen en bronnen Internal Link

FastComments ondersteunt webhooks alleen voor de Comment resource.

We ondersteunen webhooks voor het aanmaken, verwijderen en bijwerken van commentaar.

Elk van deze wordt beschouwd als een afzonderlijk evenement in ons systeem en heeft daarom verschillende semantiek en structuren voor de webhook‑evenementen.

Een willekeurig aantal eindpunten kan zich abonneren op hetzelfde evenement, via het dashboard of via de API (zie Managing Webhooks via de API). Elke webhook wordt onafhankelijk afgeleverd.

Testen Internal Link

De nieuwe en bewerk webhook‑pagina's hebben een Send Test Payload‑knop die een verzoek naar de URL in het formulier stuurt, ongeacht of deze is opgeslagen. De Create‑ en Update‑gebeurtenissen sturen een dummy WebhookComment‑object, terwijl bij het testen van Delete een dummy request‑body met alleen een ID wordt verzonden.

Verifiëren van payloads

Bij het testen van je webhook‑integratie, controleer of de binnenkomende verzoeken de volgende headers bevatten:

  1. X-FastComments-Timestamp – Unix‑tijdstempel (seconden)
  2. X-FastComments-Signature – HMAC‑SHA256‑handtekening

Webhooks die zijn aangemaakt vóór de introductie van het handtekeningschema ontvangen ook een token‑header met je API‑secret. Nieuwe webhooks niet.

Gebruik de HMAC‑handtekeningverificatie om te garanderen dat payloads authentiek zijn.

Testhulpmiddelen

Je kunt tools zoals webhook.site of ngrok gebruiken om binnenkomende webhook‑payloads tijdens de ontwikkeling te inspecteren.

Evenementtypen

  • Create Event: Wordt geactiveerd wanneer een nieuw commentaar wordt aangemaakt.
  • Update Event: Wordt geactiveerd wanneer een commentaar wordt bewerkt.
  • Delete Event: Wordt geactiveerd wanneer een commentaar wordt verwijderd.

Elke webhook is gekoppeld aan één evenement en één HTTP‑methode (POST, PUT of DELETE). Elk evenement bevat de volledige commentaargegevens in de request‑body (zie Data Structures voor het payload‑formaat).

Gegevensstructuren Internal Link

De enige structuur die via webhooks wordt verstuurd is het WebhookComment object, hieronder uitgelijnd in TypeScript.

De WebhookComment Objectstructuur

De "Create" Eventstructuur

De request body van het "create" event is een WebhookComment object.

De "Update" Eventstructuur

De request body van het "update" event is een WebhookComment object.

De "Delete" Eventstructuur

De request body van het "delete" event is een WebhookComment object.

Wijziging per 14 november 2023
Voorheen bevatte de request body van het "delete" event alleen de comment id. Deze bevat nu de volledige comment op het moment van verwijdering.

Elke key is altijd aanwezig in de body. Wanneer de comment geen waarde heeft voor een veld bevat de body null (of false voor booleans en [] voor lijsten), zodat de vorm van een levering nooit verschilt van de ene comment naar de andere.

Het 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 /** The badges shown next to the commenter's name on this comment, as of when the event was created. Empty when there are none. **/
60 badges: CommentUserBadgeInfo[]
61}
62

Wanneer gebruikers getagd worden in een comment, wordt de informatie opgeslagen in een lijst genaamd mentions. Elk object in die lijst heeft de volgende structuur.

Het 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

Elke entry in de badges lijst heeft de volgende structuur. Zie de Badges gids voor de badge types.

Het Webhook Badge Object
Copy CopyRun External Link
1
2interface CommentUserBadgeInfo {
3 /** The id of the badge awarded to this user. **/
4 id: string
5 /** The badge type. **/
6 type: number
7 /** The badge description, shown when hovering over the badge. **/
8 description: string
9 /** The text shown on the badge. **/
10 displayLabel?: string | null
11 /** The image shown for the badge, if it uses one. **/
12 displaySrc?: string | null
13 backgroundColor?: string | null
14 borderColor?: string | null
15 textColor?: string | null
16 /** An extra CSS class applied to the badge. **/
17 cssClass?: string | null
18}
19

HTTP Methoden

Je kunt de HTTP-methode voor elk webhook event type configureren in het admin paneel:

  • Create Event: POST of PUT (standaard: PUT)
  • Update Event: POST of PUT (standaard: PUT)
  • Delete Event: DELETE, POST of PUT (standaard: DELETE)

Aangezien alle requests een ID bevatten, zijn Create- en Update-operaties standaard idempotent (PUT). Het herhalen van dezelfde Create- of Update-request zou aan jouw kant geen dubbele objecten mogen creëren.

Request Headers

Elke webhook request bevat de volgende headers:

HeaderBeschrijving
Content-Typeapplication/json
tokenJe API Secret
X-FastComments-TimestampUnix timestamp (seconden) waarop de request is ondertekend
X-FastComments-SignatureHMAC-SHA256 handtekening (sha256=<hex>)

Zie Beveiliging & API Tokens voor informatie over het verifiëren van de HMAC handtekening.

Beveiliging & API-tokens Internal Link

FastComments webhookverzoeken bevatten meerdere authenticatiemechanismen voor beveiliging.

Verzonden headers

HeaderBeschrijving
tokenUw API Secret (voor achterwaartse compatibiliteit)
X-FastComments-TimestampUnix-timestamp (seconden) wanneer het verzoek werd ondertekend
X-FastComments-SignatureHMAC-SHA256-handtekening van de payload

HMAC-handtekeningverificatie (aanbevolen)

We raden sterk aan de HMAC-handtekening te verifiëren om ervoor te zorgen dat webhook-payloads authentiek zijn en niet zijn gewijzigd.

Handtekeningformaat: sha256=<hex-encoded-signature>

Hoe de handtekening wordt berekend:

  1. Samenvoegen: timestamp + "." + JSON_payload_body
  2. Bereken HMAC-SHA256 met uw API Secret als sleutel
  3. Hex-encodeer het resultaat

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

    // Controleer of de timestamp recent is (binnen 5 minuten)
    const now = Math.floor(Date.now() / 1000);
    if (Math.abs(now - parseInt(timestamp, 10)) > 300) {
        return false;  // Ter voorkoming van replay-aanvallen
    }

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

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

Voorbeeldverificatie (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

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

    # Verifieer handtekening
    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}"

Voorbeeldverificatie (PHP)

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

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

    // Controleer of de timestamp recent is (binnen 5 minuten)
    $now = time();
    if (abs($now - intval($timestamp)) > 300) {
        return false;
    }

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

    return hash_equals($expectedSignature, $signature);
}

Verouderde authenticatie

De token-header die uw API Secret bevat, wordt nog steeds verzonden voor achterwaartse compatibiliteit. We raden echter aan over te stappen op HMAC-verificatie voor verbeterde beveiliging, omdat dit beschermt tegen replay-aanvallen.

Webhooks beheren via de API Internal Link

Webhooks kunnen ook worden beheerd via de REST‑API. Dit is hoe integraties zoals Zapier zich abonneren op commentaar‑gebeurtenissen zonder het dashboard aan te raken, en het volgt het REST‑Hooks‑patroon: abonneren, gebeurtenissen ontvangen, afmelden.

API‑abonnementen bestaan naast de webhooks die in het dashboard zijn geconfigureerd. Een commentaar‑gebeurtenis wordt geleverd aan elke webhook die overeenkomt met zijn domein, elk als een eigen levering, ongeacht hoe de webhook is aangemaakt.

Authenticatie

Elke aanvraag heeft uw API‑sleutel nodig in de x-api-key‑header (of de API_KEY‑queryparameter) en uw tenant‑ID in de tenantId‑queryparameter. Beide worden weergegeven op de API‑Secret‑pagina in het dashboard.

Abonneren

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"
}
VeldVerplichtBeschrijving
urlJaEen absolute http‑ of https‑URL.
eventJacomment-created, comment-updated of comment-deleted.
domainNeeEen domein uit uw accountconfiguratie. Standaard *, wat gebeurtenissen voor elk domein ontvangt.
methodNeePOST (standaard), PUT of DELETE.

De respons bevat het abonnement:

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

Het opnieuw abonneren van dezelfde URL op dezelfde gebeurtenis en hetzelfde domein retourneert het bestaande abonnement in plaats van een duplicaat te maken, zodat een client veilig kan herhalen. Elke tenant kan tot 50 API‑abonnementen hebben.

Lijst

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

Retourneert elke webhook voor de tenant, inclusief die beheerd in het dashboard ("source": "dashboard"). Filter met event, domain of source.

Afmelden

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

Het verwijderen van een abonnement verwijdert ook eventuele nog in de wachtrij staande gebeurtenissen. Alleen abonnementen die via de API zijn aangemaakt, kunnen op deze manier worden verwijderd; een dashboard‑webhook, of een id die niet bestaat in uw account, geeft 404 met code not-found. Dashboard‑webhooks worden bewerkt op de Webhooks‑pagina.

Payloads en ondertekening

Leveringen gebruiken dezelfde payload als dashboard‑webhooks (zie Data Structures) en worden ondertekend met hetzelfde HMAC‑schema (zie Security & API Tokens). API‑abonnementen ontvangen nooit de legacy token‑header, dus verifieer in plaats daarvan de X-FastComments-Signature‑header.

Voorbeeldpayloads

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

Retourneert de meest recente commentaren van het account in precies de vorm die een levering heeft, zodat een integratie echte voorbeeldgegevens kan tonen voordat de eerste gebeurtenis arriveert. event is optioneel en alleen gevalideerd, aangezien elke gebeurtenis hetzelfde commentaarobject levert. limit standaard op 3 en accepteert 1 tot 10. Kost 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
        }
    ]
}

Reageren met 410 Gone

Als het eindpunt van een API‑abonnement reageert met HTTP 410 Gone, behandelt FastComments dat als een afmelding: het abonnement wordt verwijderd samen met de in de wachtrij staande gebeurtenissen, en er worden geen verdere leveringen meer geprobeerd. Webhooks die in het dashboard zijn geconfigureerd, worden nooit automatisch verwijderd; voor hen is een 410 een gewone fout. Elke andere foutstatus wordt opnieuw geprobeerd en schakelt uiteindelijk de webhook uit, zoals beschreven in How it Works & Handling Retries.

Dashboard

API‑abonnementen verschijnen in de Webhooks‑lijst met de bron API, waar een beheerder ze kan bewerken, uitschakelen, opnieuw inschakelen of verwijderen.

Tot slot

Hiermee is onze Webhooks-documentatie afgerond.

We hopen dat je de FastComments Webhook-integratie gemakkelijk te begrijpen vindt en snel kunt instellen.

Als je denkt dat er hiaten in onze documentatie zitten, laat het ons hieronder weten.