On Thursday 5 November 2026, hoax bomb threats were sent against flights across Europe for the second time in a week. Reuters counted at least 27 affected flights. Fighter jets escorted airliners into Rome and Sofia, an Aer Lingus jet landed at a French air base, and a Wizz Air flight turned back over the Czech Republic. No explosives were found anywhere.

Two days earlier, on Tuesday, around seven flights had been targeted in a first wave, and an airport outside Warsaw was briefly closed.

This case study looks at the Thursday wave through the data SkyLink recorded: about 19,700 ADS-B positions for the threatened flights we could identify, the aircraft that recovered two of them, and the schedule and status observations from airport boards. It covers what happened to each flight, what the disruption cost in hours and extra flights, what the data could and couldn't see, and what operations teams can take from it.

The full dataset is free to download under CC BY 4.0, which allows commercial use: europe-bomb-threats-2026-11-05.zip (1.1 MB). The Get the data section describes what's in it.

A note on scope: this is an after-the-fact analysis of public data. It isn't an account of how authorities or airlines made their decisions that day, which they haven't published.

Tracks of the threatened flights on 5 November 2026, with the three diversions in amber and dashed lines where coverage drops out

The flights

Eight of the flights named in press reports are in the data. All times here are UTC, an hour behind Central European Time.

FlightRouteWhat the data showsScheduled arrivalActual
Aer Lingus EI402Dublin–RomeDiverted to Évreux-Fauville air base09:35Passengers reached Rome 22:35 on a replacement aircraft
Wizz Air WMT4BXMilan–WarsawTurned back over the Czech Republic, landed Viennaabout 08:00 †Replacement aircraft landed Warsaw 13:25
Ryanair FR1730Stansted–SofiaPassed over Sofia, landed Plovdiv09:05Plovdiv 09:28
Finnair AY1761Helsinki–RomeLanded at destination08:5508:36
Ryanair FR6410Madrid–SofiaLanded at destination08:3008:14
Ryanair FR3606Copenhagen–SofiaLanded at destination08:3508:17
LOT LO235Warsaw–BrusselsLanded at destination, searched—08:26
Finnair AY1471Helsinki–ViennaLanded at destination09:3509:33

† A typical arrival for a Milan–Warsaw flight leaving at 06:15. Scheduled times come from airport boards; "Actual" is the board's arrival time where published and the ADS-B landing time otherwise.

Three of the eight diverted, and the other five arrived on time or early. The escorted flights into Rome and Sofia landed 16 to 19 minutes ahead of schedule, so a fighter escort didn't slow them down. The difference was what happened on the ground after the threat.

Aer Lingus EI402: an hour of manoeuvring, then a military field

EI402 took off from Dublin at about 06:40 with 151 passengers and six crew, according to Al Jazeera, and climbed to 37,000 feet. It crossed the English Channel on a normal track for Rome.

At 07:49, just south of Paris, it started down. For the next 77 minutes the track is the opposite of a routine flight: a descent to 11,000 feet, a long level hold, down to 7,000, a climb back up to 20,000 feet at 08:38, then a final descent into Évreux-Fauville, a French Air Force base about 80 km west of Paris, at 09:06.

EI402's track over northern France: descent from FL370 at 07:49, about an hour of manoeuvring west of Paris, and landing at Évreux-Fauville air base at 09:06

Press reports say French fighters intercepted the flight and that the crew "did not cooperate as expected", without saying how. The climb back to 20,000 feet in the middle of a descent is consistent with an aircraft being held and repositioned while the people on the ground decided where it should land, though the data can't tell you why.

The aircraft flew about 820 nautical miles that morning to reach an airfield 385 nm from Dublin in a straight line.

EI402 altitude profile: cruise at 37,000 feet, then 77 minutes from the start of the descent to touchdown at Évreux

The recovery took the rest of the day. A second Aer Lingus A320, EI-DEI, flew from Dublin to Paris Charles de Gaulle as EI990, landing at 19:20, then on to Rome as EI1402, landing at 22:31. Rome's board shows EI1402 arriving at 22:35, thirteen hours after EI402 was due. The data suggests the passengers were taken by road from Évreux to Charles de Gaulle, but no report we've seen confirms it. The Dublin board listed an "EVX–DUB" return for the original aircraft and marked it cancelled. EVX is Évreux's airport code.

Wizz Air WMT4BX: a U-turn at 39,000 feet

The Wizz Air Malta flight left Milan Malpensa at 06:15 and was cruising at 39,000 feet north-east over Austria and the Czech Republic. At 07:15, roughly 30 km north-west of Brno, it began a descending turn, came all the way round and headed south for Vienna, landing at 07:43, 28 minutes after the turn.

Wizz Air WMT4BX: planned leg from Milan, the U-turn over the Czech Republic, landing in Vienna at 07:43, and the replacement aircraft that flew Vienna to Warsaw at 12:28

