Alerts, emergencies and your aircraft
Emergencies and notable aircraft with /v2/events (emergency squawks, military and rare aircraft by area), the takeoffs and landings of your own aircraft (Fleet), and alerts by signed webhook, checked in Python and JavaScript.
Updated
The network records emergencies and notable aircraft (squawk 7500, 7600 and 7700, military, rare aircraft) as events. You can read them by area (/v2/events, every plan) or receive them as they happen with a webhook (paid plans). On Fleet a webhook can also follow your own aircraft: their takeoffs and landings, wherever they fly.
| Kind | When |
|---|---|
emergency | squawk 7500 (hijack), 7600 (radio failure), 7700 (general emergency), or the ADS-B emergency status (minfuel, nordo, unlawful, downed). The lifeguard status of medical flights is not an emergency. reason says which. |
military | an aircraft marked military in the aircraft database (dbFlags bit 0) |
interesting | an aircraft marked “interesting” in the aircraft database (dbFlags bit 1), such as government aircraft |
How events are recorded:
- One event per aircraft and ~0.5° cell (about 55 km north–south). A flight crossing your area is recorded where it enters each cell. The position is a sample, not the track: for the full track, use History.
- An aircraft in the same cell produces a new event only after 30 minutes away (emergencies) or 60 minutes (the others).
- A condition must be seen twice in a row, a few seconds apart, so a single garbled message does not become an event.
- An emergency squawk is what the transponder sent, not a confirmed emergency: crews sometimes set one by mistake and fix it seconds later. Positions come from ADS-B or MLAT (
source) and can be wrong or spoofed. - Events are about aircraft: they never carry the names of owners or operators.
Emergencies and notable aircraft: /v2/events
curl -H "X-Api-Key: YOUR_KEY" \
"https://api.flyitalyadsb.com/v2/events?lat=45.54&lon=10.22&dist=100&since=2026-10-21T12:00:00Z&kinds=emergency,military"
Emergencies and notable aircraft start from Starter, which reads the last 24 hours; Pro and Fleet up to 7 days. On Free the answer is 403 with code plan-feature; beyond the plan, 403 with code events-depth.
| Parameter | |
|---|---|
lat, lon, dist | the area: a point and a radius in km, required. The plan’s maximum radius applies, as for /lat/…/lon/…/dist/…. |
since, until | Unix seconds or ISO 8601 (without a time zone, UTC). Default: the last 24 hours. At most 7 days back. |
kinds | comma-separated, default all three |
limit | up to 500, default 100. truncated: true means more events matched. |
Each request counts as one. The answer lists events newest first, with the aircraft fields known at that moment, using readsb’s names:
{
"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 an AI assistant, the MCP tool notable_traffic asks the same thing (“anything unusual near Malpensa today?”) and groups the events by aircraft.
Webhook alerts (paid plans)
A webhook is a URL of yours that receives an HTTPS POST for every new event in an area, or, on Fleet, for every takeoff and landing of your aircraft. Webhooks belong to the account, not the key: regenerating the key keeps them.
Area alerts are 1 on Starter and 5 on Pro and Fleet; beyond that, creating one answers 409 with code area-webhook-limit and max_area.
curl -X POST https://api.flyitalyadsb.com/v2/webhooks \
-H "X-Api-Key: YOUR_KEY" -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"}'
The answer carries secret (whsec_…): it is shown only once. Store it where your server reads it, and use it to check every delivery.
| Request | |
|---|---|
POST /v2/webhooks | create: url, lat, lon, dist (km, within the plan’s radius), kinds (default emergency and military), description; for your aircraft, kinds: ["takeoff", "landing"] and no area (below) |
GET /v2/webhooks | the account’s webhooks, with for (area or aircraft), status (active, disabled, paused-plan) and the failures in a row, never the secret |
GET /v2/webhooks/{id}/deliveries | the last 20 deliveries: status, attempts, HTTP code, error |
POST /v2/webhooks/{id}/test | send a test delivery (webhook.test), signed like the others |
POST /v2/webhooks/{id}/enable | enable it again after it was disabled |
POST /v2/webhooks/{id}/delete | delete it |
Rules:
- At most 5 webhooks per account, for areas and aircraft together. Creating, testing and enabling need a paid plan; listing and deleting work on any plan. If the plan ends, the webhooks stay but receive nothing (
paused-plan). - The URL:
https://, port 443 or 8443, a public domain name or address, no user and password. Names that point to private, local or reserved addresses are refused, when you create the webhook and on every delivery. - Each area webhook receives the same aircraft, kind and reason at most once an hour, even if it crosses several cells of your area.
- Management calls do not count as requests (at most 20 a minute per key,
/v2/watchincluded).
Your aircraft: takeoffs and landings (Fleet)
On Fleet the account keeps a list of watched aircraft: up to 25 on Fleet. A webhook for them receives every takeoff and landing the network detects, with no area: wherever they fly. On the same plans, /v2/flights also gives today’s flights with an estimated arrival time.
Watched aircraft: /v2/watch
curl -X POST https://api.flyitalyadsb.com/v2/watch \
-H "X-Api-Key: YOUR_KEY" -H "Content-Type: application/json" \
-d '{"aircraft": ["4D2247", "EI-DAC", {"callsign": "RYR5518", "label": "Stansted shuttle"}]}'
| Request | |
|---|---|
GET /v2/watch | the watched aircraft, with count and the plan’s max |
POST /v2/watch | add: aircraft, a list of up to 50 |
POST /v2/watch/remove | remove: aircraft (written as when adding) or ids (from the list) |
Each aircraft can be:
- an ICAO address, 6 hex digits:
"4D2247"; - a registration with a dash, space or dot inside:
"EI-DAC","EI DAC"; - anything else with no separator, such as
"N123AB"or"RYR5518": it matches as a registration or a callsign; - an object that says which:
{"icao": "…"},{"reg": "…"}or{"callsign": "…"}, with an optionallabel(up to 60 characters) that comes back in every alert.
Six characters that are all hex digits are read as an ICAO address: for a callsign like that, use {"callsign": "…"}. A callsign also matches its flight-number form, as in /v2/callsign: FR5518 and RYR5518 are the same.
{
"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": "Stansted shuttle", "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, orany(registration or callsign).valueis the aircraft without separators, the ICAO address in lower case. The samevaluetwice is the same aircraft: it counts inalready_watched, not as an error. Without alabel, the label is what you wrote.- Removing answers with the list and
removed, how many came off. - Adding needs Fleet: on other plans
403withcodewatch-plan-requiredandplans. Listing and removing work on any plan. - Beyond the plan’s maximum:
409withcodewatch-limit,max,count(watched now) andadding(new in this request). An aircraft that can’t be read:400withcodeaircraft-invalid. In both cases nothing in the request is added.
Takeoff and landing alerts
curl -X POST https://api.flyitalyadsb.com/v2/webhooks \
-H "X-Api-Key: YOUR_KEY" -H "Content-Type: application/json" \
-d '{"url": "https://example.com/fia-flights", "kinds": ["takeoff", "landing"], "description": "Our fleet"}'
kinds:takeoff,landingor both, and nolat,lonordist(with an area:400). They can’t be mixed with area kinds in one webhook (400): create two.landings:confirmed(the default) orfast, for landings deduced from the aircraft disappearing (below). InGET /v2/webhooksthe webhook shows which one it uses.- One webhook follows every aircraft on the list, those added later too. In
GET /v2/webhooksit has"for": "aircraft"andlat,lon,distset tonull. - On a plan without watched aircraft:
403withcodewatch-plan-required(on Free,paid-plan-required). If the account moves to such a plan, the list and the webhook stay but nothing is sent. - Signature, retries, test deliveries,
/deliveriesand the automatic stop work as for every webhook (below).
A takeoff:
{"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"}}
| Field | |
|---|---|
type | flight.takeoff or flight.landing; data.kind is takeoff or landing |
time | when it happened: the first moment seen in the new state or, for a landing deduced from the aircraft disappearing, its last signal |
detected_at | when the network was sure of it (also timestamp) |
aircraft | icao, registration, callsign, type (ICAO type designator), helicopter; each can be null when unknown |
airport | the airport or heliport: its ICAO code where there is one, otherwise its code in OurAirports |
place | airport, or heliport for a helicopter on a pad within 1 km |
distance_km, near | how far the aircraft was from it; near is true beyond 2 km: say “near” that airport, not “at” |
seen | how the airport was recognised, as in the flight history: ground, low or approach |
runway | the runway the aircraft was lined up with, when known; null otherwise and on heliports |
position | the point the event was taken from: lat, lon, alt_ft (null on the ground), gs_kt, track |
watch | the entry of your list that matched: kind, value, label |
map | the aircraft on the live map |
When it is sent:
- Takeoffs and landings are detected in real time from what our receivers see, so they have its limits: one the network does not see is not sent. Many receivers lose aircraft low down or on the ground, so many landings are deduced from where the aircraft disappeared.
- One alert per aircraft and kind every 20 minutes: circuits and touch-and-go send the first one, not all of them.
- A landing deduced from the aircraft disappearing low near a field (
seenloworapproach) is detected about 2.5 minutes after its last signal;timeis that last signal. Withlandingsconfirmed(the default) it is sent 4 minutes after that, if the aircraft hasn’t been seen flying again: about 6.5 minutes after the last signal, and only about 5% of them are withdrawn afterwards (under 2% for airliners; measured on 7–8 November 2026). Withfastit is sent at once, and about 17% are withdrawn (8% for airliners). Takeoffs and landings seen on the ground are sent at once either way. - If that aircraft is seen flying again within 10 minutes of its last signal, it had not landed (a gap in coverage): a
flight.landing_retractedfollows, with the sameevent_id, only to the webhooks that received the landing. A landing seen on the ground is never withdrawn. - No late alerts: an event detected more than 15 minutes earlier is never sent, and a new webhook receives only what is detected after it was created.
{"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)"}}
What we send
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 is event.emergency, event.military, event.interesting or webhook.test; data is the event as in /v2/events, with distance_km from the webhook’s centre. Webhooks for your aircraft receive flight.takeoff, flight.landing and flight.landing_retracted (above) and webhook.test.
This example is signed with the sample secret whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw (not a real one): try your code on it, with the time check turned off.
Delivery:
- Answer with a 2xx within 10 seconds. Anything else is a failure, redirects included (they are not followed). Do the slow work after answering.
- Failures are retried 6 times, after about 30 s, 2 min, 10 min, 30 min, 1 h and 3 h.
webhook-idstays the same on every retry: use it to ignore duplicates. - At most 30 deliveries a minute per webhook; deliveries still waiting after 24 hours are dropped.
- After 15 failed attempts in a row, over at least an hour, the webhook is disabled:
GET /v2/webhooksshows it, andPOST /v2/webhooks/{id}/enableturns it back on. - Alerts are best effort, with no guaranteed delivery time.
Checking the signature
We sign with Standard Webhooks, so its libraries work too (pip install standardwebhooks, npm install standardwebhooks). By hand:
- Take the raw body, as received, before parsing the JSON.
- Compute HMAC-SHA256 of
{webhook-id}.{webhook-timestamp}.{body}with the key: the part of the secret afterwhsec_, decoded from base64. - Compare its base64 with the value after
v1,inwebhook-signature(there can be several, separated by spaces), in constant time. - Refuse a
webhook-timestampmore than 5 minutes from now.
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 or later):
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));