Salta al contenuto
Documentazione · Avvisi, emergenze e i tuoi aerei

Avvisi, emergenze e i tuoi aerei

Emergenze e aerei particolari con /v2/events (squawk di emergenza, militari e aerei rari per zona), i decolli e gli atterraggi dei tuoi aerei (Fleet) e gli avvisi via webhook firmati, con verifica in Python e JavaScript.

Aggiornato il

La rete registra le emergenze e gli aerei particolari (squawk 7500, 7600 e 7700, militari, aerei rari) come eventi. Si leggono per zona (/v2/events, con ogni piano) o si ricevono mentre succedono con un webhook (piani a pagamento). Con Fleet un webhook può anche seguire i tuoi aerei: i loro decolli e atterraggi, ovunque volino.

TipoQuando
emergencysquawk 7500 (dirottamento), 7600 (avaria radio), 7700 (emergenza generica), oppure lo stato di emergenza ADS-B (minfuel, nordo, unlawful, downed). Lo stato lifeguard dei voli sanitari non è un’emergenza. reason dice quale.
militaryun aereo segnato come militare nel database degli aeromobili (dbFlags bit 0)
interestingun aereo segnato come “interessante” nel database degli aeromobili (dbFlags bit 1), per esempio gli aerei di Stato

Come nascono gli eventi:

  • Un evento per aereo e per cella di circa 0,5° (circa 55 km da nord a sud). Un volo che attraversa la tua zona viene registrato dove entra in ogni cella. La posizione è un campione, non la traccia: per la traccia completa c’è lo storico.
  • Un aereo nella stessa cella dà un nuovo evento solo dopo 30 minuti di assenza (emergenze) o 60 (gli altri).
  • Una condizione deve comparire due volte di seguito, a pochi secondi di distanza: un singolo messaggio storpiato non diventa un evento.
  • Uno squawk di emergenza è quello che ha trasmesso il transponder, non un’emergenza confermata: capita che un equipaggio lo imposti per sbaglio e lo corregga dopo pochi secondi. Le posizioni vengono da ADS-B o MLAT (source) e possono essere sbagliate o falsificate.
  • Gli eventi riguardano gli aerei: non contengono mai i nomi di proprietari o operatori.

Emergenze e aerei particolari: /v2/events

curl -H "X-Api-Key: LA_TUA_CHIAVE" \
  "https://api.flyitalyadsb.com/v2/events?lat=45.54&lon=10.22&dist=100&since=2026-10-21T12:00:00Z&kinds=emergency,military"

Le emergenze e gli aerei particolari partono da Starter, che legge le ultime 24 ore; Pro e Fleet fino a 7 giorni. Con Free la risposta è 403 con code plan-feature; oltre il piano, 403 con code events-depth.

Parametro
lat, lon, distla zona: un punto e un raggio in km, obbligatori. Vale il raggio massimo del piano, come per /lat/…/lon/…/dist/….
since, untilsecondi Unix o ISO 8601 (senza fuso orario vale UTC). Di default le ultime 24 ore. Al massimo 7 giorni indietro.
kindsseparati da virgola, di default tutti e tre
limitfino a 500, di default 100. truncated: true vuol dire che ce n’erano di più.

Ogni richiesta conta come una. La risposta elenca gli eventi dal più recente, con i campi dell’aereo noti in quel momento, con i nomi di readsb:

{
  "since": "2026-10-21T12:00:00Z", "until": "2026-10-22T12:00:00Z",
  "count": 1, "truncated": false,
  "events": [{
    "id": 1842, "kind": "emergency", "reason": "general", "hex": "4ca7b5",
    "time": "2026-10-22T09:41:07Z", "lat": 45.61, "lon": 10.33, "distance_km": 11.4,
    "flight": "ABC123", "r": "I-ABCD", "t": "A320", "alt_baro": 9800, "gs": 287.4,
    "squawk": "7700", "emergency": "general", "source": "adsb_icao",
    "map": "https://map.flyitalyadsb.com/?icao=4ca7b5"
  }]
}