Wizz then moved two aircraft around to recover it. A replacement aircraft positioned from Budapest to Vienna late in the morning and flew the WMT4BX callsign from Vienna to Warsaw at 12:28, landing at 13:25. The original aircraft flew back to Milan as WMT804 at 12:18. One threat cost two extra flights and left passengers more than five hours late.

The same callsign was flown by two different airframes that day. If your tracking follows callsigns, WMT4BX "lands in Vienna and then flies to Warsaw five hours later". If it follows hex addresses, you see what really happened.

Ryanair FR1730: straight over Sofia

FR1730 from Stansted descended towards Sofia normally. At about 09:05 it passed almost directly over Sofia airport at 12,000 feet, kept going south-east, and landed at Plovdiv, 74 nm away, at 09:28. Bulgarian fighters escorted it, along with the Madrid and Copenhagen flights that landed in Sofia. Coverage over Bulgaria is thin, so the final approach into Plovdiv isn't in the data.

The aircraft repositioned from Plovdiv to Sofia as RYR317 at 13:01 and left Sofia for Stansted at 14:09.

The flights that weren't diverted

LOT's LO235 landed in Brussels as planned at 08:26 and was searched there. Its return, LO236, normally leaves around 09:40, going by the departure pattern SkyLink records for it. On 5 November it left at 11:59, more than two hours late, although it was never threatened itself. The Finnair flights to Rome and Vienna and the two Ryanair flights into Sofia arrived normally.

Two Wizz Air flights from Milan to Bucharest are in the dataset as candidates. Press reports say the Milan–Bucharest flight received a threat on approach, but not which of the two it was, and coverage ends before Bucharest for both. Czech police said four Prague-bound flights were threatened but never published which, so the dataset includes all 19 arrivals that matched the airlines named, as an unconfirmed candidate set.

What the disruption cost

The headline figure, 27 flights affected, hides how unevenly the cost fell. From the tracks and boards:

  • Three diversions among the eight confirmed flights in the data.
  • About 20 hours of combined delay across the flights we can measure: 13 hours on EI402, more than five on WMT4BX and over two on LO236, before counting the Plovdiv diversion.
  • At least four extra flights: Aer Lingus's Dublin–Paris positioning flight, Wizz Air's Budapest–Vienna positioning and Vienna–Milan return, and Ryanair's Plovdiv–Sofia repositioning.
  • One aircraft out of service for the day on the ground at Évreux, plus two more aircraft and crews pulled from their schedules to recover.
  • Knock-on delays to unthreatened flights, such as LO236 out of Brussels.

The cost of a hoax is mostly in the recovery, not in the threat or the search.

What airport boards said

The dataset keeps every status the airport boards published, with fetch times, and they show how poorly status feeds describe a disruption like this.

At 10:54, nearly two hours after EI402 had landed at Évreux, Rome's arrivals board still showed it as "Scheduled", with an estimated arrival of 12:20. Sofia's board eventually listed FR1730 as "Cancelled", although the aircraft had landed safely at Plovdiv and its passengers were 80 km away. At 10:08, Warsaw's board still listed a Wizz Air arrival from Milan due at 08:10 as "Expected".

We also pulled every board entry marked diverted or cancelled across the boards SkyLink monitors that day. It contains six diversions, all in Europe, and none of them is a threat diversion. Status data recorded the threat wave so poorly that you couldn't reconstruct it from boards alone.

That isn't a criticism of the airports. Boards describe the schedule, and a diversion to a military field isn't something their systems are designed to show. But it means a dashboard driven only by status data would have shown a quiet morning.

What the data couldn't see

Fighter escorts. Press reports describe escorts for EI402, the Finnair Rome flight and the three Ryanair Sofia flights. We checked every aircraft within 8 nm and 5,000 feet of each threatened flight for three minutes or more during its last 90 minutes. No military transponders appeared, only airliners on the same approaches. Fighters on intercept duty usually fly without broadcasting position.

Emergency squawks. The archive doesn't store transponder codes, so whether crews set 7700 or 7500 can't be checked.

Decisions. The data shows where aircraft went and when, never why.

Some of the data itself. Both Finnair flights from Helsinki show impossible positions over the Baltic, including 24 knots at 38,000 feet, consistent with the GNSS interference widely reported in the region; how GPS jamming shows up in ADS-B tracks explains the pattern. Elsewhere the feed sometimes repeated a stale value for minutes or hours. The published dataset flags both kinds of rows rather than deleting them.

Lessons for ops dashboards

1. Detect diversions from positions, not status

The boards lagged by hours, so the position data has to carry the alert. Two simple checks catch all three diversions in this dataset without a single false alarm on the five normal flights:

import math
import pandas as pd

AIRPORTS = {"FCO": (41.8003, 12.2389), "WAW": (52.1657, 20.9671), "SOF": (42.6967, 23.4114),
            "BRU": (50.9014, 4.4844), "VIE": (48.1103, 16.5697)}

def nm(lat1, lon1, lat2, lon2):
    p1, p2 = math.radians(lat1), math.radians(lat2)
    a = (math.sin((p2 - p1) / 2) ** 2
         + math.cos(p1) * math.cos(p2) * math.sin(math.radians(lon2 - lon1) / 2) ** 2)
    return 2 * 3440.065 * math.asin(math.sqrt(a))

