Kora CRMDOCS

API, Webhooks Y MCP

Conecta Kora CRM con tu web, tu ERP, Zapier/Make/n8n o asistentes de IA como Claude. Todo queda limitado al negocio dueño de la clave.

Inicio Rápido

Prueba ahora con la clave pública de demostración (solo lectura, datos del negocio demo):

curl https://tu-dominio.com/api/v1/contacts?limit=5 \
  -H "Authorization: Bearer kora_demo_solo_lectura_2026"

Para tu negocio: Panel → Configuración → Integraciones → Crear Clave. La clave se muestra una sola vez; guárdala como secreto.

Autenticación

Envía la clave en cada solicitud con Authorization: Bearer <clave>. Las claves pueden ser de solo lectura o de lectura y escritura, y se revocan al instante desde el panel. Guardamos solo un hash: si la pierdes, crea otra.

Errores Y Paginación

Las listas aceptan ?page=1&limit=25 (máx. 100) y responden con data, page, limit, total, has_more. Los errores siempre tienen la misma forma:

HTTP/1.1 403
{ "error": { "code": "read_only_key", "message": "Esta clave es de solo lectura. Crea una clave con permiso de escritura." } }

Códigos: 400 JSON o id inválido · 401 clave ausente/revocada · 403 sin permiso de escritura · 404 no existe en tu espacio · 409 duplicado o ventana de 24 h cerrada · 422 validación · 500 error interno.

Endpoints

Base: https://tu-dominio.com/api/v1

GET/contactsLista contactos. Filtros: q, stage_id
POST/contactsCrea un contacto (phone obligatorio)
GET/contacts/:idVer un contacto con sus etiquetas
PATCH/contacts/:idEdita nombre, teléfono, etiquetas, campos, agente
GET/conversationsConversaciones recientes. Filtros: unread=true, assigned_to
GET/conversations/:idContacto + mensajes
PATCH/conversations/:idAsignar agente: { assigned_to }
GET/messages?contact_id=Mensajes de un contacto
GET/messages/:idVer un mensaje
POST/messagesEnvía un WhatsApp (texto o plantilla)
GET/tagsLista etiquetas
POST/tagsCrea etiqueta { name, color }
PATCH/tags/:idEdita etiqueta
GET/pipelinesEmbudos con etapas
GET/pipelines/:idVer un embudo
GET/opportunitiesOportunidades. Filtros: pipeline_id, stage_id
POST/opportunitiesCrea { contact_id, stage_id, value }
GET/opportunities/:idVer oportunidad
PATCH/opportunities/:idMover etapa / cambiar valor / motivo de pérdida
GET/agentsMiembros del equipo
GET/agents/:idVer agente
PATCH/agents/:idCambiar rol { role: admin | agent }
GET/summary?days=7Resumen de actividad
POST/webhooks/inRecibe eventos externos
# Crear un contacto con etiquetas
curl -X POST https://tu-dominio.com/api/v1/contacts \
  -H "Authorization: Bearer $KORA_KEY" -H "Content-Type: application/json" \
  -d '{"name":"María Rojas","phone":"+506 8888 7777","tag_ids":["<uuid-etiqueta>"]}'

# Mover una oportunidad de etapa
curl -X PATCH https://tu-dominio.com/api/v1/opportunities/<contact_id> \
  -H "Authorization: Bearer $KORA_KEY" -H "Content-Type: application/json" \
  -d '{"stage_id":"<uuid-etapa>","value":350000}'

Los agentes se invitan desde el panel por seguridad; la API permite listarlos, verlos y cambiar su rol.

Enviar Mensajes De WhatsApp

Texto libre solo dentro de la ventana de 24 h desde el último mensaje del cliente; fuera de ella usa una plantilla aprobada. Sin número de Meta conectado el envío se simula (modo demostración, "demo": true).

curl -X POST https://tu-dominio.com/api/v1/messages \
  -H "Authorization: Bearer $KORA_KEY" -H "Content-Type: application/json" \
  -d '{"phone":"50688887777","text":"¡Hola! Tu cita quedó confirmada."}'

# Con plantilla aprobada
-d '{"contact_id":"<uuid>","template_id":"<uuid>","variables":["María","10:00 a. m."]}'

# Respuesta
{ "message_id": "…", "contact_id": "…", "status": "sent", "demo": false }

Webhooks Salientes

