Skip to content

LogirAI Webhooks β€” API Reference (v1) ​

This guide describes the current contract for webhooks LogirAI sends to your system. If a behavior is not described here, assume it is not supported in v1.


1. Overview ​

When events occur in the logistics operation, LogirAI sends HTTP POST notifications to the URL you have registered with us.

  • Fire-and-forget: 1 event β†’ 1 POST. No retries.
  • Authentication: Authorization: Bearer <secret> header.
  • No per-event filtering in v1: your URL receives every event available for your company.
  • One active URL per client in v1. If you need multiple destinations, contact operations.

2. Onboarding ​

Webhook setup and teardown are handled manually with the LogirAI team (no self-service in v1).

To enable webhooks, send us:

  1. Destination URL β€” must be https://... (plain HTTP is rejected).
  2. Technical contact β€” to coordinate the secret.

LogirAI will return a secret that will arrive in every request as the Authorization: Bearer <secret> header. Store it securely.

To rotate the secret or disable the subscription: contact operations. Rotation is immediate (no grace period: the next event uses the new secret).


3. Request structure ​

Method and URL ​

POST <your-registered-url>

Headers ​

HeaderValue
AuthorizationBearer <secret>
Content-Typeapplication/json
User-AgentLogirAI-Webhook/1.0
X-Logir-Delivery-IdUnique delivery UUID (format evt_<uuid-v4>)
X-Logir-EventEvent type (e.g. guide.delivered)

We do not send an HMAC signature. Authentication is exclusively the Bearer token.

Timeout ​

LogirAI waits up to 10 seconds for a response. After that, the request is aborted and the event is considered failed (no retry).

Body β€” envelope ​

All events share this outer structure:

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": { ... }
}
FieldTypeDescription
idstringUnique delivery identifier. Matches the X-Logir-Delivery-Id header. Use it to deduplicate.
typestringOne of the catalog values (Β§5).
versionstring"1.0" in v1. The version is bumped when the shape changes.
occurred_atISO 8601 (UTC)Timestamp when the envelope was built. For the business event timestamp, see the field inside data (e.g. delivered_at).
company.codestringYour company code. May be empty ("") in rare degraded cases.
company.countrystring"CL" or "AR". May be empty in the same case.
dataobjectEvent-specific payload. See Β§5.

4. What your system must do ​

Expected response ​

Return 2xx quickly. LogirAI does not inspect the response body β€” only the status code (logged for audit).

Your endpoint should:

  1. Validate the Authorization header against your secret using a timing-safe comparison.
  2. Persist or enqueue the event.
  3. Return 2xx (ideally in under 1 s).

Do not run heavy processing inline β€” receive, persist, return 200. Long-running tasks belong in a queue.

Idempotency ​

Deduplicate by envelope.id (or the X-Logir-Delivery-Id header).

  • Under normal operation, we do not send duplicates.
  • The guide.delivered event has explicit single-emit guarantees on LogirAI's side.
  • Other events may duplicate in edge cases (e.g. an operator re-runs an action). Your client must be tolerant of duplicates.

Authentication ​

Timing-safe comparison of the secret. Example in 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);
}

Never include the secret in logs or responses.

Order and latency ​

  • Order is not guaranteed between events. If your logic depends on order, use occurred_at or the timestamps inside data.
  • No per-destination rate limit: if LogirAI emits many events in a row, they arrive in parallel (bounded only by our internal concurrency).

5. Event catalog ​

The catalog is closed at 18 types, all active.

