Saltar al contenido
GUREN AI Docs

Plataforma y desarrolladores

Webhooks firmados

Cómo envía tu sistema leads y eventos a Guren AI y cómo recibe las acciones de Ana, con firma HMAC-SHA256, protección contra repetición e idempotencia.

Actualizado el

Si tu empresa tiene un sistema propio (un CRM a medida, un bot, un ERP), se conecta con Ana en ambas direcciones con dos contratos simétricos: el ingreso firmado, para que tu sistema nos envíe leads y eventos, y el webhook saliente, para que tu sistema reciba lo que Ana hace. Los dos usan el mismo esquema de firma.

#El esquema de firma

Cada solicitud lleva dos encabezados. La firma es un HMAC-SHA256, en hexadecimal, del timestamp, un punto y el cuerpo crudo de la solicitud, con un secreto que se comparte una sola vez y por un canal seguro durante la implementación.

EncabezadoContenido
X-Guren-TimestampSegundos desde 1970 (Unix) al momento de firmar.
X-Guren-Signaturesha256= seguido de hex(HMAC_SHA256(secreto, timestamp + "." + cuerpo)).
X-Guren-EventSolo en el webhook saliente: la acción que se informa.

#Ingreso firmado: de tu sistema a Ana

Envía uno o varios eventos en una misma solicitud a la URL de tu instancia, que te entregamos en la implementación.

http
/<instancia>">
POST https://guren.cl/api/v1/hooks/clinica-ejemplo/ingest
Content-Type: application/json
X-Guren-Timestamp: 1793019740
X-Guren-Signature: sha256=5f2c…e91a

{
  "events": [
    {
      "id": "crm-123",
      "type": "lead.flow_abandoned",
      "contact": { "phone": "+56912345678", "name": "Pedro Soto", "externalId": "123" },
      "text": "Abandonó la agenda en la pregunta 2",
      "data": { "consent": true, "producto": "Evaluación inicial" }
    }
  ]
}
CampoObligatorioNotas
idSíIdentificador estable del evento en tu sistema. Si reintentas con el mismo id, no se procesa dos veces.
typeSíPor defecto se aceptan lead.created, lead.updated, lead.flow_abandoned, meeting.booked y custom.
contactSíAl menos uno de phone (con código de país), email o externalId.
textNoContexto en lenguaje natural para Ana.
dataNoDatos adicionales. consent: true declara que la persona aceptó ser contactada.
respuesta 202
{ "ok": true, "accepted": 1, "rejected": [] }

Si algún evento no es válido, se informa en rejected con su posición y el motivo, y el resto se procesa. Si ninguno es válido, la respuesta es 400; si la firma no es válida, 401.

#Firmar desde tu sistema

firmar un envío (Node.js)
import { createHmac } from 'node:crypto';

export async function enviarAGuren(url, secreto, eventos) {
  const cuerpo = JSON.stringify({ events: eventos });
  const ts = Math.floor(Date.now() / 1000).toString();
  const firma = 'sha256=' + createHmac('sha256', secreto).update(`${ts}.${cuerpo}`).digest('hex');
  const res = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'X-Guren-Timestamp': ts, 'X-Guren-Signature': firma },
    body: cuerpo   // exactamente el mismo texto que se firmó
  });
  return res.json();
}
firmar un envío (Python)
import hashlib, hmac, json, time, urllib.request

def enviar_a_guren(url: str, secreto: str, eventos: list) -> dict:
    cuerpo = json.dumps({"events": eventos}, separators=(",", ":")).encode()
    ts = str(int(time.time()))
    firma = "sha256=" + hmac.new(secreto.encode(), f"{ts}.".encode() + cuerpo, hashlib.sha256).hexdigest()
    req = urllib.request.Request(url, data=cuerpo, method="POST", headers={
        "Content-Type": "application/json", "X-Guren-Timestamp": ts, "X-Guren-Signature": firma})
    with urllib.request.urlopen(req, timeout=10) as r:
        return json.load(r)

#Webhook saliente: de Ana a tu sistema

Cuando Ana crea o actualiza un contacto, registra una actividad (con la transcripción o el resumen), cambia un estado o avisa algo a tu equipo, lo publica como un POST firmado a la URL que nos indiques.

POST a tu URL
POST https://tu-sistema.cl/guren/webhook
Content-Type: application/json
X-Guren-Timestamp: 1793019801
X-Guren-Signature: sha256=a81d…07bc
X-Guren-Event: crm.log_activity

{
  "tenant": "clinica-ejemplo",
  "action": "crm.log_activity",
  "payload": { "contact": { "phone": "+56912345678", "name": "Pedro Soto" }, "channel": "whatsapp", "text": "Agendó evaluación para el jueves 10:30" },
  "sentAt": "2026-11-02T15:03:21.000Z"
}
X-Guren-EventCuándo llega
crm.upsert_contactUn contacto nuevo o con datos nuevos (nombre, correo, teléfono).
crm.log_activityUna conversación, una llamada con su resumen, una reunión agendada.
crm.set_statusCambio de estado: calificado, agendado, no interesado, baja.
notify.postUn aviso para tu equipo.

Si tu sistema responde {"ref": "<id en tu sistema>"}, Ana guarda esa referencia y la incluye en los siguientes eventos del mismo contacto, para que actualices el registro correcto sin buscarlo.

#Verificar en tu servidor

verificar la firma (Node.js + Express)
import express from 'express';
import { createHmac, timingSafeEqual } from 'node:crypto';

const app = express();
const SECRETO = process.env.GUREN_SIGNING_SECRET;

// Importante: leer el cuerpo CRUDO; la firma se calcula sobre el texto exacto
app.post('/guren/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const ts = req.get('X-Guren-Timestamp') || '';
  const recibida = req.get('X-Guren-Signature') || '';
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return res.sendStatus(401);

  const esperada = 'sha256=' + createHmac('sha256', SECRETO).update(`${ts}.${req.body}`).digest('hex');
  const a = Buffer.from(esperada), b = Buffer.from(recibida);
  if (a.length !== b.length || !timingSafeEqual(a, b)) return res.sendStatus(401);

  const evento = JSON.parse(req.body.toString('utf8'));
  res.status(200).json({ ref: guardar(evento) });   // responde rápido; procesa lo pesado después
});

#Buenas prácticas

  • Verifica con el cuerpo crudo. Si parseas y vuelves a serializar el JSON, la firma deja de coincidir.
  • Compara en tiempo constante (timingSafeEqual o hmac.compare_digest), nunca con ==.
  • Responde rápido. Devuelve 200 en cuanto validas la firma y procesa lo pesado en segundo plano.
  • Sé idempotente. Un mismo evento puede llegar más de una vez; usa el identificador del contacto y la acción para no duplicar registros.
  • Rota el secreto si sospechas que se expuso. Coordinamos la rotación contigo para no perder eventos.