Datos y eventos

Eventos (webhooks firmados)

YAVE le avisa a tu servidor cuando pasa algo en el CRM: entra un lead, cambia de etapa, se le asigna un asesor, se agenda una cita. Cada aviso es un POST firmado, con reintentos si tu servidor no responde. Así tu ERP, tu BI o tu sistema de cobranza se entera al instante sin consultar la API cada minuto.

¿Y el nodo Llamar Webhook?

Las automatizaciones siguen pudiendo llamar a cualquier URL con el cuerpo que armes. Los eventos de esta página son distintos: un formato fijo y documentado, firmados, con reintentos e historial. Úsalos cuando otro sistema dependa de ellos.

Configurar#

  1. Un administrador entra al CRM → Configuración → Integraciones, pestaña de productividad, sección Webhooks salientes.
  2. Agregar endpoint: la URL de tu servidor (tiene que ser https y pública) y los eventos que quieres recibir.
  3. Copia el secreto de firma (whsec_…). Se muestra una sola vez; si lo pierdes, genera uno nuevo desde el mismo lugar.
  4. Pulsa Probar: YAVE envía un evento ping y te muestra qué respondió tu servidor.

Una organización puede tener hasta 10 endpoints, cada uno con sus propios eventos y su propio secreto.

EventoCuándodata
lead.createdEntra un lead nuevo por cualquier canal (formulario, Meta, WhatsApp, API, manual).{ lead }
lead.stage_changedUn lead cambia de etapa, lo mueva un asesor, la IA, una automatización o la API.{ lead, from, to }
lead.assignedSe asigna o reasigna el asesor de un lead.{ lead, assignedTo }
appointment.createdSe agenda una cita o visita.{ appointment }
task.createdSe crea una tarea.{ task }

lead y appointment tienen la misma forma que en la API de consulta. Un lead que ya existía y vuelve a escribir (reingreso) no genera lead.created.

Formato

POST a tu URL
{
  "id": "evt_3f9a1c0b7d2e4a6f8b1c2d3e",
  "type": "lead.stage_changed",
  "createdAt": "2026-09-29T20:14:05.118Z",
  "organizationId": "cm1…",
  "data": {
    "lead": {
      "id": "cm9k2…",
      "name": "Laura Gómez",
      "email": "[email protected]",
      "phone": "+573001234567",
      "pipeline": { "id": "default", "name": "Ventas" },
      "stage": { "id": "s-visita", "name": "Visita agendada" },
      "assignedTo": { "id": "cm2u…", "name": "Carlos Ruiz", "email": "[email protected]" },
      "tags": ["Torre 2"],
      "createdAt": "2026-09-28T15:04:12.511Z",
      "updatedAt": "2026-09-29T20:14:05.020Z"
    },
    "from": { "id": "CONTACTED", "name": "Contactado" },
    "to": { "id": "s-visita", "name": "Visita agendada" }
  }
}
EncabezadoQué es
X-Yave-SignatureFirma: t=<timestamp>,v1=<hmac>. Ver abajo.
X-Yave-EventEl tipo de evento, igual que type.
X-Yave-Event-IdEl id del evento. Es el mismo en todos los reintentos: úsalo para no procesar dos veces.
X-Yave-Delivery-IdEl id de este envío (para soporte).
X-Yave-AttemptNúmero de intento, desde 1.

Verificar la firma#

Cualquiera que conozca tu URL puede mandarle un POST. Verifica cada evento antes de confiar en él: calcula un HMAC-SHA256 con tu secreto sobre <t>.<cuerpo crudo> y compáralo con v1. Rechaza los que tengan un t de hace más de 5 minutos, para que nadie pueda repetir un evento viejo.

Node.js (Express)
import crypto from "node:crypto";
import express from "express";

const app = express();
const SECRET = process.env.YAVE_WEBHOOK_SECRET; // whsec_…

