Asira

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

EventoClaveEstado
Cambio de estadostatus_changedDisponible
Seguimiento fallidooutreach_failedDisponible
Reunión creadameeting_createdPróximamente
Reunión actualizadameeting_updatedPróximamente
Reunión canceladameeting_cancelledPróximamente
Reunión completadameeting_completedPróximamente
Reunión no asistidameeting_no_showPróximamente
Reunión confirmadameeting_confirmedPró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)

CampoTipoDescripción
event_typestringClave del evento (status_changed, outreach_failed, …).
event_idstring | nullID del evento interno que originó la entrega.
webhook_idstringID del webhook que disparó.
delivery_idstringID único de esta entrega. Coincide con la fila registrada en el panel: úsalo para idempotencia (ver abajo).
timestampstringMomento del envío (ISO 8601, UTC).
client_idstringTu workspace_id.
testbooleantrue si es una entrega de prueba disparada desde el panel.
dataobjectCuerpo 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.

CampoTipoDescripción
from_statusobject | nullEtapa de origen. { id, key, label, category }.
to_statusobject | nullEtapa de destino. { id, key, label, category }.
reasonstring | nullMotivo del cambio.
trigger_typestring | nullQué lo disparó (tool_call, timeout, …).
actor_typestring | nullagent, system, etc.
actor_idstring | nullID del actor (ej. el agente).
changed_atstring | nullMomento del cambio (ISO 8601).

data.lead, data.workflow, data.workflow_run

  • lead: { id, first_name, last_name, email, phone, company, source, external_id, metadata }. El name único del contacto se parte en first_name / last_name por 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_changes viene null (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: Todos los 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 cuando to_status sea calificado.

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):

TokenResuelve 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.
Los tokens planos legacy {{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

Haz tu endpoint idempotenteLa entrega es best-effort, al menos una vez (no exactamente una vez): ante un reintento del barrido o un solapamiento, un mismo evento puede llegar más de una vez. Dedupica por 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.
SeguridadLos envíos pasan por una capa anti-SSRF: se exige HTTPS y una IP pública en el destino (se revalida en cada redirect). No apuntes el webhook a direcciones internas: serán bloqueadas.