FastComments.com


Con FastComments es posible invocar un endpoint de la API cada vez que se añade, actualiza o elimina un comentario de nuestro sistema.

Lo logramos con webhooks asíncronos a través de HTTP/HTTPS.


Qué son los webhooks Internal Link


Un Webhook es un mecanismo, o una integración, entre dos sistemas donde el "productor" (FastComments) desencadena un evento que el "consumidor" (Usted) consume mediante una llamada a la API.


Eventos y recursos compatibles Internal Link


FastComments admite webhooks solo para el recurso Comment.

Admitimos webhooks para la creación, eliminación y actualización de comentarios.

Cada uno de ellos se considera un evento separado en nuestro sistema y, por lo tanto, tiene distintas semánticas y estructuras para los eventos de webhook.


Pruebas Internal Link

En la administración de Webhooks hay botones Send Test Payload para cada tipo de evento (Create, Update, Delete). Los eventos Create y Update envían un objeto WebhookComment de prueba, mientras que al probar Delete se enviará un cuerpo de solicitud de prueba con solo un ID.

Verificación de Payloads

Al probar la integración de tu webhook, verifica que las solicitudes entrantes incluyan los siguientes encabezados:

  1. token - Tu secreto de la API
  2. X-FastComments-Timestamp - Marca de tiempo Unix (segundos)
  3. X-FastComments-Signature - Firma HMAC-SHA256

Usa la verificación de la firma HMAC para asegurarte de que los payloads sean auténticos.

Herramientas de prueba

Puedes usar herramientas como webhook.site o ngrok para inspeccionar los payloads entrantes del webhook durante el desarrollo.

Tipos de eventos

  • Create Event: Se desencadena cuando se crea un nuevo comentario. Método predeterminado: PUT
  • Update Event: Se desencadena cuando se edita un comentario. Método predeterminado: PUT
  • Delete Event: Se desencadena cuando se elimina un comentario. Método predeterminado: DELETE

Cada evento incluye los datos completos del comentario en el cuerpo de la solicitud (consulta Estructuras de datos para el formato del payload).

Estructuras de datos Internal Link

La única estructura enviada vía webhooks es el objeto WebhookComment, descrito en TypeScript a continuación.

Estructura del objeto WebhookComment

Estructura del evento "Create"

El cuerpo de la petición del evento "create" es un objeto WebhookComment.

Estructura del evento "Update"

El cuerpo de la petición del evento "update" es un objeto WebhookComment.

Estructura del evento "Delete"

El cuerpo de la petición del evento "delete" es un objeto WebhookComment.

Cambio a partir del 14 de noviembre de 2023
Anteriormente el cuerpo de la petición del evento "delete" solo contenía el id del comentario. Ahora contiene el comentario completo en el momento de la eliminación.
El objeto WebhookComment
Copy CopyRun External Link
1
2interface WebhookComment {
3 /** El id del comentario. **/
4 id: string
5 /** El id o URL que identifica el hilo de comentarios. Normalizado. **/
6 urlId: string
7 /** La URL que apunta al lugar donde se dejó el comentario. **/
8 url?: string
9 /** El id del usuario que dejó el comentario. Si es SSO, con el id del tenant como prefijo. **/
10 userId?: string
11 /** El correo electrónico del usuario que dejó el comentario. **/
12 commenterEmail?: string
13 /** El nombre del usuario que se muestra en el widget de comentarios. Con SSO, puede ser displayName. **/
14 commenterName: string
15 /** Texto sin procesar del comentario. **/
16 comment: string
17 /** Texto del comentario tras el parseo. **/
18 commentHTML: string
19 /** Id externo del comentario. **/
20 externalId?: string
21 /** El id del comentario padre. **/
22 parentId?: string | null
23 /** La fecha UTC cuando se dejó el comentario. **/
24 date: UTC_ISO_DateString
25 /** Karma combinado (positivos - negativos) de los votos. **/
26 votes: number
27 votesUp: number
28 votesDown: number
29 /** True si el usuario estaba logueado cuando comentó, o si verificó el comentario, o si verificó su sesión cuando se dejó el comentario. **/
30 verified: boolean
31 /** Fecha cuando se verificó el comentario. **/
32 verifiedDate?: number
33 /** Si un moderador marcó el comentario como revisado. **/
34 reviewed: boolean
35 /** La ubicación, o codificación base64, del avatar. Será base64 sólo si ese fue el valor pasado con SSO. **/
36 avatarSrc?: string
37 /** ¿El comentario fue marcado manual o automáticamente como spam? **/
38 isSpam: boolean
39 /** ¿El comentario fue marcado automáticamente como spam? **/
40 aiDeterminedSpam: boolean
41 /** ¿Hay imágenes en el comentario? **/
42 hasImages: boolean
43 /** El número de página en el que está el comentario para la dirección de ordenación "Most Relevant". **/
44 pageNumber: number
45 /** El número de página en el que está el comentario para la dirección de ordenación "Oldest First". **/
46 pageNumberOF: number
47 /** El número de página en el que está el comentario para la dirección de ordenación "Newest First". **/
48 pageNumberNF: number
49 /** ¿El comentario fue aprobado automáticamente o manualmente? **/
50 approved: boolean
51 /** El código de localización (formato: en_us) del usuario cuando se escribió el comentario. **/
52 locale: string
53 /** Las @menciones escritas en el comentario que fueron parseadas correctamente. **/
54 mentions?: CommentUserMention[]
55 /** El dominio del que proviene el comentario. **/
56 domain?: string
57 /** La lista opcional de ids de grupo de moderación asociada con este comentario. **/
58 moderationGroupIds?: string[]|null
59}
60