Configúralos en Integraciones. Eventos: contact.created, message.received, opportunity.moved, conversation.assigned. Enviamos un POST JSON; responde 2xx en menos de 10 s. Si falla, reintentamos a los 1, 5, 30, 120 y 360 minutos. Cada entrega queda en el registro del panel.

POST https://tu-servidor.com/kora
Content-Type: application/json
X-Kora-Event: message.received
X-Kora-Delivery: 7b1c…
X-Signature: t=1791456000,v1=5f2a9c…

{
  "id": "7b1c…",
  "event": "message.received",
  "created_at": "2026-10-05T14:20:00Z",
  "workspace_id": "…",
  "data": { "id": "…", "contact_id": "…", "type": "text", "body": "Hola, ¿tienen cita mañana?" }
}
// opportunity.moved
"data": { "contact_id": "…", "pipeline_id": "…", "from_stage_id": "…", "to_stage_id": "…", "value": 350000 }
// conversation.assigned
"data": { "contact_id": "…", "assigned_to": "…", "previous": null }
// contact.created
"data": { "id": "…", "name": "María Rojas", "phone": "50688887777" }

Verificar La Firma

La cabecera X-Signature contiene t (segundos Unix) y v1 = HMAC-SHA256 en hex de `${t}.${cuerpo}` con el secreto del webhook. Usa el cuerpo crudo, compara en tiempo constante y rechaza firmas con más de 5 minutos.

// Node.js / Express
import crypto from "node:crypto";
app.post("/kora", express.raw({ type: "application/json" }), (req, res) => {
  const { t, v1 } = Object.fromEntries(req.get("X-Signature").split(",").map(p => p.split("=")));
  const expected = crypto.createHmac("sha256", process.env.KORA_WEBHOOK_SECRET)
    .update(`${t}.${req.body}`).digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
  if (!fresh || !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1))) return res.sendStatus(401);
  const event = JSON.parse(req.body);
  res.sendStatus(200);
});
# Python / Flask
import hmac, hashlib, time
sig = dict(p.split("=") for p in request.headers["X-Signature"].split(","))
body = request.get_data(as_text=True)
expected = hmac.new(SECRET.encode(), f"{sig['t']}.{body}".encode(), hashlib.sha256).hexdigest()
ok = abs(time.time() - int(sig["t"])) < 300 and hmac.compare_digest(expected, sig["v1"])

Webhooks Entrantes

Envía eventos de tus sistemas a https://tu-dominio.com/api/v1/webhooks/in con una clave de API como secreto (Authorization: Bearer o X-Webhook-Secret). Todo evento queda registrado en Integraciones. form.submitted crea el contacto si no existe (requiere clave con escritura).

curl -X POST https://tu-dominio.com/api/v1/webhooks/in \
  -H "X-Webhook-Secret: $KORA_KEY" -H "Content-Type: application/json" \
  -d '{"event":"form.submitted","data":{"name":"Luis Mora","phone":"+506 7000 1122","origen":"landing"}}'

# Respuesta 202
{ "id": "…", "event": "form.submitted", "result": "contacto creado …", "created_at": "…" }

Otros eventos (por ejemplo whatsapp.message o whatsapp.status reenviados desde otra plataforma) se guardan tal cual para auditoría. Los mensajes reales de tu número oficial llegan por el webhook de Meta configurado en Configuración → WhatsApp.

Servidor MCP (Claude Y Otros Clientes)

URL: https://tu-dominio.com/mcp (Streamable HTTP, JSON-RPC). Usa la misma clave de API. Herramientas: buscar_contactos, listar_conversaciones, enviar_mensaje, crear_oportunidad, mover_etapa, asignar_agente, resumen_de_actividad, listar_embudos, listar_agentes.

Claude Desktop → Configuración → Desarrollador → Editar configuración, y pega:

{
  "mcpServers": {
    "kora-crm": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://tu-dominio.com/mcp",
               "--header", "Authorization: Bearer ${KORA_API_KEY}"],
      "env": { "KORA_API_KEY": "kora_demo_solo_lectura_2026" }
    }
  }
}

Claude Code:

claude mcp add --transport http kora-crm https://tu-dominio.com/mcp \
  --header "Authorization: Bearer TU_CLAVE"

Prueba manual:

curl -X POST https://tu-dominio.com/mcp -H "Authorization: Bearer kora_demo_solo_lectura_2026" \
  -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"resumen_de_actividad","arguments":{"dias":30}}}'

Con la clave demo las herramientas de escritura responden «solo lectura». Crea una clave con escritura para que Claude envíe mensajes o mueva oportunidades.