typev1 statusDescription
mawb.createdβœ… ActivePre-alert processed; MAWB and guides created in LogirAI.
mawb.customs_process_startedβœ… ActiveCustoms file generated (AIDA in AR, ATREX in AR).
mawb.flight_arrivedβœ… ActiveThe flight arrived at the destination airport (ATA confirmed).
mawb.incident.createdβœ… ActiveAn incident was recorded against a MAWB.
mawb.incident.resolvedβœ… ActiveAn incident on a MAWB was resolved.
guide.presumed_retentionβœ… ActiveMAWB closed with HAWBs missing from scan β€” presumed retained.
guide.held_at_customsβœ… ActiveCustoms has held the guide.
guide.customs_releasedβœ… ActiveCustoms released the guide (whether or not it had previous holds).
guide.picked_up_by_courierβœ… ActiveThe last-mile courier picked up the guide from the CD.
guide.deliveredβœ… ActiveDelivery confirmed (PoD from operator or courier).
guide.critical_annotation_addedβœ… ActiveA critical annotation was recorded against the guide (any type that is blocking for dispatch: customs hold, presumed abandonment, missing documentation, etc.).
guide.scanned_in_customsβœ… ActiveThe guide was scanned on arrival at the customs warehouse. Emitted in batches, one event per guide.
dispatch.cancelledβœ… ActiveA dispatch (hoja de ruta) was cancelled and its guides reverted. One event per company on the dispatch.
guide.damagedβœ… ActivePhysical damage was recorded against the guide's cargo.
guide.requires_documentationβœ… ActiveThe guide needs additional documentation before it can be released (ISP, SAG, SEREMI, DGMN, DIFROL, SIPRO, GICONA or SERNAPESCA permits).
guide.arrived_at_destination_cdβœ… ActiveThe guide reached the destination distribution centre.
guide.assigned_to_courierβœ… ActiveThe guide was assigned to a last-mile courier.
guide.scanned_by_courierβœ… ActiveThe courier scanned the guide along its route.

Per-event payloads (data field) ​

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 may be null if the pre-alert did not include them.

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": "Truck failed inspection",
  "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
}

One event per company represented on the dispatch: guides only ever carries that company's guides.

mawb.customs_process_started ​

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

txt_url is always null in v1: the customs file (AIDA/ATREX) is not published to a public URL.

mawb.flight_arrived ​

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

mawb.incident.created ​

Fires synchronously when the HTTP request that creates the incident completes (within the same request cycle, before the response is returned to the client).

data fields:

FieldTypeDescription
manifest_idUUIDInternal MAWB ID in LogirAI.
mawb_numberstringMaster guide number (e.g. 906-12345678).
incidence_idbigintUnique incident ID. Use as the dedup key.
type_codestringClosed code for the incident type (see values below).
type_labelstringHuman-readable description of the type, in Spanish.
commentstring | nullFree-form operator comment. May be null.
created_byUUIDID of the user who recorded the incident.
created_atISO 8601 (UTC)Creation timestamp.
is_blockingbooleantrue if the incident type blocks MAWB dispatch.
filesarrayAttached files. May be []. See detail below.

type_code β€” possible values (12 closed values):

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[] β€” each element:

FieldTypeDescription
urlstringSigned URL with a 1-hour TTL. Download immediately; the URL expires.
namestringOriginal file name.
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 arrived with signs of moisture. 4 boxes have illegible labels.",
  "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/pallet-3-photo.jpg?token=abc123&expires=1746364331",
      "name": "pallet-3-photo.jpg"
    }
  ]
}

Operational notes:

  • Synchronous emission: if your endpoint is down, the event is lost (no retries β€” see Β§6).
  • Each emission produces 1 row in manifest_incidence_notification_log with status sent or failed.
  • In parallel, LogirAI sends a notification email via Resend to the recipients configured on the company β€” independent of the webhook.
  • files[].url URLs expire in 1 hour. If you need to persist the files, download them when you receive the event.

mawb.incident.resolved ​

Fires synchronously when the HTTP request that resolves the incident completes.

data fields:

FieldTypeDescription
manifest_idUUIDInternal MAWB ID in LogirAI.
mawb_numberstringMaster guide number.
incidence_idbigintID of the resolved incident. Correlate with the incidence_id from mawb.incident.created.
type_codestringCode of the original incident type.
type_labelstringHuman-readable description of the type, in Spanish.
resolved_byUUIDID of the user who marked the incident as resolved.
resolved_atISO 8601 (UTC)Resolution timestamp.
resolution_notesstring | nullClosing notes entered by the operator. May be null.
is_blockingbooleanReflects whether the incident type was blocking. Useful to know whether dispatch can resume.
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": "The 4 affected boxes were re-labeled. Cargo is fit for dispatch.",
  "is_blocking": false
}

