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.
| Tipo | Quando |
|---|---|
emergency | squawk 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. |
military | un aereo segnato come militare nel database degli aeromobili (dbFlags bit 0) |
interesting | un 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, dist | la zona: un punto e un raggio in km, obbligatori. Vale il raggio massimo del piano, come per /lat/…/lon/…/dist/…. |
since, until | secondi Unix o ISO 8601 (senza fuso orario vale UTC). Di default le ultime 24 ore. Al massimo 7 giorni indietro. |
kinds | separati da virgola, di default tutti e tre |
limit | fino 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/webhooks | crea: 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/webhooks | i 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}/deliveries | le ultime 20 consegne: stato, tentativi, codice HTTP, errore |
POST /v2/webhooks/{id}/test | manda una consegna di prova (webhook.test), firmata come le altre |
POST /v2/webhooks/{id}/enable | lo riattiva dopo che è stato disattivato |
POST /v2/webhooks/{id}/delete | lo 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/watchcompreso).
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/watch | gli aerei seguiti, con count e il massimo del piano, max |
POST /v2/watch | aggiunge: aircraft, un elenco di al massimo 50 |
POST /v2/watch/remove | toglie: 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, oppureany(immatricolazione o nominativo).valueè l’aereo senza separatori, l’indirizzo ICAO in minuscolo. Lo stessovaluedue volte è lo stesso aereo: conta inalready_watched, non è un errore. Senzalabel, l’etichetta è quello che hai scritto.- Togliere risponde con l’elenco e
removed, quanti ne sono usciti. - Aggiungere richiede Fleet: con gli altri piani
403concodewatch-plan-requiredeplans. Elencare e togliere funzionano con ogni piano. - Oltre il massimo del piano:
409concodewatch-limit,max,count(quelli seguiti adesso) eadding(quelli nuovi della richiesta). Un aereo che non si riesce a leggere:400concodeaircraft-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,landingo entrambi, e nientelat,lonodist(con una zona:400). Non si mescolano con i tipi di zona nello stesso webhook (400): creane due.landings:confirmed(il default) ofast, per gli atterraggi dedotti dalla scomparsa dell’aereo (più sotto). InGET /v2/webhooksil webhook dice quale usa.- Un webhook segue tutti gli aerei dell’elenco, anche quelli aggiunti dopo. In
GET /v2/webhooksha"for": "aircraft"elat,lon,distanull. - Con un piano senza aerei seguiti:
403concodewatch-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,
/deliveriese 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 | |
|---|---|
type | flight.takeoff o flight.landing; data.kind è takeoff o landing |
time | quando è successo: il primo istante visto nel nuovo stato o, per un atterraggio dedotto dalla scomparsa dell’aereo, il suo ultimo segnale |
detected_at | quando la rete ne è stata sicura (anche timestamp) |
aircraft | icao, registration, callsign, type (designatore di tipo ICAO), helicopter; ognuno può essere null se non si conosce |
airport | l’aeroporto o la piazzola: il codice ICAO quando c’è, altrimenti il codice di OurAirports |
place | airport, o heliport per un elicottero su una piazzola entro 1 km |
distance_km, near | quanto era lontano l’aereo; near è true oltre i 2 km: di’ “vicino a” quell’aeroporto, non “a” |
seen | come è stato riconosciuto l’aeroporto, come nello storico voli: ground, low o approach |
runway | la pista su cui era allineato l’aereo, quando si conosce; null altrimenti e sulle piazzole |
position | il punto da cui è preso l’evento: lat, lon, alt_ft (null a terra), gs_kt, track |
watch | la voce del tuo elenco che corrisponde: kind, value, label |
map | l’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 (
seenlowoapproach) si rileva circa 2,5 minuti dopo l’ultimo segnale;timeè quell’ultimo segnale. Conlandingsconfirmed(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). Confastparte 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 stessoevent_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-idresta 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, ePOST /v2/webhooks/{id}/enablelo 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:
- Prendi il corpo grezzo, come è arrivato, prima di leggere il JSON.
- Calcola l’HMAC-SHA256 di
{webhook-id}.{webhook-timestamp}.{corpo}con la chiave: la parte del segreto dopowhsec_, decodificata da base64. - Confronta il suo base64 con il valore dopo
v1,inwebhook-signature(possono essercene più d’uno, separati da spazi), in tempo costante. - Rifiuta un
webhook-timestamplontano 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));