Skip to content

LogirAI Webhooks — API Reference (v1)

Esta guía describe el contrato actual de los webhooks que LogirAI envía a tu sistema. Si un comportamiento no aparece acá, asumí que no está soportado en v1.


1. Resumen

Cuando ocurren eventos en la operación logística, LogirAI envía notificaciones HTTP POST a la URL que nos hayas registrado.

  • Fire-and-forget: 1 evento → 1 POST. Sin reintentos.
  • Autenticación: header Authorization: Bearer <secret>.
  • Sin filtros por evento en v1: tu URL recibe todos los eventos disponibles para tu company.
  • Una sola URL activa por cliente en v1. Si necesitás múltiples destinos, contactá a operaciones.

2. Onboarding

El alta y baja de webhooks se gestiona manualmente con el equipo de LogirAI (no hay self-service en v1).

Para activar webhooks, enviá:

  1. URL de destino — debe ser https://... (HTTP plano se rechaza).
  2. Contacto técnico — para coordinar el secret.

LogirAI te devolverá un secret que vas a recibir en cada request en el header Authorization: Bearer <secret>. Guardalo de forma segura.

Para rotar el secret o desactivar la suscripción: contactá a operaciones. La rotación es inmediata (no hay grace period: el siguiente evento usa el secret nuevo).


3. Estructura del request

Método y URL

POST <tu-url-registrada>

Headers

HeaderValor
AuthorizationBearer <secret>
Content-Typeapplication/json
User-AgentLogirAI-Webhook/1.0
X-Logir-Delivery-IdUUID único del envío (formato evt_<uuid-v4>)
X-Logir-Eventtipo del evento (e.g. guide.delivered)

No enviamos firma HMAC. La autenticación es exclusivamente el Bearer token.

Timeout

LogirAI espera respuesta hasta 10 segundos. Pasado ese tiempo el request se aborta y el evento se considera fallido (sin reintento).

Body — envelope

Todos los eventos comparten esta estructura externa:

json
{
  "id": "evt_550e8400-e29b-41d4-a716-446655440000",
  "type": "guide.delivered",
  "version": "1.0",
  "occurred_at": "2026-04-28T14:32:11.123Z",
  "company": {
    "code": "ACME",
    "country": "CL"
  },
  "data": { ... }
}
CampoTipoDescripción
idstringIdentificador único del envío. Coincide con el header X-Logir-Delivery-Id. Usalo para deduplicar.
typestringUno de los valores del catálogo (§5).
versionstring"1.0" en v1. Si cambiamos el shape, la versión se incrementa.
occurred_atISO 8601 (UTC)Timestamp en el que se construyó el envelope. Para el timestamp del hecho de negocio, ver el campo dentro de data (e.g. delivered_at).
company.codestringCódigo de tu company. Puede venir vacío ("") en casos excepcionales de degradación.
company.countrystring"CL" o "AR". Puede venir vacío en el mismo caso.
dataobjectPayload específico del evento. Ver §5.

4. Lo que tu sistema debe hacer

Respuesta esperada

Devolver 2xx rápido. LogirAI no inspecciona el contenido del response, solo el status code (lo registramos para auditoría).

Tu endpoint debería:

  1. Validar el header Authorization comparando contra el secret con comparación timing-safe.
  2. Persistir o encolar el evento.
  3. Devolver 2xx (idealmente en < 1 s).

No proceses lógica pesada en línea — recibí, persistí, devolvé 200. Tareas largas van a una cola.

Idempotencia

Deduplicá por envelope.id (o el header X-Logir-Delivery-Id).

  • En operación normal, no enviamos duplicados.
  • El evento guide.delivered tiene garantía explícita de single-emit del lado de LogirAI.
  • El resto de los eventos pueden duplicarse en escenarios excepcionales (e.g. operario re-ejecuta una acción). Tu cliente debe ser tolerante a duplicados.

Autenticación

Comparación timing-safe del secret. Ejemplo en Node.js:

js
const crypto = require('crypto');

