Skip to content

Eventi in uscita

LifeDome consegna eventi ai partner su due canali che condividono envelope e identificatori. Una sola implementazione di parsing + dedup copre entrambi.

CanaleTriggerUso
Webhook pushLifeDome POST verso l'URL del partner a ogni eventoReal-time
Replay endpointPartner fa GET /api/v1/partner/outbound-events/ con paginazione cursorCatch-up dopo downtime / riavvio

L'envelope è lo stesso byte-per-byte. Il campo id (UUIDv7) è il valore che viaggia anche come header Idempotency-Key sul webhook: usalo come unica chiave di dedup, valida su entrambi i canali.


Configurazione

Tutto si configura sull'integrazione partner via portale (sezione "Accesso API" → card "Webhook in tempo reale") oppure su richiesta a LifeDome.

CampoObbligatorio per abilitareNote
webhook_urlHTTPS only. HTTP rifiutato.
webhook_secretSegreto HMAC-SHA256 per la firma. Restituito una volta sola al momento della rotazione.
webhook_event_typesnoWhitelist tipi. Vuoto ⇒ deriva dagli scope concessi all'integrazione.
webhook_device_filternoSubset device. Vuoto ⇒ tutti i device condivisi.

Disabilitare il webhook: svuotare webhook_url. Il replay endpoint resta disponibile.


Envelope

json
{
  "id": "01HXYZ12345...",
  "type": "alarm.sos",
  "occurred_at": "2026-05-11T10:00:00Z",
  "partner_integration_id": "01HX...",
  "device": { "uid": "01HXDEV...", "imei": "353456789012345" },
  "data": { "...": "shape per-tipo" }
}

Tutti i timestamp ISO-8601 UTC con suffisso Z. Coordinate WGS84. Lo shape di data è specifico per type; sample canonici disponibili nello Schema OpenAPI.


Tipi di evento

TipoScope richiesto
location.updatelocations:read
alarm.sos, alarm.fall, alarm.low_batteryevents:read
geofence.enter, geofence.exitevents:read + geofences:read
health.snapshottelemetry:read
command.ack, command.failedcommands:read
device.online, device.offlinedevices:read
message.received, message.sent, message.delivered, message.failedmessaging:read

Un evento il cui scope non è nel token NON arriva al partner, anche se elencato in webhook_event_types. Il gate è applicato per riga sia in delivery che in replay.


Canale 1 — Webhook push

Request

LifeDome esegue un POST verso webhook_url con body JSON dell'envelope e questi header:

HeaderValore
Content-Typeapplication/json
Idempotency-Keyuguale a envelope.id (UUIDv7)
X-Signaturesha256=<hmac_hex> (vedi sotto)
X-Event-Typecopia di envelope.type — utile per routing senza parsare il body
X-Delivery-Attemptnumero del tentativo corrente, 15

Delivery semantics

  • At-least-once: il partner DEVE dedup su Idempotency-Key.
  • Retry: 5 tentativi, backoff esponenziale (1s, 5s, 30s, 2min, 10min).
  • Timeout: 10s per tentativo.
  • Successo: qualsiasi 2xx entro il timeout.
  • Errori retry-abili: 4xx (eccetto 410), 5xx, errori di rete / timeout.
  • 410 Gone dal partner → la consegna passa immediatamente in DLQ, senza ulteriori retry. Usalo per segnalare il ritiro definitivo dell'URL; per pause temporanee preferire 503.
  • DLQ: dopo 5 fallimenti la riga viene parcheggiata; il recupero avviene via replay endpoint (sotto).
  • Ordine: gli eventi POSSONO arrivare fuori ordine in retry. Ordinare per occurred_at, dedup per id.

Verifica firma

python
import hmac, hashlib

expected = "sha256=" + hmac.new(
    secret.encode(), raw_body, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected, request.headers["X-Signature"]):
    raise Unauthorized()

