Kelova Docs

Webhooks salientes

Recibir en tu sistema los eventos que ocurren dentro de Kelova

Kelova puede avisarle a tu sistema cuando pasa algo: una reserva confirmada por el agente, un lead capturado en una conversación. Registras una URL HTTPS y Kelova le hace un POST firmado con el evento.

Es la vía para conectar Kelova con n8n, Make, Zapier o tu propio backend sin consultar la API cada minuto.

Los webhooks salientes requieren un plan de pago (Piloto en adelante) y rol admin. No confundir con Webhooks, que son los entrantes de WhatsApp y Telegram hacia Kelova.

Eventos disponibles

EventoCuándo se dispara
booking.createdEl agente confirmó una cita con un cliente
lead.createdEl agente capturó un lead en una conversación
test.pingSolo a pedido, con el endpoint de prueba. No se suscribe

Un endpoint recibe únicamente los eventos a los que está suscrito — salvo test.ping, que llega siempre que lo dispares a mano.

Crear un endpoint

El secreto de firma se genera acá y se muestra una sola vez. Kelova lo guarda cifrado y no existe forma de volver a verlo: si lo pierdes, rotas.

curl -X POST https://api.kelova.app/integrations/webhooks \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://n8n.tuempresa.com/webhook/kelova",
    "events": ["booking.created", "lead.created"]
  }'

Respuesta (201):

{
  "endpoint": {
    "id": "9c1e2b4a...",
    "url": "https://n8n.tuempresa.com/webhook/kelova",
    "events": ["booking.created", "lead.created"],
    "status": "active",
    "consecutive_failures": 0,
    "last_triggered_at": null,
    "created_at": "2026-09-05T14:00:00Z",
    "updated_at": "2026-09-05T14:00:00Z"
  },
  "secret": "whsec_3f9a1c7e5b2d8046a1c3e5f7b9d0..."
}

Copia secret de inmediato. Ninguna otra respuesta lo devuelve — ni en claro ni cifrado.

Restricciones de la URL:

  • Tiene que ser https://. HTTP plano se rechaza con INVALID_WEBHOOK_URL.
  • No puede apuntar a una red interna, a localhost ni a rangos especiales. Kelova resuelve el nombre y valida cada IP antes de conectar, y vuelve a validar en cada redirección (máximo 3).
  • Hasta 5 endpoints por tenant.

Qué recibe tu servidor

POST /webhook/kelova HTTP/1.1
Content-Type: application/json
User-Agent: Kelova-Webhooks/1
X-Kelova-Event: booking.created
X-Kelova-Delivery: 7d3f9a1c-5b2d-4046-a1c3-e5f7b9d02468
X-Kelova-Signature: t=1788901678,v1=5f3c...
{
  "id": "7d3f9a1c-5b2d-4046-a1c3-e5f7b9d02468",
  "type": "booking.created",
  "tenant_id": "t9f8e7...",
  "created_at": "2026-09-05T14:32:10Z",
  "data": {
    "booking_id": "b1a2c3...",
    "agent_id": "a4d5e6...",
    "conversation_id": "c7f8a9...",
    "starts_at": "2026-09-08T15:00:00Z",
    "duration_min": 30,
    "timezone": "America/El_Salvador",
    "contact_name": "María Rivera",
    "contact_email": "maria@ejemplo.com",
    "contact_phone": "+50370001234"
  }
}

starts_at viaja siempre en UTC; timezone es la zona de la cuenta, la que el cliente vio al agendar.

El data de lead.created:

{
  "session_id": "s1b2c3...",
  "agent_id": "a4d5e6...",
  "channel": "whatsapp",
  "intent": "cotización",
  "captured_at": "2026-09-05T14:32:10Z",
  "contact_name": "María Rivera",
  "contact_email": "maria@ejemplo.com",
  "contact_phone": "+50370001234"
}

Los campos de contacto se omiten cuando el cliente no los dio. Responde cualquier 2xx para aceptar la entrega; Kelova espera hasta 10 segundos.

Verificar la firma

X-Kelova-Signature tiene el formato t=<unix>,v1=<hex>, donde v1 es HMAC-SHA256 del secreto sobre la cadena <t>.<cuerpo exacto>. El timestamp entra en el material firmado, así que no se puede reusar una firma vieja con un t nuevo.

const crypto = require("crypto");

