FastComments.com

Webhooks

Com o FastComments é possível invocar um endpoint de API sempre que um comentário for adicionado, atualizado ou removido do nosso sistema.

Fazemos isso com webhooks assíncronos sobre HTTP/HTTPS.

O que são Webhooks Internal Link

Um Webhook é um mecanismo, ou uma integração, entre dois sistemas em que o "produtor" (FastComments) dispara um evento que o "consumidor" (você) consome por meio de uma chamada de API.

Eventos e Recursos Suportados Internal Link

FastComments suporta webhooks apenas para o recurso Comentário.

Nós suportamos webhooks para criação, remoção e atualização de comentários.

Cada um desses é considerado um evento separado em nosso sistema e, como tal, possui semânticas e estruturas diferentes para os eventos de webhook.

Qualquer número de endpoints pode se inscrever no mesmo evento, a partir do painel ou através da API (veja Gerenciando Webhooks via API). Cada webhook é entregue de forma independente.

Teste Internal Link

As páginas de novo webhook e de edição de webhook têm um botão Send Test Payload que envia uma requisição para a URL atualmente no formulário, tenha ela sido salva ou não. Os eventos Create e Update enviam um objeto WebhookComment fictício, enquanto o teste de Delete enviará um corpo de requisição fictício contendo apenas um ID.

Verificando Payloads

Ao testar sua integração de webhook, verifique se as requisições recebidas incluem os seguintes cabeçalhos:

  1. X-FastComments-Timestamp - Timestamp Unix (segundos)
  2. X-FastComments-Signature - Assinatura HMAC-SHA256

Webhooks criados antes da introdução do esquema de assinatura também recebem um cabeçalho token contendo seu API Secret. Webhooks novos não recebem.

Use a verificação de assinatura HMAC para garantir que os payloads são autênticos.

Ferramentas de Teste

Você pode usar ferramentas como webhook.site ou ngrok para inspecionar os payloads de webhook recebidos durante o desenvolvimento.

Tipos de Evento

  • Evento Create: Disparado quando um novo comentário é criado.
  • Evento Update: Disparado quando um comentário é editado.
  • Evento Delete: Disparado quando um comentário é excluído.

Cada webhook está vinculado a um evento e a um método HTTP (POST, PUT ou DELETE). Cada evento inclui os dados completos do comentário no corpo da requisição (veja Estruturas de Dados para o formato do payload).

Estruturas de Dados Internal Link

A única estrutura enviada via webhooks é o objeto WebhookComment, descrita em TypeScript abaixo.

Estrutura do Objeto WebhookComment

Estrutura do Evento "Create"

O corpo da requisição do evento "create" é um objeto WebhookComment.

Estrutura do Evento "Update"

O corpo da requisição do evento "update" é um objeto WebhookComment.

Estrutura do Evento "Delete"

O corpo da requisição do evento "delete" é um objeto WebhookComment.

Alteração a partir de 14 de nov de 2023
Anteriormente, o corpo da requisição do evento "delete" continha apenas o id do comentário. Agora contém o comentário completo no momento da exclusão.

Cada chave está sempre presente no corpo. Quando o comentário não tem valor para um campo, o corpo contém null
(ou false para booleanos e [] para listas), de modo que a estrutura de uma entrega nunca varia de um comentário para outro.