// Usa el cuerpo CRUDO: si lo parseas y lo vuelves a serializar, la firma no coincide.
app.post("/webhooks/yave", express.raw({ type: "application/json" }), (req, res) => {
  const header = req.get("X-Yave-Signature") ?? "";
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return res.sendStatus(400);

  const expected = crypto
    .createHmac("sha256", SECRET)
    .update(`${t}.${req.body}`)
    .digest("hex");
  const ok =
    parts.v1?.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(parts.v1, "hex"), Buffer.from(expected, "hex"));
  if (!ok) return res.sendStatus(400);

  const event = JSON.parse(req.body);
  // Responde rápido y procesa aparte; ignora ids que ya procesaste.
  queue.push(event);
  res.sendStatus(200);
});
PHP
<?php
$secret  = getenv('YAVE_WEBHOOK_SECRET'); // whsec_…
$payload = file_get_contents('php://input'); // cuerpo crudo
$header  = $_SERVER['HTTP_X_YAVE_SIGNATURE'] ?? '';

parse_str(str_replace(',', '&', $header), $parts);
$t = (int) ($parts['t'] ?? 0);
if (!$t || abs(time() - $t) > 300) { http_response_code(400); exit; }

$expected = hash_hmac('sha256', $t . '.' . $payload, $secret);
if (!hash_equals($expected, $parts['v1'] ?? '')) { http_response_code(400); exit; }

$event = json_decode($payload, true);
// … guarda $event['id'] para no procesarlo dos veces
http_response_code(200);
Python (Flask)
import hashlib, hmac, json, os, time
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = os.environ["YAVE_WEBHOOK_SECRET"].encode()  # whsec_…

@app.post("/webhooks/yave")
def yave():
    payload = request.get_data()  # cuerpo crudo (bytes)
    parts = dict(p.split("=", 1) for p in request.headers.get("X-Yave-Signature", "").split(","))
    t = int(parts.get("t", 0))
    if not t or abs(time.time() - t) > 300:
        abort(400)
    expected = hmac.new(SECRET, f"{t}.".encode() + payload, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, parts.get("v1", "")):
        abort(400)
    event = json.loads(payload)
    # … encola event y deduplica por event["id"]
    return "", 200

Respuestas y reintentos#

  • Responde 2xx en menos de 10 segundos. Cualquier otra respuesta, un timeout o un error de conexión cuenta como fallo. Si tienes que hacer algo lento, encólalo y responde de una vez.
  • Si falla, YAVE reintenta a los 1 min, 5 min, 30 min, 2 h, 6 h, 12 h y 24 h: 8 intentos en total, durante casi dos días.
  • No se siguen redirecciones: registra la URL final.
  • Entrega al menos una vez: un evento puede llegar repetido (por ejemplo, si tu servidor lo procesó pero la respuesta no llegó). Deduplica por X-Yave-Event-Id.
  • El orden no está garantizado: un reintento puede llegar después de un evento más nuevo. Usa createdAt del evento o lead.updatedAt para quedarte con el estado más reciente, o consulta el lead en la API de consulta.
  • Si 20 eventos seguidos agotan sus reintentos, el endpoint se desactiva solo y el CRM muestra el motivo. Arregla tu servidor y vuelve a activarlo; los eventos fallidos se pueden reenviar desde el historial.

Historial y reenvíos#

En el CRM, cada endpoint tiene un Historial con los últimos envíos: evento, estado, código HTTP, intentos y el error, si hubo. Un envío fallido se puede Reenviar después de corregir tu servidor. El historial se guarda 30 días.

Seguridad#

  • Solo https. YAVE no envía a direcciones internas (localhost, redes privadas, metadatos de nube), ni siquiera si un nombre público resuelve a una.
  • Guarda el secreto como una contraseña. Si se filtra, pulsa Nuevo secreto: el anterior deja de servir de inmediato.
  • Los eventos llevan datos de contacto de personas. Trátalos con el mismo cuidado que tu base de clientes.