Il body usato per HMAC è il raw request body ricevuto, byte-per-byte. Non re-serializzare il JSON prima di calcolare la firma attesa: anche differenze di whitespace o ordinamento di chiavi farebbero fallire compare_digest.


Canale 2 — Replay endpoint

GET /api/v1/partner/outbound-events/
    ?since=<uuid7>     # cursor esclusivo; ritorna righe con id > since
    &types=<csv>       # opzionale; CSV di tipi, intersecato con i sottoscritti
    &limit=<n>         # opzionale; default 100, cap 500
Authorization: Bearer <partner-token>

Stesso token OAuth2 usato per le altre chiamate /api/v1/*. Nessuno scope nuovo. La chiamata rientra nel rate limit del partner come ogni altro endpoint REST. Il server applica webhook_event_types, webhook_device_filter e gli scope defensivamente anche in replay, così lo stream è equivalente al webhook.

Parametri:

  • since: UUIDv7 di una envelope ricevuta in precedenza. Omesso ⇒ parte dalla riga più vecchia ancora in finestra di retention.
  • types: CSV di tipi da intersecare con i tipi sottoscritti dall'integrazione. Stringa vuota → 400 invalid_filter (omettere il parametro per ricevere tutti i tipi). Tipi sconosciuti vengono silenziosamente ignorati.
  • limit: intero in 1..500. Fuori range o non intero → 400 invalid_limit.

Response

json
{
  "data": [ /* lista di envelope */ ],
  "next_cursor": "<uuid7>|null",
  "has_more": true
}
  • next_cursor: id dell'ultima riga della pagina corrente. Passalo come ?since= per la pagina successiva. null quando has_more è false.
  • has_more: true se ci sono altre righe disponibili oltre questa pagina.

Errori

StatuscodeQuando
400invalid_cursorsince non è un UUIDv7
400invalid_limitlimit non intero o fuori range 1..500
400invalid_filtertypes= presente ma vuoto
410cursor_expiredsince punta a una riga più vecchia della retention; response carry oldest_available_cursor

Shape:

json
// 410 — cursor fuori finestra di retention (30 giorni)
{
  "error": {
    "code": "cursor_expired",
    "message": "Cursor older than retention window.",
    "oldest_available_cursor": "01HXOLDEST..."
  }
}

// 400 — esempio cursor non UUID
{
  "error": {
    "code": "invalid_cursor",
    "message": "since must be a UUIDv7 (the `id` from a previous response)."
  }
}

In caso di 410 ripartire dal valore oldest_available_cursor accettando il gap (eventi più vecchi della finestra di retention non sono più disponibili).

Recupero post-downtime

text
# pagina dall'ultimo id processato
GET /api/v1/partner/outbound-events/?since=01HXLASTACK&limit=200
→ { "data": [...200], "next_cursor": "01HXPAGE1END", "has_more": true }

# continua finché has_more=false
GET /api/v1/partner/outbound-events/?since=01HXPAGE1END&limit=200
→ { "data": [...87], "next_cursor": null, "has_more": false }

Nessun header X-Signature sulle response di replay: il canale è autenticato via OAuth2 su TLS — identità dal token, integrità da TLS.


Idempotenza condivisa

L'id dell'envelope è UUIDv7 monotonicamente crescente. È al tempo stesso:

  • la chiave primaria della riga lato LifeDome,
  • l'Idempotency-Key del webhook,
  • il cursor del replay.

Tienilo come unica chiave di dedup. Stesso id ricevuto via webhook e poi via replay (o viceversa, durante un catch-up) ⇒ scarta la copia duplicata.


Raw resource access (opzionale)

Per scenari di analytics o backfill su righe sorgente — non per il recupero di eventi — restano disponibili gli endpoint /api/v1/events/, /api/v1/devices/{uid}/locations/, /api/v1/devices/{uid}/telemetry/ e /api/v1/devices/{uid}/commands/ con paginazione cursor (?cursor=&order=asc). La finestra di look-back è quella della tabella sorgente, non quella del webhook (30 giorni).

© Mastersoft — LifeDome