In un assistente AI, lo strumento MCP notable_traffic fa la stessa domanda (“qualcosa di insolito vicino a Malpensa oggi?”) e raggruppa gli eventi per aereo.

Avvisi via webhook (piani a pagamento)

Un webhook è un tuo URL che riceve una POST HTTPS per ogni nuovo evento in una zona o, con Fleet, per ogni decollo e atterraggio dei tuoi aerei. I webhook sono dell’account, non della chiave: rigenerare la chiave non li perde.

Gli avvisi su un’area sono 1 con Starter e 5 con Pro e Fleet; oltre, la creazione risponde 409 con code area-webhook-limit e max_area.

curl -X POST https://api.flyitalyadsb.com/v2/webhooks \
  -H "X-Api-Key: LA_TUA_CHIAVE" -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/fia-hook", "lat": 45.54, "lon": 10.22, "dist": 80,
       "kinds": ["emergency", "military"], "description": "Brescia"}'

La risposta contiene secret (whsec_…): si vede solo questa volta. Salvalo dove lo legge il tuo server e usalo per verificare ogni consegna.

Richiesta
POST /v2/webhookscrea: url, lat, lon, dist (km, entro il raggio del piano), kinds (di default emergency e military), description; per i tuoi aerei, kinds: ["takeoff", "landing"] e nessuna zona (più sotto)
GET /v2/webhooksi webhook dell’account, con for (area o aircraft), status (active, disabled, paused-plan) e i fallimenti di fila, mai il segreto
GET /v2/webhooks/{id}/deliveriesle ultime 20 consegne: stato, tentativi, codice HTTP, errore
POST /v2/webhooks/{id}/testmanda una consegna di prova (webhook.test), firmata come le altre
POST /v2/webhooks/{id}/enablelo riattiva dopo che è stato disattivato
POST /v2/webhooks/{id}/deletelo cancella

Regole:

  • Al massimo 5 webhook per account, per zone e aerei insieme. Creare, provare e riattivare richiedono un piano a pagamento; elencare e cancellare funzionano con ogni piano. Se il piano finisce, i webhook restano ma non ricevono nulla (paused-plan).
  • L’URL: https://, porta 443 o 8443, un nome di dominio o un indirizzo pubblico, senza utente e password. I nomi che puntano a indirizzi privati, locali o riservati vengono rifiutati, alla creazione e a ogni consegna.
  • Ogni webhook di zona riceve lo stesso aereo, tipo e motivo al massimo una volta l’ora, anche se attraversa più celle della tua zona.
  • Le chiamate di gestione non contano come richieste (al massimo 20 al minuto per chiave, /v2/watch compreso).

I tuoi aerei: decolli e atterraggi (Fleet)

Con Fleet l’account tiene un elenco di aerei seguiti: fino a 25 con Fleet. Un webhook per questi aerei riceve ogni decollo e atterraggio che la rete rileva, senza una zona: ovunque volino. Con gli stessi piani, /v2/flights dà anche i voli di oggi con l’orario di arrivo stimato.

Gli aerei seguiti: /v2/watch

curl -X POST https://api.flyitalyadsb.com/v2/watch \
  -H "X-Api-Key: LA_TUA_CHIAVE" -H "Content-Type: application/json" \
  -d '{"aircraft": ["4D2247", "EI-DAC", {"callsign": "RYR5518", "label": "Navetta Stansted"}]}'
Richiesta
GET /v2/watchgli aerei seguiti, con count e il massimo del piano, max
POST /v2/watchaggiunge: aircraft, un elenco di al massimo 50
POST /v2/watch/removetoglie: aircraft (scritti come quando li aggiungi) o ids (dall’elenco)

