Webhooks por etapa
Los webhooks de salida hacen que Asira te avise a ti: cada vez que un lead cambia de etapa en el pipeline (u ocurre otro evento suscrito), Asira hace un POST con un payload JSON a la URL que configures. Sirve para sincronizar tu CRM, disparar automatizaciones en n8n, o alertar a tu equipo.
Se crean y administran en Lógica adicional → Webhooks dentro del panel.
Eventos
| Evento | Clave | Estado |
|---|---|---|
| Cambio de estado | status_changed | Disponible |
| Seguimiento fallido | outreach_failed | Disponible |
| Reunión creada | meeting_created | Próximamente |
| Reunión actualizada | meeting_updated | Próximamente |
| Reunión cancelada | meeting_cancelled | Próximamente |
| Reunión completada | meeting_completed | Próximamente |
| Reunión no asistida | meeting_no_show | Próximamente |
| Reunión confirmada | meeting_confirmed | Próximamente |
status_changed: se dispara con cada cambio de etapa del pipeline (no con cualquier cambio interno de estado de la conversación).outreach_failed: un envío automático (recontacto o campaña) falló al ejecutarse. Útil para cablear alertas al equipo. Su payload es sin PII: solo ids, canal y motivo.
El payload
Todos los envíos comparten el mismo envelope en la raíz y un objeto data que cambia según el evento. Este es el payload completo de un status_changed:
{
"event_type": "status_changed",
"event_id": "evt_a1b2c3d4",
"webhook_id": "wh_1234",
"delivery_id": "dlv_5678",
"timestamp": "2026-07-09T12:00:00.000Z",
"client_id": "ws_abcd",
"test": false,
"data": {
"status_change": {
"from_status": {
"id": "stg_1111",
"key": "contactado",
"label": "Contactado",
"category": "active"
},
"to_status": {
"id": "stg_2222",
"key": "calificado",
"label": "Calificado",
"category": "active"
},
"reason": "El lead confirmó interés",
"trigger_type": "tool_call",
"actor_type": "agent",
"actor_id": "ag_1234",
"changed_at": "2026-07-09T12:00:00.000Z"
},
"lead": {
"id": "ld_1234",
"first_name": "Juan",
"last_name": "Pérez",
"email": "juan@correo.com",
"phone": "+56912345678",
"company": "Clínica Sonrisa",
"source": "meta_ads",
"external_id": "hl_9876",
"metadata": { "plan": "premium", "utm_source": "facebook" }
},
"workflow": { "id": "pl_1234", "name": "Pipeline de ventas" },
"workflow_run": {
"id": "conv_1234",
"started_at": "2026-07-09T11:30:00.000Z",
"current_channel": "whatsapp",
"total_status_changes": null,
"origin": "meta_ads"
}
}
}Envelope (raíz)
| Campo | Tipo | Descripción |
|---|---|---|
event_type | string | Clave del evento (status_changed, outreach_failed, …). |
event_id | string | null | ID del evento interno que originó la entrega. |
webhook_id | string | ID del webhook que disparó. |
delivery_id | string | ID único de esta entrega. Coincide con la fila registrada en el panel: úsalo para idempotencia (ver abajo). |
timestamp | string | Momento del envío (ISO 8601, UTC). |
client_id | string | Tu workspace_id. |
test | boolean | true si es una entrega de prueba disparada desde el panel. |
data | object | Cuerpo del evento (ver abajo). |
data.status_change (solo en status_changed)
from_status y to_status son objetos { id, key, label, category }. from_status es null en la primera entrada al funnel.
| Campo | Tipo | Descripción |
|---|---|---|
from_status | object | null | Etapa de origen. { id, key, label, category }. |
to_status | object | null | Etapa de destino. { id, key, label, category }. |
reason | string | null | Motivo del cambio. |
trigger_type | string | null | Qué lo disparó (tool_call, timeout, …). |
actor_type | string | null | agent, system, etc. |
actor_id | string | null | ID del actor (ej. el agente). |
changed_at | string | null | Momento del cambio (ISO 8601). |
data.lead, data.workflow, data.workflow_run
lead:{ id, first_name, last_name, email, phone, company, source, external_id, metadata }. Elnameúnico del contacto se parte enfirst_name/last_namepor el primer espacio.workflow: la pipeline del contacto:{ id, name }.workflow_run: la conversación:{ id, started_at, current_channel, total_status_changes, origin }.total_status_changesvienenull(no se calcula por entrega).
data.outreach_failed (solo en outreach_failed)
En este evento, status_change viene null y aparece outreach_failed:
{
"event_type": "outreach_failed",
"data": {
"status_change": null,
"outreach_failed": {
"contact_id": "c_1234",
"channel": "whatsapp",
"reason": "El envío del recontacto falló: la ventana de 24h está cerrada y no se pudo usar plantilla",
"reason_code": "channel_down",
"failed_stage": "pre_send",
"failed_at": "2026-07-14T16:30:00.000Z",
"source": "recontact"
},
"lead": { "id": "ld_1234", "...": "..." }
}
}reason es el motivo legible en español (para humanos). reason_code es la taxonomía machine-readable — úsala para rutear en n8n/Zapier sin parsear texto:
opt_out— el lead pidió no recibir mensajes (no reintentar).channel_down— el canal no está disponible ahora (ej. ventana de 24h de WhatsApp cerrada sin plantilla utilizable).missing_contact_data— no hay conversación ni dato de contacto por ese canal.template_vars_missing— la plantilla exige variables que el envío automático no puede rellenar (reconfigurar, no reintentar).template_not_approved— la plantilla existe pero Meta todavía no la aprobó (borrador, rechazada o pausada): hay que aprobarla, no reintentar.channel_unsupported— el canal no soporta ese tipo de envío (plantillas/adjuntos).delivery_failed— el proveedor rechazó el envío (número inválido, canal caído). Fallback genérico: el texto crudo del proveedor nunca se reenvía.
failed_stage distingue pre_send (un guard de Asira cortó el envío antes de llegar al proveedor) de send (el proveedor lo rechazó). A diferencia de otras plataformas, en Asira un fallo de entrega nunca consume intentos del recontacto — el lead no se enfría por fallas de canal — y además congela el contacto automático hasta revisión humana (bandeja de Tareas).
Filtros y alcance
Cada webhook se puede acotar para no recibir de más:
- Alcance por agente:
Todoslos agentes, o uno específico. - Filtros: condiciones que se evalúan en AND sobre los campos del evento (
to_status,from_status,workflow= la pipeline,trigger_type,meeting_type,host), con los mismos 13 operadores de los pre-procesadores (igual a, contiene, en lista, etc.). Ejemplo: disparar solo cuandoto_statusseacalificado.
Cuerpo personalizado (opcional)
Por defecto se envía el payload estándar de arriba. Puedes reemplazarlo por una plantilla propia con variables {{token}}: útil para encajar con el shape que espera tu sistema. Los valores se escapan JSON-safe.
Tokens disponibles (extracto):
| Token | Resuelve a |
|---|---|
{{event.event_type}} | Tipo de evento. |
{{event.delivery_id}} | ID de la entrega. |
{{event.client_id}} | Tu workspace_id. |
{{status_change.to_status.key}} | key de la etapa destino. |
{{status_change.to_status.label}} | Nombre de la etapa destino. |
{{status_change.to_status.id}} | ID de la etapa destino. |
{{status_change.from_status.key}} | key de la etapa origen. |
{{lead.first_name}}, {{lead.phone}}, {{lead.email}} | Datos del lead. |
{{lead.metadata}} | metadata del lead (serializado a JSON). |
{{workflow.name}} | Nombre de la pipeline. |
{{status_change.from_status}} / {{status_change.to_status}} siguen resolviendo a la key de la etapa (retrocompatibilidad). Los sub-paths .id / .key / .label / .category dan acceso al objeto completo.Autenticación del endpoint
La URL destino debe ser HTTPS. Además de eso, eliges cómo se autentica el POST contra tu servidor:
Sin header de autenticación. Solo recomendable si tu endpoint valida de otra forma (ej. una URL con un secreto).
Los secretos de autenticación (token bearer, valor de header, contraseña basic) se guardan cifrados y nunca viajan al historial de entregas ni a los logs.
Semántica de entrega
delivery_id (o event_id) del lado tuyo.- No es en tiempo real. Un proceso periódico (cada ~60 s) escanea los eventos y entrega los webhooks. Hay un pequeño lag deliberado (unos segundos) para no perder eventos recién ocurridos.
- Timeout. Cada entrega espera hasta 8 segundos una respuesta. Si tu endpoint no responde a tiempo, la entrega se marca como fallida.
- Éxito = HTTP 2xx. Cualquier otro código (o timeout / error de red) cuenta como entrega fallida.
- Sin reintentos con backoff. Una entrega fallida no se reintenta en una cola aparte: responde rápido con 2xx y procesa async de tu lado (un reenvío solo puede ocurrir por el reprocesamiento best-effort del barrido, de ahí la idempotencia).
- Registro. Cada intento queda registrado (estado, código HTTP, latencia, error) y visible en el panel para diagnóstico.
- Prueba. Desde el panel puedes disparar una entrega de prueba (
test: true) con datos de muestra, sin tocar datos reales.