There's a structural weakness in ADS-B that everyone building on it should understand. The middle letter stands for dependent: the aircraft does not get located by an independent sensor the way primary radar locates it. It works out its own position from satellite navigation and broadcasts that. The receiving system takes the aircraft's word for it.

Which means if you interfere with the satellite signal, ADS-B faithfully broadcasts whatever the aircraft now believes. The transponder isn't malfunctioning. The broadcast is valid, the CRC checks out, and the position is wrong.

GNSS interference around several regions has become common enough that anyone running a tracking product will have it in their data whether or not they've noticed. This post is about what it looks like.

Jamming and spoofing are different problems

Worth separating, because they produce different signatures and you'll misread one as the other otherwise.

Jamming is denial. A transmitter drowns the satellite signal in noise, and receivers within range lose their fix. The aircraft knows it has lost GNSS — it isn't fooled, it's blind — and reverts to inertial navigation and ground-based aids. In the data, positions degrade or stop rather than becoming false.

Spoofing is deception. A transmitter broadcasts counterfeit satellite signals that a receiver accepts as genuine, and the aircraft computes a confident, precise, wrong position. This is the dangerous one for data consumers, because nothing in the broadcast says anything is amiss. You get clean, plausible, internally consistent coordinates for an aircraft that is somewhere else.

Jamming makes aircraft vanish. Spoofing makes them lie.

The signatures

Six patterns, roughly in order of how obvious they are.

Aircraft converging on a single point. The clearest spoofing signature. Rather than scattering aircraft randomly, many spoofing setups push every affected receiver to the same coordinates, often an airport. On a map you get a cluster of unrelated aircraft — different airlines, different types, different flight levels — all reporting positions within a few hundred metres of each other and of a point none of them should be near. Aircraft that should be 200 nm apart claiming to occupy the same patch of ground is not something normal traffic ever produces.

Position frozen, timestamp advancing. The aircraft keeps transmitting, the record keeps updating, and the coordinates don't change. Distinguish this carefully from a stale record being republished, which looks identical from the outside — the difference is that here the aircraft really is broadcasting, with a locked position.

A jump, then a jump back. The track runs normally, leaps a long way, continues coherently in the wrong place for a while, then snaps back to where the aircraft actually was. The return leap is the tell. A decoding error usually produces one bad fix; spoofing produces a sustained excursion with structure to it.

Impossible implied speed. The standard kinematic check catches the entry and exit of a spoofed excursion: distance between consecutive fixes divided by elapsed time gives a speed no aircraft achieves. This is the same gate described in handling gaps, jumps, and bad positions, and it's the cheapest detector you have.

Coverage holes with a shape. Jamming shows up as absence. What separates it from ordinary coverage loss is geography and consistency: a persistent, roughly circular region where aircraft stop reporting positions and resume on the far side, day after day, in an area with otherwise fine receiver coverage. Ordinary coverage gaps sit over oceans and empty terrain, not over well-covered land in a repeatable circle.

Position stops but the aircraft doesn't. A subtle one. Some aircraft continue transmitting identity, altitude, and velocity while their position field goes unavailable, because barometric altitude and airspeed don't depend on GNSS. A record with a live callsign and altitude but no usable latitude and longitude, in a region where that keeps happening, is a jamming footprint.

A navigation satellite above Earth

Detecting it in a live feed

Our live ADS-B endpoint returns positions, not interference assessments, so on the live feed this is detection you do yourself. Two checks get you most of the way.

The kinematic gate you should be running regardless:

import requests
from math import radians, sin, cos, asin, sqrt

BASE = "https://skylink-api.p.rapidapi.com"
H = {"X-RapidAPI-Key": KEY, "X-RapidAPI-Host": "skylink-api.p.rapidapi.com"}

def haversine_nm(lat1, lon1, lat2, lon2):
    lat1, lon1, lat2, lon2 = map(radians, (lat1, lon1, lat2, lon2))
    h = sin((lat2-lat1)/2)**2 + cos(lat1)*cos(lat2)*sin((lon2-lon1)/2)**2
    return 2 * 3440.065 * asin(sqrt(h))