Ogni aereo può essere:

  • un indirizzo ICAO, 6 cifre esadecimali: "4D2247";
  • un’immatricolazione con un trattino, uno spazio o un punto in mezzo: "EI-DAC", "EI DAC";
  • qualsiasi altra cosa senza separatori, come "N123AB" o "RYR5518": vale come immatricolazione o come nominativo;
  • un oggetto che dice cos’è: {"icao": "…"}, {"reg": "…"} o {"callsign": "…"}, con un’etichetta facoltativa, label (fino a 60 caratteri), che torna in ogni avviso.

Sei caratteri tutti esadecimali si leggono come indirizzo ICAO: per un nominativo così usa {"callsign": "…"}. Un nominativo vale anche nella forma del numero di volo, come in /v2/callsign: FR5518 e RYR5518 sono lo stesso.

{
  "aircraft": [
    {"id": 12, "kind": "icao", "value": "4d2247", "label": "4D2247", "created_at": "2026-11-07T10:02:11Z"},
    {"id": 13, "kind": "reg", "value": "EIDAC", "label": "EI-DAC", "created_at": "2026-11-07T10:02:11Z"},
    {"id": 14, "kind": "callsign", "value": "RYR5518", "label": "Navetta Stansted", "created_at": "2026-11-07T10:02:11Z"}
  ],
  "count": 3, "max": 25, "added": 3, "already_watched": 0,
  "docs": "https://flyitalyadsb.com/data/api/docs/alerts/#aircraft"
}
  • kind: icao, reg, callsign, oppure any (immatricolazione o nominativo). value è l’aereo senza separatori, l’indirizzo ICAO in minuscolo. Lo stesso value due volte è lo stesso aereo: conta in already_watched, non è un errore. Senza label, l’etichetta è quello che hai scritto.
  • Togliere risponde con l’elenco e removed, quanti ne sono usciti.
  • Aggiungere richiede Fleet: con gli altri piani 403 con code watch-plan-required e plans. Elencare e togliere funzionano con ogni piano.
  • Oltre il massimo del piano: 409 con code watch-limit, max, count (quelli seguiti adesso) e adding (quelli nuovi della richiesta). Un aereo che non si riesce a leggere: 400 con code aircraft-invalid. In entrambi i casi non si aggiunge niente della richiesta.

Avvisi di decollo e atterraggio

curl -X POST https://api.flyitalyadsb.com/v2/webhooks \
  -H "X-Api-Key: LA_TUA_CHIAVE" -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/fia-flights", "kinds": ["takeoff", "landing"], "description": "La nostra flotta"}'
  • kinds: takeoff, landing o entrambi, e niente lat, lon o dist (con una zona: 400). Non si mescolano con i tipi di zona nello stesso webhook (400): creane due.
  • landings: confirmed (il default) o fast, per gli atterraggi dedotti dalla scomparsa dell’aereo (più sotto). In GET /v2/webhooks il webhook dice quale usa.
  • Un webhook segue tutti gli aerei dell’elenco, anche quelli aggiunti dopo. In GET /v2/webhooks ha "for": "aircraft" e lat, lon, dist a null.
  • Con un piano senza aerei seguiti: 403 con code watch-plan-required (con Free, paid-plan-required). Se l’account passa a un piano così, l’elenco e il webhook restano ma non parte niente.
  • Firma, ritentativi, consegne di prova, /deliveries e lo stop automatico funzionano come per ogni webhook (più sotto).

Un decollo:

{"type":"flight.takeoff","timestamp":"2026-11-07T10:41:31Z","data":{
  "webhook_id":9,"event_id":51234,"kind":"takeoff",
  "time":"2026-11-07T10:41:05Z","detected_at":"2026-11-07T10:41:31Z",
  "aircraft":{"icao":"4d2247","registration":"9H-QDF","callsign":"RYR5519","type":"B738","helicopter":false},
  "airport":"EIDW","place":"airport","distance_km":1.3,"near":false,"seen":"ground","runway":"28L",
  "position":{"lat":53.4235,"lon":-6.2493,"alt_ft":null,"gs_kt":141.0,"track":278.0},
  "watch":{"kind":"icao","value":"4d2247","label":"4D2247"},
  "map":"https://map.flyitalyadsb.com/?icao=4d2247"}}
