Empezar

La API de LeadFlow te da acceso a los leads, tareas y equipo de tu cuenta, y los webhooks te avisan en cuanto pasa algo. Todo va sobre HTTPS y JSON. Dirección base:

https://api-leads.vred.es/v1

Primera llamada: crea una clave en Configuración › API e integraciones y pide la raíz.

curl https://api-leads.vred.es/v1 \
  -H "Authorization: Bearer lf_live_TU_CLAVE"
{ "name": "LeadFlow API", "version": "v1", "tenant": { "id": 28, "name": "Tu empresa" }, "scope": "read", "endpoints": [ "GET /v1/leads", "..." ] }

Autenticación

Cada petición lleva la clave en la cabecera Authorization: Bearer lf_live_… (también vale x-api-key). La clave identifica a tu cuenta: nunca hay que enviar un identificador de empresa.

La clave se muestra una sola vez al crearla. Si se pierde o se filtra, revócala en el panel y crea otra; el corte es inmediato.

Límites y errores

120 peticiones por minuto por clave. Cada respuesta lleva X-RateLimit-Limit y X-RateLimit-Remaining; al superarlo recibes 429 con Retry-After en segundos.

Los errores devuelven siempre la misma forma:

{ "error": { "code": "validation_error", "message": "limit: Number must be less than or equal to 100" } }
HTTPcodeCuándo
400validation_errorFalta un campo o tiene un formato incorrecto
401missing_api_key / invalid_api_keySin clave, clave incorrecta o revocada
403read_only_key / account_pausedLa clave no puede escribir, o la cuenta está en pausa
404not_foundEl recurso no existe o no es de tu cuenta
429rate_limitedDemasiadas peticiones

Leads

GET/leads

Lista paginada. Filtros: status (uno o varios separados por coma), assigned_to (id de usuario), tag (nombre o slug), q (busca en nombre, teléfono y email), created_since y updated_since (ISO 8601), sort (updated, created, last_interaction), limit (máx. 100) y offset.

curl "https://api-leads.vred.es/v1/leads?status=cualificado,cita_agendada&updated_since=2026-09-01T00:00:00Z&limit=50" \
  -H "Authorization: Bearer lf_live_TU_CLAVE"
{ "data": [ {
      "id": 14072,
      "name": "Calería Maychi, S.L.",
      "phone": "34698911195",
      "email": "info@caleriamaychi.es",
      "status": "en_cualificacion",
      "source": "prospector",
      "channel": "whatsapp_waha",
      "tags": [{ "id": 42, "name": "BdB", "slug": "bdb" }],
      "assignedTo": { "id": 6, "name": "Enrique", "email": "enrique@empresa.com" },
      "conversationOwner": "ia",
      "outreachPaused": false,
      "deal": { "value": 2500, "probability": 25, "expectedCloseAt": "2026-10-15", "wonAt": null, "lostAt": null, "lostReason": null },
      "appointment": { "at": null, "outcome": null },
      "data": { "provincia": "Cádiz", "empresa": "Calería Maychi" },
      "lastInteractionAt": "2026-09-04T11:34:22.000Z",
      "createdAt": "2026-09-03T10:55:29.000Z",
      "updatedAt": "2026-09-04T11:53:21.000Z"
    } ], "total": 1, "limit": 50, "offset": 0 }

Estados posibles: nuevo, en_cualificacion, cualificado, cita_agendada, ganado, perdido. conversationOwner dice si la conversación la lleva la IA (ia) o una persona (humano). data son los datos que la IA o el equipo han ido recogiendo.

GET/leads/{id}

La ficha completa: lo anterior más notes, tasks, messages (los últimos 100, en orden) y events (el historial).

POST/leads

Crea un lead o, si ya existe uno con ese teléfono o email, lo actualiza con lo que envíes: las etiquetas se añaden, los datos se fusionan. Responde 201 al crear y 200 al actualizar, y la cabecera X-Lead-Created lo confirma. Hace falta phone o email.

curl -X POST https://api-leads.vred.es/v1/leads \
  -H "Authorization: Bearer lf_live_TU_CLAVE" -H "Content-Type: application/json" \
  -d '{
    "name": "Ferretería García",
    "phone": "600 123 456",
    "email": "info@ferreteriagarcia.es",
    "source": "web",
    "tags": ["Web", "Cadena88"],
    "data": { "provincia": "Murcia", "interes": "Control de stock" },
    "deal": { "value": 3000, "probability": 20 },
    "note": "Pidió información desde el formulario de la web",
    "outreach": true
  }'
outreach: por defecto un lead creado por API no recibe mensajes automáticos. Envía "outreach": true si quieres que el seguimiento con IA le escriba. Las respuestas que llegan por WhatsApp o email siempre se atienden.