function isAuthorized(req, expectedSecret) {
  const header = req.headers.authorization || '';
  const m = header.match(/^Bearer\s+(.+)$/);
  if (!m) return false;
  const a = Buffer.from(m[1]);
  const b = Buffer.from(expectedSecret);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Nunca incluyas el secret en logs ni respuestas.

Orden y latencia

  • No garantizamos orden entre eventos. Si tu lógica depende de orden, usá occurred_at o los timestamps dentro de data.
  • No hay rate limit por destino: si LogirAI emite muchos eventos seguidos, te llegan en paralelo (limitado solo por nuestra concurrencia interna).

5. Catálogo de eventos

El catálogo está cerrado en 18 tipos, todos activos.

typeEstado v1Descripción
mawb.created✅ ActivoPre-alerta procesada, MAWB y guías creadas en LogirAI.
mawb.customs_process_started✅ ActivoSe generó el archivo aduanero (AIDA en AR, ATREX en AR).
mawb.flight_arrived✅ ActivoEl vuelo arribó al aeropuerto destino (ATA confirmado).
mawb.incident.created✅ ActivoSe registró una incidencia sobre un MAWB.
mawb.incident.resolved✅ ActivoUna incidencia sobre un MAWB fue resuelta.
guide.presumed_retention✅ ActivoCierre de MAWB con HAWBs faltantes en escaneo — se presume retenida.
guide.held_at_customs✅ ActivoAduana retuvo la guía.
guide.customs_released✅ ActivoAduana liberó la guía (haya tenido o no holds previos).
guide.picked_up_by_courier✅ ActivoEl courier de last-mile recogió la guía del CD.
guide.delivered✅ ActivoEntrega confirmada (PoD del operario o del courier).
guide.critical_annotation_added✅ ActivoSe registró una anotación crítica sobre la guía (cualquier tipo bloqueante para despacho: retención aduanera, presunción de abandono, documentación faltante, etc.).
guide.scanned_in_customs✅ ActivoLa guía fue escaneada al llegar a la bodega de aduana. Se emite en lotes, un evento por guía.
dispatch.cancelled✅ ActivoSe anuló una hoja de ruta y sus guías volvieron atrás. Un evento por empresa presente en el despacho.
guide.damaged✅ ActivoSe registró daño físico sobre la carga de la guía.
guide.requires_documentation✅ ActivoLa guía requiere documentación adicional para poder liberarse (permisos ISP, SAG, SEREMI, DGMN, DIFROL, SIPRO, GICONA, SERNAPESCA).
guide.arrived_at_destination_cd✅ ActivoLa guía llegó al centro de distribución de destino.
guide.assigned_to_courier✅ ActivoLa guía fue asignada a un courier de última milla.
guide.scanned_by_courier✅ ActivoEl courier escaneó la guía en su recorrido.

Payloads por evento (campo data)

mawb.created

json
{
  "mawb": "020-12345678",
  "flight_number": "LA800",
  "origin_airport": "MIA",
  "destination_airport": "SCL",
  "scheduled_arrival": "2026-04-29T08:00:00Z",
  "guides_count": 47
}

flight_number, origin_airport, destination_airport, scheduled_arrival pueden ser null si la pre-alerta no incluía esa información.

guide.scanned_in_customs

json
{
  "guide_number": "DRL-2026-00001",
  "mawb": "020-12345678",
  "manifest_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "scanned_at": "2026-04-29T14:03:11Z",
  "country": "CL"
}

dispatch.cancelled

json
{
  "dispatch_id": "d1e2f3a4-b5c6-7890-abcd-ef1234567890",
  "dispatch_number": "HR-2026-0042",
  "cancelled_at": "2026-04-30T18:20:00Z",
  "cancelled_by_user_id": "u1234567-89ab-cdef-0123-456789abcdef",
  "reason": "El camión no pasó la inspección",
  "total_guides": 2,
  "guides": [
    { "guide_id": "g1", "hawb": "DRL-2026-00001", "manifest_id": "m1", "reverted_to_status_code": 2 }
  ],
  "mawb_ids_affected": ["m1"],
  "batches_reopened": 1,
  "bigboxes_reopened": 0
}

Un evento por empresa representada en el despacho: guides sólo lleva las guías de esa empresa.

mawb.customs_process_started

json
{
  "mawb": "020-12345678",
  "process_id": "uuid-del-manifest",
  "txt_url": null,
  "started_at": "2026-04-28T14:32:11.123Z"
}

txt_url es siempre null en v1: el archivo aduanero (AIDA/ATREX) no se publica en una URL pública.

mawb.flight_arrived

json
{
  "mawb": "020-12345678",
  "ata": "2026-04-29T08:14:00Z",
  "destination_airport": "SCL",
  "guides_count": 47
}

mawb.incident.created

Se dispara de forma síncrona al completar el request HTTP de creación de la incidencia (en el mismo ciclo de request, antes de devolver respuesta al cliente).

Campos de data:

CampoTipoDescripción
manifest_idUUIDID interno del MAWB en LogirAI.
mawb_numberstringNúmero de guía maestra (e.g. 906-12345678).
incidence_idbigintID único de la incidencia. Usalo como clave de deduplicación.
type_codestringCódigo cerrado del tipo de incidencia (ver valores abajo).
type_labelstringDescripción legible del tipo, en español.
commentstring | nullComentario libre del operador. Puede ser null.
created_byUUIDID del usuario que registró la incidencia.
created_atISO 8601 (UTC)Timestamp de creación.
is_blockingbooleantrue si el tipo de incidencia bloquea el despacho del MAWB.
filesarrayArchivos adjuntos. Puede ser []. Ver detalle abajo.

type_code — valores posibles (12 valores cerrados):

CARGO_NOT_ARRIVED, DAMAGED_BOXES_AND_LABELS, MISSING_PIECES, EXCESS_PIECES, WEIGHT_DISCREPANCY, DOCUMENTATION_ISSUE, CUSTOMS_HOLD, PROHIBITED_ITEM, REPACKAGING_REQUIRED, TEMPERATURE_EXCURSION, SECURITY_INCIDENT, OTHER

files[] — cada elemento:

CampoTipoDescripción
urlstringURL firmada con TTL de 1 hora. Descargá el archivo inmediatamente; la URL expira.
namestringNombre original del archivo.
json
{
  "manifest_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "mawb_number": "906-12345678",
  "incidence_id": 4821,
  "type_code": "DAMAGED_BOXES_AND_LABELS",
  "type_label": "Cajas y etiquetas dañadas",
  "comment": "Pallet 3 llegó con signos de humedad. 4 cajas con etiquetas ilegibles.",
  "created_by": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "created_at": "2026-05-01T14:32:11.123Z",
  "is_blocking": false,
  "files": [
    {
      "url": "https://storage.logir.ai/signed/foto-pallet-3.jpg?token=abc123&expires=1746364331",
      "name": "foto-pallet-3.jpg"
    }
  ]
}

Notas operacionales:

  • Emisión síncrona: si tu endpoint está caído, el evento se pierde (sin reintentos, ver §6).
  • Cada emisión genera 1 registro en manifest_incidence_notification_log con status sent o failed.
  • En paralelo, LogirAI envía un email de notificación via Resend a los destinatarios configurados en la company — independiente del webhook.
  • Las URLs de files[].url expiran en 1 hora. Si necesitás persistir los archivos, descargalos al recibir el evento.

mawb.incident.resolved

Se dispara de forma síncrona al completar el request HTTP de resolución de la incidencia.

Campos de data:

CampoTipoDescripción
manifest_idUUIDID interno del MAWB en LogirAI.
mawb_numberstringNúmero de guía maestra.
incidence_idbigintID de la incidencia resuelta. Correlacioná con el incidence_id de mawb.incident.created.
type_codestringCódigo del tipo de incidencia original.
type_labelstringDescripción legible del tipo, en español.
resolved_byUUIDID del usuario que marcó la incidencia como resuelta.
resolved_atISO 8601 (UTC)Timestamp de resolución.
resolution_notesstring | nullNotas de cierre ingresadas por el operador. Puede ser null.
is_blockingbooleanRefleja si el tipo de incidencia era bloqueante. Útil para saber si el despacho puede reanudarse.
json
{
  "manifest_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "mawb_number": "906-12345678",
  "incidence_id": 4821,
  "type_code": "DAMAGED_BOXES_AND_LABELS",
  "type_label": "Cajas y etiquetas dañadas",
  "resolved_by": "c89de012-34ab-5678-cdef-901234567890",
  "resolved_at": "2026-05-01T17:15:42.000Z",
  "resolution_notes": "Se re-etiquetaron las 4 cajas afectadas. Mercadería apta para despacho.",
  "is_blocking": false
}

Notas operacionales:

  • Emisión síncrona dentro del request de resolución (mismo patrón que mawb.incident.created).
  • Genera 1 registro en manifest_incidence_notification_log.
  • Email de notificación vía Resend en paralelo, independiente del webhook.
  • No hay archivos adjuntos en el evento de resolución.

guide.presumed_retention

json
{
  "guide_number": "ABC123",
  "mawb": "020-12345678",
  "html_url": null,
  "presumed_at": "2026-04-28T14:32:11.123Z"
}

html_url es siempre null en v1.

guide.held_at_customs

json
{
  "guide_number": "ABC123",
  "mawb": "020-12345678",
  "hold_reason": "D",
  "hold_detail": "Falta DUI",
  "held_at": "2026-04-28T14:32:11.123Z"
}
  • hold_reason: código aduanero canónico (string corto, e.g. D, R, DF).
  • hold_detail: razón libre escrita por el operador. Puede ser null.

guide.customs_released

json
{
  "guide_number": "ABC123",
  "mawb": "020-12345678",
  "released_at": "2026-04-28T14:32:11.123Z",
  "had_previous_holds": true
}

had_previous_holds: true si la guía estuvo retenida antes de liberarse.

guide.picked_up_by_courier

json
{
  "guide_number": "ABC123",
  "mawb": "020-12345678",
  "picked_up_at": "2026-04-28T14:32:11.123Z"
}

guide.delivered

json
{
  "guide_number": "ABC123",
  "mawb": "020-12345678",
  "delivered_at": "2026-04-28T14:32:11.123Z",
  "pod_signatory": "Juan Pérez",
  "pod_image_url": "https://storage.../pod.jpg"
}

pod_signatory y pod_image_url pueden ser null (algunos couriers no devuelven foto/firma).

guide.critical_annotation_added

Se dispara fire-and-forget (no bloquea el request HTTP de creación de la anotación) cada vez que se registra sobre una guía una anotación de un tipo marcado como bloqueante para despacho (annotation_types.is_blocking_for_dispatch = true). Cubre los 4 paths internos de creación de anotaciones: endpoint /api/annotations (alta unitaria), endpoint /api/annotations/bulk (alta masiva, emite una vez por guía), cron de presunción de abandono, y anotaciones legacy generadas por acciones de aduana (hold/release).

Campos de data:

CampoTipoDescripción
guide_idUUIDID interno de la guía en LogirAI.
annotation_idUUIDID único de la anotación. Usalo como clave de deduplicación a nivel de anotación.
type_codestringCódigo del tipo de anotación (ver tipos críticos abajo).
hold_idUUIDID del hold asociado a la anotación. Las anotaciones críticas siempre generan un hold.
json
{
  "guide_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "annotation_id": "9f8e7d6c-5b4a-3210-9876-543210fedcba",
  "type_code": "withheld_physical",
  "hold_id": "11223344-5566-7788-99aa-bbccddeeff00"
}

type_code — tipos críticos actualmente activos:

withheld_physical, withheld_documentary_declaration, withheld_documentary_customs, withheld_unpaid_taxes, sag_import_permit, required_documentation, presumption_abandonment, customs_retained_legacy

Notas operacionales:

  • Co-emisión con eventos de aduana: cuando una anotación crítica se origina en una acción de aduana (hold o release), LogirAI emite dos eventos diferentes sobre la misma operación física: el evento de dominio (guide.held_at_customs o guide.customs_released) y guide.critical_annotation_added. Son eventos distintos, no duplicados. Si solo te interesa uno, filtralo por type en tu lado.
  • Bulk: una alta masiva con N guías afectadas emite N eventos guide.critical_annotation_added (uno por guía), no un solo evento agregado.
  • Idempotency: si reintentás un alta de anotación con la misma Idempotency-Key y la anotación ya existe (replay), no se re-emite el evento.
  • Fire-and-forget: si el emisor falla (timeout, 5xx), la anotación igual se persiste correctamente — la emisión del webhook no bloquea ni rompe la operación de negocio.
  • Routing per-company: el evento solo llega al webhook configurado para la company dueña de la guía. La anotación crítica de una guía de Lion360 va al webhook de Lion360, no al de otras companies.

6. Garantías y limitaciones

Garantizamos

  • https:// obligatorio en la URL de destino.
  • Single-emit de guide.delivered: aunque dos paths internos (scan operario + webhook courier) marquen entrega concurrente, sólo se emite un evento.
  • Auditoría interna de cada intento (éxito o fallo) para soporte y debugging.

NO garantizamos

  • No hay reintentos. Un fallo (timeout, 5xx, 4xx, conexión rechazada) es definitivo.
  • No hay firma HMAC. La autenticación es exclusivamente el Bearer secret.
  • No hay garantía de orden entre eventos.
  • No hay deduplicación exactly-once salvo guide.delivered. Tu cliente debe deduplicar por id.
  • No hay backpressure: si tu endpoint está caído, los POST igual se disparan y vencen por timeout.

7. Recomendaciones

  • Respondé 2xx rápido (< 1 s). Procesamiento pesado en cola.
  • Deduplicá por envelope.id siempre.
  • Validá Authorization con timing-safe compare.
  • Tolerá campos null en payloads — los marcamos como nullable cuando aplica.
  • Tolerá campos extra: si en v1.x agregamos campos al data, no incrementamos version. Bumps de version se reservan para cambios breaking.
  • Monitoreá el endpoint: si tu URL devuelve 5xx por una ventana larga, vas a perder eventos (no los reintentamos).
  • Pedí re-envío manual a operaciones si detectás un gap. Tenemos auditoría completa de cada intento.

LogirAI — Cross-Border Logistics API for Retailers and Marketplaces