Campo
typeflight.takeoff o flight.landing; data.kind è takeoff o landing
timequando è successo: il primo istante visto nel nuovo stato o, per un atterraggio dedotto dalla scomparsa dell’aereo, il suo ultimo segnale
detected_atquando la rete ne è stata sicura (anche timestamp)
aircrafticao, registration, callsign, type (designatore di tipo ICAO), helicopter; ognuno può essere null se non si conosce
airportl’aeroporto o la piazzola: il codice ICAO quando c’è, altrimenti il codice di OurAirports
placeairport, o heliport per un elicottero su una piazzola entro 1 km
distance_km, nearquanto era lontano l’aereo; near è true oltre i 2 km: di’ “vicino a” quell’aeroporto, non “a”
seencome è stato riconosciuto l’aeroporto, come nello storico voli: ground, low o approach
runwayla pista su cui era allineato l’aereo, quando si conosce; null altrimenti e sulle piazzole
positionil punto da cui è preso l’evento: lat, lon, alt_ft (null a terra), gs_kt, track
watchla voce del tuo elenco che corrisponde: kind, value, label
mapl’aereo sulla mappa live

Quando parte:

  • Decolli e atterraggi si rilevano in tempo reale da quello che vedono i nostri ricevitori, e ne hanno i limiti: uno che la rete non vede non parte. Molti ricevitori perdono gli aerei bassi o a terra, quindi molti atterraggi si deducono da dove l’aereo è sparito.
  • Un avviso per aereo e tipo ogni 20 minuti: giri di campo e touch-and-go mandano il primo, non tutti.
  • Un atterraggio dedotto dalla scomparsa dell’aereo basso vicino a un campo (seen low o approach) si rileva circa 2,5 minuti dopo l’ultimo segnale; time è quell’ultimo segnale. Con landings confirmed (il default) parte 4 minuti dopo, se l’aereo non si è rivisto in volo: circa 6,5 minuti dopo l’ultimo segnale, e solo il 5% circa viene poi annullato (meno del 2% per gli aerei di linea; misurato il 7-8 novembre 2026). Con fast parte subito, e circa il 17% viene annullato (l’8% per gli aerei di linea). Decolli e atterraggi visti a terra partono subito in entrambi i casi.
  • Se quell’aereo si rivede in volo entro 10 minuti dall’ultimo segnale, non era atterrato (un buco di copertura): segue un flight.landing_retracted, con lo stesso event_id, solo ai webhook che avevano ricevuto l’atterraggio. Un atterraggio visto a terra non si annulla mai.
  • Niente avvisi in ritardo: un evento rilevato più di 15 minuti prima non parte mai, e un webhook nuovo riceve solo quello che si rileva dopo la sua creazione.
{"type":"flight.landing_retracted","timestamp":"2026-11-07T12:14:05Z","data":{
  "webhook_id":9,"event_id":51301,"kind":"landing",
  "time":"2026-11-07T12:05:40Z","retracted_at":"2026-11-07T12:14:05Z","reason":"reappeared",
  "aircraft":{"icao":"4d2247","registration":"9H-QDF","callsign":"RYR5519","type":"B738","helicopter":false},
  "airport":"EGSS",
  "message":"the aircraft was seen flying again: it had not landed (a gap in coverage)"}}

Cosa mandiamo

