Webhook de ingesta de leads
El webhook de ingesta es la vía para meter leads a Asira desde afuera: tu CRM, una landing, HighLevel, Meta Ads, n8n. Recibe uno o varios leads y, por cada uno, hace el upsert del contacto, lo rutea con tus pre-procesadores hacia el agente correspondiente y lo mete en el pipeline de ese agente.
sygk_live_…) y su respuesta usa un shape propio ({ error: "…" }), no el envelope { error: { code, message } } de la API v1.Endpoint
POST /api/webhooks/leads?wsid=<workspaceId>&token=<token>El token también se puede mandar en el header X-API-Key en vez de la query:
POST /api/webhooks/leads?wsid=<workspaceId>
X-API-Key: <token>Autenticación
wsid(query, obligatorio): el ID de tu workspace. Es la fuente de verdad del tenant, el workspace se toma siempre de acá, nunca del body.token(query o headerX-API-Key, obligatorio): el token de ingesta del workspace. Se compara en tiempo constante contra el token configurado; un token que no coincide devuelve401.
El wsid y el token de ingesta se generan y ven en Configuración → Leads del panel. Si el workspace no tiene la ingesta habilitada (sin token configurado), todos los requests devuelven 401.
Cuerpo del request
El body acepta cuatro formas (todas equivalentes), para encajar con lo que mande cada origen:
[
{ "name": "Ana", "phone": "+56911111111" },
{ "name": "Beto", "phone": "+56922222222" }
]Hasta 1000 leads por request.
Campos de un lead
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
phone | string | Sí | Teléfono del lead. Es su identidad en el workspace (UNIQUE por workspace + teléfono) y el canal por el que lo contacta el agente. |
name | string | No | Nombre del lead. |
email | string | No | Email del lead. |
source | string | No | Origen del lead (ej. landing_web, meta_ads). Si no viene, se usa metadata.source o lead_api. |
tags | string[] | No | Etiquetas a asignar (se recortan espacios y se fusionan con las existentes). |
metadata | object | No | Datos custom clave-valor. |
name / phone / email / source / tags / metadata se pliega automáticamente dentro de metadata. Enviar { "phone": "…", "edad": 35 } equivale a { "phone": "…", "metadata": { "edad": 35 } }.Algunas claves internas del motor (como last_recontact_at o stage_changed_at) no se pueden setear desde la ingesta: se descartan de metadata para que el emisor no controle los relojes de recontacto/timeout del pipeline.
Ejemplo
curl -X POST "https://TU-DOMINIO/api/webhooks/leads?wsid=TU_WORKSPACE_ID" \
-H "Content-Type: application/json" \
-H "X-API-Key: TU_TOKEN_DE_INGESTA" \
-d '{
"leads": [
{
"name": "Ana Pérez",
"phone": "+56911111111",
"email": "ana@correo.com",
"source": "landing_web",
"tags": ["vip"],
"campaña": "black-friday"
}
]
}'Qué pasa con cada lead
Upsert del contacto
Se busca por
workspace_id+phone. Si existe, se actualiza (mergeandometadataytags, sin pisar lo que ya había); si no, se crea.Ruteo por pre-procesadores
Se corren tus reglas de pre-procesador (Processor) sobre los datos del lead para resolver a qué agente va.
Entrada al pipeline
Si el lead no estaba ya asignado a un agente, se lo mete en la etapa inicial del agente resuelto y arranca la conversación. Un lead que ya tenía agente no se secuestra: se respeta su conversación en curso (
source: "already_assigned").
Respuesta
Semántica bulk tolerante a fallos: un lead inválido no tumba el batch entero. La respuesta reporta el resultado por lead.
{
"ok": true,
"received": 1,
"processed": 1,
"routed": 1,
"failed": 0,
"results": [
{
"index": 0,
"ok": true,
"contact_id": "c_1234",
"agent_id": "ag_5678",
"rule_matched": "Landing web → Ventas",
"source": "processor"
}
]
}| Campo | Descripción |
|---|---|
received | Cantidad de leads recibidos en el request. |
processed | Cuántos se procesaron con éxito. |
routed | Cuántos quedaron asignados a un agente. |
failed | Cuántos fallaron (received menos processed). |
results[] | Detalle por lead: index, ok, contact_id, agent_id, rule_matched, source y error (si falló). |
Errores
| HTTP | Body | Cuándo |
|---|---|---|
401 | { "error": "Unauthorized" } | Falta wsid o token; el workspace no tiene ingesta habilitada; el token no coincide. |
400 | { "error": "Invalid JSON body" } | El body no es JSON válido. |
400 | { "error": "El body debe traer un lead, un array de leads o { leads: [...] }" } | Forma de body no reconocida. |
400 | { "error": "No hay leads en el body" } | El array vino vacío. |
400 | { "error": "Máximo 1000 leads por request…" } | Se superó el tope de 1000. |