Campos: name, phone, email, status, source, tags (se crean si no existen), data (texto por clave), deal (value en euros, probability 0-100, expectedCloseAt AAAA-MM-DD), assignedTo (id de usuario o null), outreach, note.

PATCH/leads/{id}

Modifica solo lo que envíes. Además de los campos anteriores: lostReason, addTags, removeTags (o tags para reemplazarlas todas). Poner null en una clave de data la borra.

curl -X PATCH https://api-leads.vred.es/v1/leads/14072 \
  -H "Authorization: Bearer lf_live_TU_CLAVE" -H "Content-Type: application/json" \
  -d '{ "status": "ganado", "deal": { "value": 4200 }, "assignedTo": 6 }'

Ganar o perder por API hace lo mismo que desde el panel: cierra las tareas de la cita, apunta la fecha y lo atribuye a la campaña que lo originó.

Notas

POST/leads/{id}/notes

curl -X POST https://api-leads.vred.es/v1/leads/14072/notes \
  -H "Authorization: Bearer lf_live_TU_CLAVE" -H "Content-Type: application/json" \
  -d '{ "body": "Presupuesto enviado por email, pendiente de respuesta", "pinned": true }'

La nota aparece en la ficha firmada como API · nombre de la clave.

Tareas

GET/tasks

Por defecto las pendientes, ordenadas por vencimiento. Filtros: status (pending, done, all), assigned_to, lead_id, due_before.

POST/leads/{id}/tasks

curl -X POST https://api-leads.vred.es/v1/leads/14072/tasks \
  -H "Authorization: Bearer lf_live_TU_CLAVE" -H "Content-Type: application/json" \
  -d '{ "title": "Llamar para cerrar la demo", "kind": "llamar", "dueAt": "2026-09-08T09:00:00+02:00", "assignedTo": 6 }'

kind: llamar, email, whatsapp, reunion, otro. Sin assignedTo, la tarea va al asignado del lead.

PATCH/tasks/{id}

curl -X PATCH https://api-leads.vred.es/v1/tasks/9 \
  -H "Authorization: Bearer lf_live_TU_CLAVE" -H "Content-Type: application/json" \
  -d '{ "done": true }'

Mensajes

POST/leads/{id}/messages

Envía un mensaje al lead por whatsapp o email (con subject). Sin channel, usa el canal por el que llegó el lead. Funciona como un envío manual desde el panel: la IA se aparta de esa conversación y el mensaje queda en el hilo.

curl -X POST https://api-leads.vred.es/v1/leads/14072/messages \
  -H "Authorization: Bearer lf_live_TU_CLAVE" -H "Content-Type: application/json" \
  -d '{ "channel": "whatsapp", "body": "Hola, te confirmo la demo del lunes a las 10:00." }'

Responde 201 si el canal aceptó el envío y 502 con el motivo si no.

Equipo y etiquetas

GET/team devuelve los usuarios (id, nombre, email, rol, activo) para usar en assignedTo. GET/tags devuelve las etiquetas (id, nombre, slug) para filtrar.

Webhooks

Un webhook es una URL tuya a la que LeadFlow envía un POST cuando ocurre un evento. Se crean en Configuración › Webhooks salientes, eligiendo los eventos. Al crearlo recibes un secreto con el que se firma cada envío.

Cada aviso es un JSON con esta forma:

{
  "id": "evt_dzILQu_aa6gpWSmN",
  "event": "lead.status_changed",
  "createdAt": "2026-09-04T14:32:36.000Z",
  "tenant": { "id": 28, "name": "Tu empresa" },
  "data": {
    "lead": { ...el lead, igual que en GET /leads/{id}... },
    "change": { "from": "cualificado", "to": "ganado" },
    "actor": "Enrique"
  }
}

Cabeceras: X-LeadFlow-Event, X-LeadFlow-Event-Id (el mismo para todos los webhooks que reciben ese evento; sirve para no procesarlo dos veces), X-LeadFlow-Delivery, X-LeadFlow-Timestamp y X-LeadFlow-Signature.

Tu servidor debe responder con un código 2xx en menos de 10 segundos; haz el trabajo pesado después de responder. Si falla, reintentamos a 1 min, 10 min, 1 h, 6 h y 16 h. Tras tres días fallando, el webhook se desactiva y el email de alertas de la cuenta recibe un aviso; se puede reactivar desde el panel. El registro de entregas, con reintento manual, está en el panel.

Eventos