And the clustering check, which is specific to spoofing and which almost nobody implements:

from collections import defaultdict

def suspicious_clusters(aircraft, radius_nm=1.0, min_count=4):
    """Unrelated aircraft reporting near-identical positions is not normal traffic."""
    airborne = [a for a in aircraft
                if not a.get("is_on_ground")
                and a.get("latitude") is not None]

    clusters = defaultdict(list)
    for a in airborne:
        # coarse grid key, then refine
        key = (round(a["latitude"], 1), round(a["longitude"], 1))
        clusters[key].append(a)

    out = []
    for key, group in clusters.items():
        if len(group) < min_count:
            continue
        anchor = group[0]
        tight = [b for b in group
                 if haversine_nm(anchor["latitude"], anchor["longitude"],
                                 b["latitude"], b["longitude"]) <= radius_nm]
        # Several airborne aircraft inside a 1 nm circle, at different levels,
        # is either a formation flight or a spoofed cluster. Formations are rare.
        if len(tight) >= min_count:
            levels = {b.get("altitude") for b in tight}
            out.append({"where": key, "count": len(tight), "distinct_altitudes": len(levels)})
    return out

r = requests.get(f"{BASE}/adsb/aircraft", headers=H,
                 params={"bbox": "33.0,32.0,36.0,36.0"})
for c in suspicious_clusters(r.json().get("aircraft", [])):
    print(c)

Tune the thresholds against your own traffic before trusting them. Holding patterns, airport surfaces, and air displays all produce legitimate tight clusters, which is why the check above excludes ground traffic and looks at altitude spread.

The archive is more explicit about it

Where the live feed gives you raw positions, the Historical ADS-B group carries interference assessment as data. Archived flight records include gps_spoofing_suspected, and individual positions in a track or position-history response carry gps_spoofed.

That difference matters for how you'd use each. Retrospective analysis — did this flight pass through interference, is this incident report trustworthy, which of our tracked flights were affected last month — is a job for the archive, where the assessment has already been made with the benefit of hindsight and neighbouring data. Live detection is heuristic and yours, because in real time nobody has that context yet.

If you're doing anything where a false position has consequences, read the flag before trusting the track. A replay that looks perfectly smooth can still be a spoofed one, and smoothness is not evidence of correctness — a spoofed track is often cleaner than a real one, because it was generated rather than observed.

An Airbus A319 flight deck

What this means for what you build

Three practical positions worth taking.

Don't present a single position as ground truth. If your product makes decisions from aircraft positions, carry an uncertainty notion through to the interface rather than rendering every dot with equal confidence. An aircraft in a region with known interference deserves a different visual treatment from one over a dense receiver network.

Don't treat a jamming-shaped hole as an incident. Tracks that stop mid-air over land generate alarming-looking events. In an interference region the boring explanation is usually right, and an ops dashboard that pages someone every time an aircraft's position drops out over a known jamming area will be muted within a week.

Don't build a public interference map without care. Aggregate patterns in tracking data are interesting and it's tempting to publish. The regions involved are frequently active conflict zones, and inferring the location of a ground transmitter from flight data is a different act from noting that flights in an area report unreliable positions. Worth being deliberate about which of those you're doing.

None of this is exotic any more. It's a normal property of the data now, and the systems that handle it well are the ones that assumed from the start that a position is a claim rather than a measurement.

Getting the data

The live ADS-B endpoint is on every plan including the free trial, which is enough to run the clustering check over a region and see what your own traffic looks like. The explicit gps_spoofing_suspected and gps_spoofed flags live in the Historical ADS-B group, which needs Pro or above.

For the wider question of separating interference from ordinary bad data, flight data quality covers the filtering layer these checks sit inside, and the anatomy of an ADS-B message explains why the protocol has this weakness in the first place.