def diversion_alerts(track, dest, low_ft=15_000, far_nm=60, back_off_nm=15, hold_s=120):
    """Yield (time, reason) when a flight stops behaving like it's going to `dest`.

    track: rows in time order with timestamp, latitude, longitude, altitude_baro,
    and the dataset's stale and implausible flags.
    A condition has to hold for `hold_s` seconds, so one bad position can't fire it.
    """
    dlat, dlon = AIRPORTS[dest]
    closest, peak = float("inf"), 0
    since = {}            # condition -> first time it was seen true
    fired = set()
    for row in track.itertuples():
        if row.stale or row.implausible or pd.isna(row.latitude):
            continue
        d = nm(row.latitude, row.longitude, dlat, dlon)
        closest = min(closest, d)
        peak = max(peak, row.altitude_baro)
        checks = {
            # came down from cruise while still far from the destination
            "low_far": peak > 25_000 and row.altitude_baro < low_ft and d > far_nm,
            # was getting closer, now clearly moving away
            "moving_away": peak > 25_000 and d > closest + back_off_nm,
        }
        for name, now in checks.items():
            if not now:
                since.pop(name, None)
                continue
            start = since.setdefault(name, row.timestamp)
            if name not in fired and (row.timestamp - start).total_seconds() >= hold_s:
                fired.add(name)
                yield start, (f"below {low_ft:,} ft, {d:.0f} nm from {dest}" if name == "low_far"
                              else f"{d - closest:.0f} nm further from {dest} than its closest point")

Run against the published tracks, it flags EI402 from 07:57, eight minutes after the descent began, with the aircraft already 27 nm further from Rome than its closest point. It flags WMT4BX from 07:20, five minutes after the turn, and FR1730 from 09:08, three minutes after it passed over Sofia without landing. The two-minute hold matters: without it, a single bad position over the Baltic set off a false alarm on a Finnair flight.

In production, feed it positions from the live ADS-B endpoint for the flights you're watching, keep the state per flight, and alert on the first reason. Building a real-time flight tracker covers the polling side.

2. Clean the feed before you alert on it

About 18% of the raw rows in this dataset were stale repeats, and a few hundred were physically impossible. An alerting system that trusts every row will fire on GPS interference and miss real events hidden behind frozen values. Two cheap rules catch most of it: a position that doesn't change while the reported speed says the aircraft is moving, and a speed or jump no airliner can manage. Flight data quality goes deeper.

3. Follow airframes, not just callsigns

WMT4BX was two different aircraft on 5 November. EI402's passengers reached Rome as EI1402. A dashboard that tracks by callsign or flight number loses the thread of the recovery; one that tracks hex addresses, and links replacement aircraft to the original flight, can show the whole story. Callsign vs flight number and Mode S hex codes cover the identifiers.

4. Treat status text as a lagging, partial signal

Use flight status and schedules for what they do well: the planned operation, gates, and confirmed arrivals. Don't use them as the trigger for a disruption alert, and expect them to be wrong during one. Where position and status disagree, show both and say which is newer.

5. Measure the recovery, not just the event

The cost of the day was in replacement aircraft, positioning flights and knock-on delays, all of which happened hours after the threats. If your dashboard closes an incident when the aircraft lands, it misses most of the cost. Keep watching the airframes and the next rotations until the schedule is back to normal.

Get the data

The dataset is free to use, including commercially, under Creative Commons Attribution 4.0. Credit "SkyLink API" and link back.

It contains 12 flight tracks: eight threatened flights, two recovery aircraft and two unconfirmed candidates, at roughly one position every five seconds, each row flagged stale or implausible where it should be dropped. It also has the schedule and status observations with fetch times, SkyLink's flight-leg records, the day's board-level cancellations and diversions, and the Prague candidate arrivals.

Loading it takes three lines:

import pandas as pd

pos = pd.read_csv("positions_all.csv", parse_dates=["timestamp"], dtype={"icao24": str})
clean = pos[~pos.stale & ~pos.implausible]
ei402 = clean[clean.callsign == "EIN40G"]

Read icao24 as text, or hex addresses with leading zeros will break. The README lists the remaining caveats, including the thin coverage over Bulgaria and Romania and a wrong aircraft-database record for one of the Wizz Air airframes.

Doing this for your own operation

Every track here came from the same data SkyLink serves through its API. Live positions come from the ADS-B endpoint, and past tracks from historical ADS-B, which can replay any flight from the last 90 days on Pro, as replaying a flight track shows. Flight status and schedules provide the planned side to compare against. For watching airspace rather than individual flights, see monitoring airspace closures in real time.

You can try the live endpoints on the free trial, which is for personal, non-commercial use. For an operations dashboard, see pricing.

Sources for the press details: Al Jazeera, CNBC, Euronews and CNN, all 5 November 2026. Flight data: SkyLink API.