Operational notes:

  • Synchronous emission within the resolution request (same pattern as mawb.incident.created).
  • Produces 1 row in manifest_incidence_notification_log.
  • Notification email via Resend in parallel, independent of the webhook.
  • No file attachments on the resolution event.

guide.presumed_retention ​

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

html_url is always null in v1.

guide.held_at_customs ​

json
{
  "guide_number": "ABC123",
  "mawb": "020-12345678",
  "hold_reason": "D",
  "hold_detail": "Missing DUI",
  "held_at": "2026-04-28T14:32:11.123Z"
}
  • hold_reason: canonical customs code (short string, e.g. D, R, DF).
  • hold_detail: free-text reason written by the operator. May be 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 if the guide was held before being released.

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 and pod_image_url may be null (some couriers do not return signature or photo).

guide.critical_annotation_added ​

Fires fire-and-forget (does not block the HTTP request that creates the annotation) every time a guide receives an annotation of a type marked as blocking for dispatch (annotation_types.is_blocking_for_dispatch = true). Covers all 4 internal annotation creation paths: /api/annotations endpoint (single create), /api/annotations/bulk endpoint (bulk create β€” emits once per guide), the abandonment-presumption cron, and legacy annotations generated by customs actions (hold/release).

data fields:

FieldTypeDescription
guide_idUUIDInternal LogirAI guide ID.
annotation_idUUIDUnique annotation ID. Use as the dedup key at the annotation level.
type_codestringAnnotation type code (see critical types below).
hold_idUUIDID of the hold associated with the annotation. Critical annotations always create a 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 β€” critical types currently active:

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

Operational notes:

  • Co-emission with customs events: when a critical annotation originates from a customs action (hold or release), LogirAI emits two different events for the same physical operation: the domain event (guide.held_at_customs or guide.customs_released) and guide.critical_annotation_added. They are distinct events, not duplicates. If you only care about one, filter by type on your side.
  • Bulk: a bulk create affecting N guides emits N events of guide.critical_annotation_added (one per guide), not a single aggregate event.
  • Idempotency: retrying an annotation create with the same Idempotency-Key when the annotation already exists (replay) does not re-emit the event.
  • Fire-and-forget: if the emitter fails (timeout, 5xx), the annotation is still persisted correctly β€” webhook emission does not block or break the business operation.
  • Per-company routing: the event is delivered only to the webhook configured for the company that owns the guide. A critical annotation on a Lion360 guide goes to Lion360's webhook, not to other companies'.

6. Guarantees and limitations ​

We guarantee ​

  • https:// is required for the destination URL.
  • Single-emit for guide.delivered: even if two internal paths (operator scan + courier webhook) mark delivery concurrently, only one event is emitted.
  • Internal audit of every attempt (success or failure) for support and debugging.

We do NOT guarantee ​

  • No retries. A failure (timeout, 5xx, 4xx, connection refused) is final.
  • No HMAC signature. Authentication is exclusively the Bearer secret.
  • No ordering guarantee between events.
  • No exactly-once delivery except for guide.delivered. Your client must deduplicate by id.
  • No backpressure: if your endpoint is down, POSTs are still issued and time out.

7. Recommendations ​

  • Return 2xx fast (< 1 s). Heavy processing belongs in a queue.
  • Always deduplicate by envelope.id.
  • Validate Authorization with a timing-safe compare.
  • Tolerate null fields in payloads β€” we mark them as nullable when applicable.
  • Tolerate extra fields: if we add fields to data in a v1.x release, we do not bump version. Version bumps are reserved for breaking changes.
  • Monitor your endpoint: if your URL returns 5xx for a long window, you will lose events (we do not retry them).
  • Request a manual replay from operations if you detect a gap. We have a full audit trail of every attempt.

LogirAI β€” Cross-Border Logistics API for Retailers and Marketplaces