Webflow Webhooks · AgriTech Koala
Automatizaciones / webhook.js · CULTIVA IA
Production-ready
Webhooks de Webflow: recepción y verificación
Implementación completa para AgriTech Koala — Node.js + Express · HMAC-SHA256 · ventana anti-replay
🌊
Webflow
POST
🔐
Verificar
HMAC
Anti-replay
5 min
🔀
Router
eventos
Acción
DB / CDN
200 OK
ACK
🔐
Firma HMAC-SHA256
Se calcula HMAC("ts:body") con el secreto de Webflow y se compara con x-webflow-signature.
⏱️
Ventana anti-replay
Se rechaza cualquier petición cuyo x-webflow-timestamp difiera más de 5 minutos del reloj del servidor.
⚖️
Tiempo constante
crypto.timingSafeEqual() previene ataques de temporización en la comparación de firmas.
verifyWebflowSignature() JavaScript
const crypto = require('crypto');

/**
 * Verifica la firma HMAC-SHA256 del webhook de Webflow.
 * @param rawBody    Cuerpo crudo (Buffer / string)
 * @param signature  Cabecera x-webflow-signature
 * @param timestamp  Cabecera x-webflow-timestamp  (epoch ms)
 * @param secret     WEBFLOW_WEBHOOK_SECRET
 */
function verifyWebflowSignature(rawBody, signature, timestamp, secret) {
  // 1. Ventana anti-replay de 5 minutos
  const now = Date.now();
  if (Math.abs(now - parseInt(timestamp, 10)) > 300_000) {
    return false;
  }

  // 2. HMAC-SHA256("timestamp:rawBody")
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}:${rawBody}`)
    .digest('hex');

  // 3. Comparación en tiempo constante (previene timing attacks)
  try {
    return crypto.timingSafeEqual(
      Buffer.from(signature),
      Buffer.from(expected)
    );
  } catch {
    return false; // Longitudes distintas → inválido
  }
}
POST /webhooks/webflow — Router de eventos Express 5
router.post(
  '/webhooks/webflow',
  express.raw({ type: 'application/json' }), // ← raw body obligatorio
  async (req, res) => {
    const signature = req.headers['x-webflow-signature'];
    const timestamp  = req.headers['x-webflow-timestamp'];

    if (!signature || !timestamp)
      return res.status(400).json({ error: 'Cabeceras ausentes' });

    const valid = verifyWebflowSignature(
      req.body.toString(), signature, timestamp,
      process.env.WEBFLOW_WEBHOOK_SECRET
    );
    if (!valid) return res.status(401).json({ error: 'Firma inválida' });

    const event = JSON.parse(req.body);

    switch (event.triggerType) {
      case 'form_submission':
        await handleFormSubmission(event.payload, req.db); // → INSERT lead
        break;
      case 'ecomm_new_order':
        await handleNewOrder(event.payload, req.db);       // → Activar licencia
        break;
      case 'site_publish':
        await handleSitePublish(event.payload);            // → Purgar caché CDN
        break;
    }

    res.status(200).json({ ok: true }); // Siempre 200 → evita reintentos
  }
);
Eventos configurados
triggerType Descripción Acción en AgriTech Koala Estado
form_submission Lead rellena formulario de demo INSERT en leads · notificar equipo ventas Activo
ecomm_new_order Compra de licencia / curso INSERT en licenses · email credenciales Activo
site_publish Publicación del sitio Webflow Purge Cloudflare CDN purge_everything Activo
collection_item_created Nuevo artículo CMS Sync Algolia · reindex Pendiente
collection_item_changed Artículo CMS actualizado Update Algolia index Pendiente
ecomm_order_changed Estado pedido cambia Actualizar estado licencia Pendiente
Prueba manual con cURL · form_submission bash
# 1. Generar firma (Node REPL)
const ts   = Date.now().toString();
const body = JSON.stringify({
  triggerType: 'form_submission',
  payload: { data: { name: 'Ana García', email: 'ana@demo.com', company: 'Koala Demo' } }
});
const sig  = crypto.createHmac('sha256', 'whsec_k0AlA_s3cR3T_4gR1T3cH')
                    .update(`${ts}:${body}`).digest('hex');

# 2. Enviar petición
curl -X POST http://localhost:3000/webhooks/webflow \
     -H "Content-Type: application/json" \
     -H "x-webflow-signature: <sig>" \
     -H "x-webflow-timestamp: <ts>" \
     -d '<body>'

# Respuesta esperada: {"ok":true}