EventoCuándo se envía
lead.createdSe crea un lead: por WhatsApp o email entrante, por el prospector, por una landing, a mano en el panel o por esta API. Las importaciones masivas no avisan.
lead.status_changedCambia el estado del lead (nuevo, en_cualificacion, cualificado, cita_agendada, ganado, perdido). `data.change` trae `from` y `to`.
lead.wonEl lead pasa a ganado. Se envía además de lead.status_changed.
lead.lostEl lead pasa a perdido. `data.change.lostReason` si se indicó.
lead.assignedSe asigna el lead a alguien del equipo (a mano o automáticamente).
appointment.bookedSe agenda una cita. `data.change.appointmentAt` en ISO 8601.
appointment.outcomeSe marca el resultado de la cita: `realizada` o `no_asistio`.
message.receivedLlega un mensaje del lead por cualquier canal. `data.message` trae id, canal, texto y fecha.
ai.handoffLa IA se aparta de la conversación y hace falta una persona. `data.change` trae el motivo.
task.createdSe crea una tarea sobre el lead.
task.doneSe marca una tarea como hecha.

Verificar la firma

X-LeadFlow-Signature es sha256= seguido del HMAC SHA-256, en hexadecimal, calculado con tu secreto sobre la cadena timestamp + "." + cuerpo, donde el cuerpo son los bytes exactos recibidos. Rechaza avisos con más de 5 minutos de antigüedad.

Node.js (Express)

import express from "express";
import { createHmac, timingSafeEqual } from "node:crypto";

const app = express();
app.post("/leadflow", express.raw({ type: "application/json" }), (req, res) => {
  const ts = req.header("x-leadflow-timestamp");
  const sig = req.header("x-leadflow-signature") ?? "";
  const expected = "sha256=" + createHmac("sha256", process.env.LEADFLOW_SECRET).update(ts + "." + req.body).digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300;
  if (!fresh || sig.length !== expected.length || !timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) return res.sendStatus(401);
  const event = JSON.parse(req.body);
  res.sendStatus(200);            // responde primero
  procesa(event);                 // trabaja después
});

PHP

$body = file_get_contents('php://input');
$ts = $_SERVER['HTTP_X_LEADFLOW_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_LEADFLOW_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $ts . '.' . $body, LEADFLOW_SECRET);
if (abs(time() - (int)$ts) > 300 || !hash_equals($expected, $sig)) { http_response_code(401); exit; }
$event = json_decode($body, true);
http_response_code(200);

Python

import hmac, hashlib, time
def verify(secret: str, ts: str, body: bytes, signature: str) -> bool:
    expected = "sha256=" + hmac.new(secret.encode(), f"{ts}.".encode() + body, hashlib.sha256).hexdigest()
    return abs(time.time() - int(ts)) < 300 and hmac.compare_digest(expected, signature)

Receta: Zapier

Lead ganado → fila en Google Sheets

  1. En Zapier, crea un Zap con el disparador Webhooks by Zapier › Catch Hook. Copia la URL que te da.
  2. En LeadFlow, Configuración › Webhooks salientes › Nuevo webhook: pega la URL y marca Lead ganado. Pulsa Probar para que Zapier reciba una muestra.
  3. Añade la acción Google Sheets › Create Spreadsheet Row y mapea data__lead__name, data__lead__phone, data__lead__deal__value y data__lead__assignedTo__name.

Nuevo contacto en otra herramienta → lead en LeadFlow

Acción Webhooks by Zapier › Custom Request: método POST, URL https://api-leads.vred.es/v1/leads, cabecera Authorization: Bearer lf_live_…, Content-Type: application/json, y en Data el JSON con name, phone, email, source y tags.

Receta: Make

  1. Módulo Webhooks › Custom webhook: crea uno y copia la URL.
  2. Crea el webhook en LeadFlow con esa URL y los eventos que quieras; pulsa Probar para que Make determine la estructura.
  3. Para escribir en LeadFlow usa HTTP › Make a request con la cabecera de autorización y el JSON del recurso. Para consultar, GET https://api-leads.vred.es/v1/leads?updated_since=… con un módulo programado.

Receta: formulario de tu web

Desde tu servidor (nunca desde el navegador, la clave quedaría expuesta), al recibir el formulario:

await fetch("https://api-leads.vred.es/v1/leads", {
  method: "POST",
  headers: { "Authorization": "Bearer " + process.env.LEADFLOW_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({
    name: form.nombre, phone: form.telefono, email: form.email,
    source: "web", tags: ["Web"],
    data: { interes: form.interes, mensaje: form.mensaje },
    outreach: true,   // la IA le escribe para cualificarlo
  }),
});

Si la misma persona vuelve a rellenar el formulario, se actualiza su ficha en vez de duplicarla.

Especificación completa: openapi.json (importable en Postman, Insomnia o Make).