Cuando los usuarios son etiquetados en un comentario, la información se almacena en una lista llamada mentions. Cada objeto en esa lista tiene la siguiente estructura.

El objeto de menciones del Webhook
Copy CopyRun External Link
1
2interface CommentUserMention {
3 /** El id del usuario. Para usuarios SSO, tendrá el id de tu tenant como prefijo. **/
4 id: string
5 /** El texto final de la etiqueta @mention, incluyendo el símbolo @. **/
6 tag: string
7 /** El texto original de la etiqueta @mention, incluyendo el símbolo @. **/
8 rawTag: string
9 /** Qué tipo de usuario fue etiquetado. user = cuenta de FastComments.com. sso = SSOUser. **/
10 type: 'user'|'sso'
11 /** Si el usuario se da de baja de las notificaciones, esto seguirá establecido en true. **/
12 sent: boolean
13}
14

Métodos HTTP

Puedes configurar el método HTTP para cada tipo de evento de webhook en el panel de administración:

  • Evento Create: POST o PUT (predeterminado: PUT)
  • Evento Update: POST o PUT (predeterminado: PUT)
  • Evento Delete: DELETE, POST o PUT (predeterminado: DELETE)

Dado que todas las solicitudes contienen un ID, las operaciones Create y Update son idempotentes por defecto (PUT). Repetir la misma petición Create o Update no debería crear objetos duplicados en tu lado.

Encabezados de la petición

Cada petición de webhook incluye los siguientes encabezados:

Encabezado Descripción
Content-Type application/json
token Tu secreto de API
X-FastComments-Timestamp Marca de tiempo Unix (segundos) cuando la petición fue firmada
X-FastComments-Signature Firma HMAC-SHA256 (sha256=<hex>)

Consulta Seguridad y tokens de API para obtener información sobre cómo verificar la firma HMAC.


Seguridad y tokens de API Internal Link


Las solicitudes webhook de FastComments incluyen múltiples mecanismos de autenticación por motivos de seguridad.

Encabezados enviados

Header Description
token Tu API Secret (para compatibilidad con versiones anteriores)
X-FastComments-Timestamp Marca de tiempo Unix (segundos) cuando se firmó la solicitud
X-FastComments-Signature Firma HMAC-SHA256 de la carga útil

Verificación de firma HMAC (Recomendado)

Recomendamos encarecidamente verificar la firma HMAC para garantizar que las cargas útiles de los webhooks sean auténticas y no hayan sido manipuladas.

Formato de la firma: sha256=<hex-encoded-signature>

Cómo se calcula la firma:

  1. Concatenar: timestamp + "." + JSON_payload_body
  2. Calcular HMAC-SHA256 usando tu API Secret como clave
  3. Codificar el resultado en hexadecimal

Ejemplo de verificación (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;
    }

    // Verificar que la marca de tiempo sea reciente (dentro de 5 minutos)
    const now = Math.floor(Date.now() / 1000);
    if (Math.abs(now - parseInt(timestamp, 10)) > 300) {
        return false;  // Prevención de ataques de repetición
    }

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

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

Ejemplo de verificación (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

    # Verificar que la marca de tiempo sea reciente
    now = int(time.time())
    if abs(now - int(timestamp)) > 300:
        return False

    # Verificar firma
    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}"

Ejemplo de verificación (PHP)

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

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

    // Verificar que la marca de tiempo sea reciente (dentro de 5 minutos)
    $now = time();
    if (abs($now - intval($timestamp)) > 300) {
        return false;
    }

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

    return hash_equals($expectedSignature, $signature);
}

Autenticación heredada

El encabezado token que contiene tu API Secret todavía se envía por compatibilidad con versiones anteriores. Sin embargo, recomendamos migrar a la verificación HMAC para mejorar la seguridad, ya que protege contra ataques de repetición.


En conclusión

Esto concluye nuestra documentación de Webhooks.

Esperamos que la integración de Webhooks de FastComments le resulte fácil de entender y rápida de configurar.

Si considera que ha identificado alguna laguna en nuestra documentación, háganoslo saber a continuación.