← How Alerts Work
The Brief
Alerts · Technical Reference
5 live sources · 3 gatesv1 · 2026
Destination Alerts · Technical Reference

From an official feed to a banner, with a paper trail.

The technical reference for the destination alerts system: official-source ingestion, the three qualifying gates, deduplication and the expiry state machine, surfacing rules, and the shadow-trial machinery new sources must pass. For engineers and journalists. Looking for the plain-English version? How Alerts Work →

Stage 01Ingest · official feeds only

Every report is recorded with its source attached.

Poll, normalize, remember.

The pipeline polls each official feed on a fixed cycle with conditional requests — a feed that hasn’t changed costs one cheap check and is skipped. Each source has its own fetcher (alerts/sources/) that normalizes whatever the feed speaks — GeoJSON, CAP, Atom, agency APIs — into one common event shape.

Every report also lands in an append-only observation ledger: one row per source per sighting, the audit trail of which source said what, when. An alert can always answer “who reported this, and how recently?”

Per-source failures are isolated — one broken feed never takes down the cycle — and a feed that fails repeatedly is flagged unhealthy on an operator dashboard rather than failing silently.

NormalizedAlertEvent (sample)alerts/types.ts
{
  "sourceKey": "gdacs",
  "sourceEventId": "TC-1029-2",
  "category": "cyclone",
  "severity": "severe",  // band, already mapped
  "title": "Tropical Cyclone — Yucatán",
  "countryCodes": ["MX"],
  "lat": 21.2, "lng": -86.8,
  "occurredAt": "2026-06-11T09:40:00Z",
  "sourceUrl": "https://www.gdacs.org/…"
}
Stage 02Qualify · three gates

Most events are recorded and never published.

Category bar, severity floor, geo relevance.

A normalized event passes three gates (alerts/gating.ts) or it stays in the ledger unpublished. Gate one: the category must be on the qualifying list at all — a hazard type absent from the table below never qualifies, however severe. Gate two:the severity band must clear that category’s floor. Gate three: the event must map to at least one destination in our catalog — by country code, by region rollup, or, for events that arrive as a bare coordinate, by a severity-scaled radius: Minor 50 km · Moderate 150 km · Severe 300 km · Extreme 600 km. Bigger events reach further.

Advisory changes are the deliberate exception to the floor: every advisory level change qualifies, because the jump itself is the signal — a country moving from level 2 to level 3 is news even though a standing level 3 is not.

The thresholds are operator-tunable configuration, and the table below is rendered from that live configuration — the published numbers cannot drift from the enforcing code.

Qualifying floors (live config)alerts/gating.ts
CategoryMinimum severity
advisoryMinor
cycloneSevere
earthquakeModerate
floodSevere
healthSevere
severe-weatherSevere
tsunamiSevere
volcanoSevere
wildfireSevere
Stage 03Identity · update, never re-fire

One event, one alert, one honest ending.

Deterministic identity.

Every alert has a deterministic dedupe key — for hazards, the source plus the source’s own event id (gdacs:TC-1029-2); for advisories, a synthesized key that folds in the advisory level and a recurrence counter. A feed that reports the same event fifty times produces one alert, updated in place: severity, title, and coordinates refresh; the alert’s identity and history don’t.

Lifecycle is a one-way state machine: activeresolved, retracted, or expired — and the terminal states are absorbing. A resolved alert never silently reactivates; a genuinely new flare-up gets a new identity and a fresh paper trail.

Expiry is the backstop for silent feeds: every fresh report extends the clock from the report’s own timestamp, and when reports stop, the window runs out and the alert expires on its own. A stale banner cannot outlive its source’s silence.

Expiry windows (live config)alerts/lifecycle.ts
CategoryWindowWhy this window
advisory90 daysstanding advisories change slowly
cyclone10 daysmulti-day track plus the immediate aftermath
earthquake3 dayscovers the immediate aftershock window
flood7 daysfloodwater recedes over days, not hours
health30 daysoutbreaks are long-lived
severe-weather36 hoursofficial weather warnings are short-lived
tsunami2 dayswarnings clear fast once the wave passes
volcano14 dayseruptive episodes persist
wildfire14 dayslarge fires burn for weeks
Stage 04Surfaces · banner and follow email

Two surfaces, one vocabulary, zero paraphrase.

The banner.

A destination page shows an alert banner only while the alert is active and unexpired andits source is enabled and fully promoted — an unknown or still-in-trial source fails closed and surfaces nothing. Region pages roll up their member countries’ alerts.

The follow email.

Readers who follow a destination can get an email when an alert fires. Sends are claim-first: the send is recorded before it happens, so a retry can never double-deliver — one alert, one reader, at most one email, ever. If a source withdraws an alert, followers who were notified get an explicit correction rather than silence.

Each follow carries a sensitivity setting in plain terms: confirmed alerts only; confirmed plus serious unconfirmed reports; or everything including early unconfirmed reports. Today only the first tier can fire — see the trust stage below.

What every surfaced alert carriesalerts/alert-labels.ts
{
  "severity": "Severe",      // always a word
  "confidence": "Confirmed",
  "source": "GDACS",           // named, every time
  "asOf": "Jun 11, 2026, 9:40 AM UTC",
  "disclaimer": "Informational only — …"
}
One vocabulary, by construction: the severity words, the confidence phrasing, the source names, and the disclaimer are single shared constants — the banner, the email, and this page all import the same strings, so a paraphrase drift between surfaces is a compile-time impossibility, not an editorial hope. The disclaimer, verbatim: Informational only — not official safety guidance. Confirm details with official sources and local authorities before making travel decisions.
Stage 05Trust · how a source earns its place

New sources run in shadow until they prove out.

Shadow first.

Every new source onboards with a shadow flag: it is ingested, normalized, gated, and scored exactly like a live source — and surfaces nothing. Its would-have-fired record accumulates on an operator dashboard where false positives are visible before any reader could have seen them. Promotion is a deliberate, audited flip, made only after the trial earns it; sources that fail the trial are retired without a reader ever knowing they were tested.

Confidence tiers.

Every published alert today is Confirmed— relayed from an official source. The vocabulary reserves a second tier for corroborated-but-unofficial reports (“Developing — unconfirmed, verify with officials”), and the outbound gate enforces that nothing below Confirmed can fire. The second tier publishes nothing today.

Experimental signals.

We are also evaluating an experimental corroboration signal drawn from official government accounts on social media — it runs in shadow, is judged server-side against the same rules as everything else, and cannot create or surface an alert on its own. Its trial works like every other shadow trial, with a stricter evidence rule: the pipeline issues precisely-scoped queries, accepts only citation links back — never narrative text — and judges every response server-side against an allowlist before a single row is recorded. By construction there is no code path from that signal to a published alert.

Shadow-trial verdicts (operator view)/admin · shadow review
{
  "source": "(in trial)",
  "ingested": 412,
  "wouldSurface": 9,
  "falsePositives": 2,   // the number that decides
  "surfacedToReaders": 0  // always, in shadow
}
Principles

The rules that don’t bend.

Official sources or nothing.

An alert is always a relay of a named official source. There is no editorial path, no AI path, and no social-media path to a published alert.

Update in place, never re-fire.

One real-world event is one alert with one history. Terminal states are absorbing — what resolved stays resolved.

Fail closed.

Unknown source? Still in trial? Pipeline dark? The answer is always the same: surface nothing. Every gate defaults to silence, and the published numbers above are rendered from the enforcing configuration itself.

Bounded, and said so.

Coverage is the destination catalog and the hazard types in the table above, on a polling cadence. The absence of an alert is never an all-clear — and the page you’re reading says so in plain language too.