Skip to content
Documentation · Alerts, emergencies and your aircraft

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.

KindWhen
emergencysquawk 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.
militaryan aircraft marked military in the aircraft database (dbFlags bit 0)
interestingan 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, distthe area: a point and a radius in km, required. The plan’s maximum radius applies, as for /lat/…/lon/…/dist/….
since, untilUnix seconds or ISO 8601 (without a time zone, UTC). Default: the last 24 hours. At most 7 days back.
kindscomma-separated, default all three
limitup 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/webhookscreate: 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/webhooksthe 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}/deliveriesthe last 20 deliveries: status, attempts, HTTP code, error
POST /v2/webhooks/{id}/testsend a test delivery (webhook.test), signed like the others
POST /v2/webhooks/{id}/enableenable it again after it was disabled
POST /v2/webhooks/{id}/deletedelete 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/watch included).

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/watchthe watched aircraft, with count and the plan’s max
POST /v2/watchadd: aircraft, a list of up to 50
POST /v2/watch/removeremove: 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 optional label (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, or any (registration or callsign). value is the aircraft without separators, the ICAO address in lower case. The same value twice is the same aircraft: it counts in already_watched, not as an error. Without a label, the label is what you wrote.
  • Removing answers with the list and removed, how many came off.
  • Adding needs Fleet: on other plans 403 with code watch-plan-required and plans. Listing and removing work on any plan.
  • Beyond the plan’s maximum: 409 with code watch-limit, max, count (watched now) and adding (new in this request). An aircraft that can’t be read: 400 with code aircraft-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, landing or both, and no lat, lon or dist (with an area: 400). They can’t be mixed with area kinds in one webhook (400): create two.
  • landings: confirmed (the default) or fast, for landings deduced from the aircraft disappearing (below). In GET /v2/webhooks the webhook shows which one it uses.
  • One webhook follows every aircraft on the list, those added later too. In GET /v2/webhooks it has "for": "aircraft" and lat, lon, dist set to null.
  • On a plan without watched aircraft: 403 with code watch-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, /deliveries and 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
typeflight.takeoff or flight.landing; data.kind is takeoff or landing
timewhen it happened: the first moment seen in the new state or, for a landing deduced from the aircraft disappearing, its last signal
detected_atwhen the network was sure of it (also timestamp)
aircrafticao, registration, callsign, type (ICAO type designator), helicopter; each can be null when unknown
airportthe airport or heliport: its ICAO code where there is one, otherwise its code in OurAirports
placeairport, or heliport for a helicopter on a pad within 1 km
distance_km, nearhow far the aircraft was from it; near is true beyond 2 km: say “near” that airport, not “at”
seenhow the airport was recognised, as in the flight history: ground, low or approach
runwaythe runway the aircraft was lined up with, when known; null otherwise and on heliports
positionthe point the event was taken from: lat, lon, alt_ft (null on the ground), gs_kt, track
watchthe entry of your list that matched: kind, value, label
mapthe 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 (seen low or approach) is detected about 2.5 minutes after its last signal; time is that last signal. With landings confirmed (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). With fast it 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_retracted follows, with the same event_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-id stays 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/webhooks shows it, and POST /v2/webhooks/{id}/enable turns 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:

  1. Take the raw body, as received, before parsing the JSON.
  2. Compute HMAC-SHA256 of {webhook-id}.{webhook-timestamp}.{body} with the key: the part of the secret after whsec_, decoded from base64.
  3. Compare its base64 with the value after v1, in webhook-signature (there can be several, separated by spaces), in constant time.
  4. Refuse a webhook-timestamp more 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));