function verificar(secreto, header, cuerpoCrudo) {
  const partes = Object.fromEntries(
    header.split(",").map((p) => p.split("=", 2))
  );
  const t = Number(partes.t);

  // Rechaza lo que esté a más de 5 minutos de tu reloj.
  if (Math.abs(Date.now() / 1000 - t) > 300) return false;

  const esperado = crypto
    .createHmac("sha256", secreto)
    .update(`${t}.${cuerpoCrudo}`)
    .digest("hex");

  // Puede venir más de un v1= durante una rotación: basta con que uno coincida.
  return header
    .split(",")
    .filter((p) => p.startsWith("v1="))
    .some((p) =>
      crypto.timingSafeEqual(
        Buffer.from(p.slice(3), "hex"),
        Buffer.from(esperado, "hex")
      )
    );
}

Firma sobre el cuerpo crudo, byte por byte. Si tu framework parsea el JSON y lo vuelve a serializar, la firma no va a coincidir.

Reintentos y entregas duplicadas

Si tu servidor no responde 2xx, Kelova reintenta cuatro veces: 1 minuto, 5 minutos, 25 minutos y 2 horas después del intento en línea. Agotada la escalera, la entrega queda en failed.

Cada reintento vuelve a firmar (el t cambia) pero conserva el mismo X-Kelova-Delivery. Deduplica por ese header, no por el contenido.

Una URL que apunta a una red interna no se reintenta: no va a dejar de hacerlo por esperar.

Tras 10 fallos consecutivos el endpoint se desactiva solo y recibes una notificación en el dashboard. Se reactiva con un PATCH que lo ponga en active, lo que además pone el contador de fallos en cero.

Rotar el secreto

curl -X POST https://api.kelova.app/integrations/webhooks/{id}/rotate-secret \
  -H "Authorization: Bearer <token>"
{
  "secret": "whsec_8b0d2f4a6c8e...",
  "grace_period_ends_in": "1h0m0s"
}

Durante una hora Kelova firma con el secreto nuevo y con el anterior: el header trae dos v1= y tu receptor sigue validando con el que todavía tenga configurado. Pasada la gracia, solo vale el nuevo.

Probar un endpoint

Dispara un test.ping contra un endpoint concreto, esté suscrito a lo que esté:

curl -X POST https://api.kelova.app/integrations/webhooks/{id}/test \
  -H "Authorization: Bearer <token>"

Responde 202 — la entrega es asíncrona. El resultado se consulta en el registro de entregas. Máximo una prueba por minuto por endpoint; un endpoint desactivado responde 409.

Registro de entregas

curl "https://api.kelova.app/integrations/webhooks/deliveries?status=failed&limit=20" \
  -H "Authorization: Bearer <token>"
{
  "deliveries": [
    {
      "id": "7d3f9a1c...",
      "endpoint_id": "9c1e2b4a...",
      "event_type": "booking.created",
      "status": "failed",
      "attempts": 5,
      "next_retry_at": null,
      "response_status": 500,
      "response_body": "internal error",
      "duration_ms": 412,
      "delivered_at": null,
      "created_at": "2026-09-05T14:32:10Z",
      "payload": { "booking_id": "b1a2c3..." }
    }
  ]
}

Filtros: status (pending, success, failed), endpoint_id y limit (default 50, tope 200). Se guardan los primeros 1 KB de la respuesta de tu servidor, útil para depurar. Las entregas se retienen 90 días.

Listar, editar y borrar

# Listar — nunca devuelve el secreto
curl https://api.kelova.app/integrations/webhooks \
  -H "Authorization: Bearer <token>"

# Cambiar URL, eventos o estado
curl -X PATCH https://api.kelova.app/integrations/webhooks/{id} \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"status": "disabled"}'

# Borrar el endpoint y sus entregas
curl -X DELETE https://api.kelova.app/integrations/webhooks/{id} \
  -H "Authorization: Bearer <token>"

Desactivar y editar la URL funcionan siempre, incluso si tu plan bajó — para que puedas frenar tus webhooks en cualquier momento. Reactivar sí vuelve a pedir plan de pago.

Errores

CódigoHTTPCuándo ocurre
WEBHOOKS_REQUIRE_PAID_PLAN403Tu plan no incluye webhooks salientes
WEBHOOK_LIMIT_REACHED409Ya tienes 5 endpoints
WEBHOOK_NOT_FOUND404El endpoint no existe o no es de tu tenant
INVALID_WEBHOOK_URL400No es HTTPS, está malformada o apunta a una red no permitida
INVALID_WEBHOOK_EVENTS400Lista vacía, evento repetido o fuera del catálogo

Todavía no

Configurar webhooks desde el dashboard (por ahora es solo API), los eventos handoff.requested y order.confirmed, y el reenvío manual de una entrega fallida.

On this page