POST /fia-hook HTTP/1.1
Content-Type: application/json
User-Agent: FlyItalyADSB-Webhooks/1 (+https://flyitalyadsb.com/data/api/docs/alerts/)
webhook-id: msg_5170
webhook-timestamp: 1761126067
webhook-signature: v1,cZLu+GFFOqfhSUJYVrKqWHSoDrfb/KFk0VCjFa0OYMM=

{"type":"event.military","timestamp":"2026-10-22T09:41:07Z","data":{"id":1843,"kind":"military","hex":"33ffd1","time":"2026-10-22T09:41:07Z","lat":45.42,"lon":10.12,"distance_km":15.2,"flight":"IAM4653","r":"MM62179","t":"C130","alt_baro":23925,"source":"mlat","map":"https://map.flyitalyadsb.com/?icao=33ffd1","webhook_id":7}}

type vale event.emergency, event.military, event.interesting o webhook.test; data è l’evento come in /v2/events, con distance_km dal centro del webhook. I webhook dei tuoi aerei ricevono flight.takeoff, flight.landing e flight.landing_retracted (più sopra) e webhook.test.

L’esempio è firmato con il segreto di prova whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw (non è un segreto vero): prova il tuo codice su questo, con il controllo dell’ora spento.

La consegna:

  • Rispondi con un 2xx entro 10 secondi. Tutto il resto è un fallimento, redirect compresi (non li seguiamo). Il lavoro lento fallo dopo aver risposto.
  • I fallimenti si ritentano 6 volte, dopo circa 30 s, 2 min, 10 min, 30 min, 1 h e 3 h. webhook-id resta uguale a ogni tentativo: usalo per scartare i doppioni.
  • Al massimo 30 consegne al minuto per webhook; quelle ancora in attesa dopo 24 ore si scartano.
  • Dopo 15 tentativi falliti di fila, in almeno un’ora, il webhook viene disattivato: lo mostra GET /v2/webhooks, e POST /v2/webhooks/{id}/enable lo riaccende.
  • Gli avvisi sono “best effort”, senza un tempo di consegna garantito.

Verificare la firma

Firmiamo con Standard Webhooks, quindi vanno bene anche le sue librerie (pip install standardwebhooks, npm install standardwebhooks). A mano:

  1. Prendi il corpo grezzo, come è arrivato, prima di leggere il JSON.
  2. Calcola l’HMAC-SHA256 di {webhook-id}.{webhook-timestamp}.{corpo} con la chiave: la parte del segreto dopo whsec_, decodificata da base64.
  3. Confronta il suo base64 con il valore dopo v1, in webhook-signature (possono essercene più d’uno, separati da spazi), in tempo costante.
  4. Rifiuta un webhook-timestamp lontano più di 5 minuti da adesso.

Python:

import base64, hashlib, hmac, time

def verify(secret: str, headers, body: bytes, tolerance: int = 300) -> bool:
    msg_id, ts = headers["webhook-id"], headers["webhook-timestamp"]
    if abs(time.time() - int(ts)) > tolerance:
        return False
    key = base64.b64decode(secret.removeprefix("whsec_"))
    expected = base64.b64encode(
        hmac.new(key, f"{msg_id}.{ts}.".encode() + body, hashlib.sha256).digest()
    ).decode()
    return any(
        hmac.compare_digest(expected, sig.split(",", 1)[1])
        for sig in headers["webhook-signature"].split()
        if sig.startswith("v1,")
    )

# Flask: verify(SECRET, request.headers, request.get_data())

JavaScript (Node.js 18 o successivo):

import crypto from 'node:crypto';

export function verify(secret, headers, rawBody, toleranceSeconds = 300) {
  const id = headers['webhook-id'];
  const ts = headers['webhook-timestamp'];
  if (Math.abs(Date.now() / 1000 - Number(ts)) > toleranceSeconds) return false;
  const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');
  const expected = crypto.createHmac('sha256', key).update(`${id}.${ts}.`).update(rawBody).digest();
  return headers['webhook-signature'].split(' ').some((entry) => {
    const [version, sig] = entry.split(',');
    if (version !== 'v1' || !sig) return false;
    const given = Buffer.from(sig, 'base64');
    return given.length === expected.length && crypto.timingSafeEqual(given, expected);
  });
}

// Express: app.post('/fia-hook', express.raw({ type: 'application/json' }),
//   (req, res) => verify(SECRET, req.headers, req.body) ? res.sendStatus(204) : res.sendStatus(401));