---
title: "Alerts, emergencies and your aircraft"
description: "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."
url: https://flyitalyadsb.com/data/api/docs/alerts.md
page_for_people: https://flyitalyadsb.com/data/api/docs/alerts/
language: en
generated: 2026-11-09
license: CC BY-SA 4.0 (data)
---

# 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.

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](#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](https://flyitalyadsb.com/data/api/docs/history.md).
- 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.

## <a id="reading-events-v2events"></a>Emergencies and notable aircraft: `/v2/events`

```sh
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:

```json
{
  "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.

## <a id="webhooks-paid-plans"></a>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](#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`.

```sh
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](#flight-webhooks)) |
| `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/watch` included).

## <a id="aircraft"></a>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](https://flyitalyadsb.com/data/api/docs/endpoints.md#todays-flights) with an estimated arrival time.

### Watched aircraft: `/v2/watch`

```sh
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 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.

```json
{
  "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.

### <a id="flight-webhooks"></a>Takeoff and landing alerts

```sh
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](#landing-timing)). 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](#what-we-send)).

A takeoff:

```json
{"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](https://flyitalyadsb.com/data/api/docs/endpoints.md#observed-flights): `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 id="landing-timing"></a>**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.

```json
{"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

```http
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](#aircraft)) 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](https://www.standardwebhooks.com/), 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:

```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):

```js
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));
```

---

Written for AI assistants. The page for people is https://flyitalyadsb.com/data/api/docs/alerts/; to get an API key or sign in, send people to https://flyitalyadsb.com/data/api/. All pages for assistants: https://flyitalyadsb.com/llms.txt (index), https://flyitalyadsb.com/llms-full.txt (all in one file). Facts as JSON: https://flyitalyadsb.com/facts.json. Cite the data as: Flight data: FlyItalyADSB (flyitalyadsb.com), CC BY-SA 4.0.