Objeto 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 /** 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

Quando usuários são marcados em um comentário, a informação é armazenada em uma lista chamada mentions. Cada objeto nessa lista tem a seguinte estrutura.

Objeto de Menções do 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

Cada entrada na lista badges tem a seguinte estrutura. Veja o Guia de Badges para os tipos de badge.

Objeto de Badge do Webhook
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

Métodos HTTP

Você pode configurar o método HTTP para cada tipo de evento webhook no painel de administração:

  • Evento de Criação: POST ou PUT (padrão: PUT)
  • Evento de Atualização: POST ou PUT (padrão: PUT)
  • Evento de Exclusão: DELETE, POST ou PUT (padrão: DELETE)

Como todas as requisições contêm um ID, as operações de Criação e Atualização são idempotentes por padrão (PUT). Repetir a mesma requisição de Criação ou Atualização não deve criar objetos duplicados no seu lado.

Cabeçalhos da Requisição

Cada requisição webhook inclui os seguintes cabeçalhos:

HeaderDescription
Content-Typeapplication/json
tokenSeu Segredo de API
X-FastComments-TimestampTimestamp Unix (segundos) quando a requisição foi assinada
X-FastComments-Signatureassinatura HMAC-SHA256 (sha256=<hex>)

Veja Segurança & Tokens de API para informações sobre como verificar a assinatura HMAC.

Segurança e Tokens de API Internal Link

As solicitações de webhook do FastComments incluem múltiplos mecanismos de autenticação para segurança.

Cabeçalhos Enviados

CabeçalhoDescrição
tokenSeu API Secret (para compatibilidade retroativa)
X-FastComments-TimestampTimestamp Unix (segundos) de quando a requisição foi assinada
X-FastComments-SignatureAssinatura HMAC-SHA256 do payload

Verificação de Assinatura HMAC (Recomendado)

Recomendamos fortemente verificar a assinatura HMAC para garantir que os payloads do webhook sejam autênticos e não tenham sido adulterados.

Formato da assinatura: sha256=<hex-encoded-signature>

Como a assinatura é calculada:

  1. Concatenar: timestamp + "." + JSON_payload_body
  2. Calcular HMAC-SHA256 usando seu API Secret como chave
  3. Codificar o resultado em hexadecimal

Exemplo de Verificação (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}`;
}

Exemplo de Verificação (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}"

Exemplo de Verificação (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);
}

Autenticação Legada

O cabeçalho token contendo seu API Secret ainda é enviado para compatibilidade retroativa. No entanto, recomendamos migrar para a verificação HMAC para melhorar a segurança, pois ela protege contra ataques de replay.

Gerenciando Webhooks pela API Internal Link

Webhooks também podem ser gerenciados através da API REST. É assim que integrações como o Zapier se inscrevem em eventos de comentário sem tocar no painel, e segue o padrão REST Hooks: inscrever, receber eventos, cancelar inscrição.

As inscrições de API convivem ao lado dos webhooks configurados no painel. Um evento de comentário é entregue a cada webhook que corresponde ao seu domínio, cada um como sua própria entrega, independentemente de como o webhook foi criado.

Autenticação

Cada requisição precisa da sua API Key no cabeçalho x-api-key (ou no parâmetro de consulta API_KEY) e do seu ID de locatário no parâmetro de consulta tenantId. Ambos são exibidos na página API Secret no painel.

Inscrever-se

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"
}
CampoObrigatórioDescrição
urlSimUm URL http ou https absoluto.
eventSimcomment-created, comment-updated ou comment-deleted.
domainNãoUm domínio da configuração da sua conta. O padrão é *, que recebe eventos para todos os domínios.
methodNãoPOST (padrão), PUT ou DELETE.

A resposta contém a inscrição:

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

Inscrever a mesma URL no mesmo evento e domínio novamente retorna a inscrição existente em vez de criar um duplicado, permitindo que o cliente tente novamente com segurança. Cada locatário pode ter até 50 inscrições de API.

Listar

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

Retorna todos os webhooks do locatário, incluindo os gerenciados no painel (\"source\": \"dashboard\"). Filtre com event, domain ou source.

Cancelar inscrição

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

Excluir uma inscrição também descarta quaisquer eventos ainda enfileirados para ela. Apenas inscrições criadas através da API podem ser excluídas desta forma; um webhook do painel, ou um id que não exista na sua conta, responde 404 com o código not-found. Webhooks do painel são editados na página Webhooks.

Payloads e assinatura

Entregas usam o mesmo payload dos webhooks do painel (veja Estruturas de Dados) e são assinadas com o mesmo esquema HMAC (veja Segurança & Tokens de API). Inscrições de API nunca recebem o cabeçalho legado token, portanto verifique o cabeçalho X-FastComments-Signature.

Payloads de exemplo

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

Retorna os comentários mais recentes da conta exatamente no formato que uma entrega transporta, permitindo que uma integração mostre dados de exemplo reais antes que o primeiro evento chegue. event é opcional e apenas validado, já que todo evento entrega o mesmo objeto de comentário. limit tem padrão 3 e aceita de 1 a 10. Custa 2 créditos de 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
        }
    ]
}

Respondendo com 410 Gone

Se o endpoint de uma inscrição de API responder com HTTP 410 Gone, o FastComments trata isso como um cancelamento de inscrição: a inscrição é excluída junto com seus eventos enfileirados, e nenhuma entrega adicional é tentada. Webhooks configurados no painel nunca são excluídos automaticamente; para eles um 410 é uma falha comum. Qualquer outro status de falha é refeito e eventualmente desabilita o webhook, conforme descrito em Como funciona & Tratamento de Repetições.

Painel

Inscrições de API aparecem na lista de Webhooks com a origem API, onde um administrador pode editar, desativar, reativar ou excluí‑las.

Conclusão

Isto conclui nossa documentação sobre Webhooks.

Esperamos que você ache a integração de Webhooks do FastComments fácil de entender e rápida de configurar.

Se você sentir que identificou alguma lacuna em nossa documentação, informe-nos abaixo.