Every NWS hazard this system ingests is published to a public broker as it arrives, and stays there, retained, for as long as it is live. Subscribing replays everything currently in effect for your filter, so a client that has been offline comes back up knowing the current state of the world instead of waiting for the next statement.
This is the same broker that carries the lightning feed, on a separate topic tree. To look alerts up on demand instead of following them live, use the alerts REST API.
To write a first client, follow Subscribe to live alerts over MQTT. For what keeps one running, see Build a reliable MQTT client.
Connecting
| URL | wss://mqtt.wxalerts.org/mqtt |
| Host and port | mqtt.wxalerts.org, 443 |
| Transport | MQTT over WebSocket, path /mqtt, TLS |
| Username | wxalerts |
| Password | wxalerts |
The credentials are published on purpose. The account is subscribe-only and the broker’s ACL denies publishing on every topic, so there is nothing to protect. A publish from it is dropped without an error and without disconnecting you. There is no plain MQTT port open to the internet: 1883 and 8883 are closed, so the WebSocket on 443 is the only way in.
Your client ID must be unique. Two clients sharing one will kick each other off in a loop. Derive it from something stable and per-installation rather than hardcoding a constant.
The topic tree
Every hazard is announced twice: once under the office that issued it, and once per county it covers.
| Topic | Retained | QoS | What it carries |
|---|---|---|---|
wxalerts/nws/v1/alert/{office}/{phen}/{sig}/{etn} |
yes | 1 | one live hazard, by issuing office |
wxalerts/nws/v1/same/{same}/{etn} |
yes | 1 | the same hazard, by county |
wxalerts/nws/v1/alert/{office}/Messages/{hash} |
1 hour | 0 | raw text products |
QoS is what the ingest publishes at. You receive the lower of that and the QoS you subscribe with, so subscribe at QoS 1 to get hazards at QoS 1.
The levels are:
{office}: the issuing office, likeKMOB. Alerts that arrive without VTEC are given the four-letter office responsible for their zones. Only when that cannot be worked out does the sender’s name stand in, uppercased with non-alphanumerics replaced by underscores (NWS_MEDFORD_OR).{phen}: the VTEC phenomenon, soTOtornado,SVsevere thunderstorm,FFflash flood, and so on. Without VTEC, the slugged event name stands in.{sig}: significance, soWwarning,Awatch,Yadvisory,Sstatement.Xwhen there is no VTEC to read it from.{etn}: the VTEC event tracking number, zero-padded to four digits. Alerts without VTEC useidfollowed by the alert’s own id.
The ETN is in the topic because the message is retained and one office can have
two tornado warnings live at once. Without it they would share
alert/KMOB/TO/W and the second would erase the first.
Subscribing
Pick the tree that matches how you think about coverage. By county, the way a weather radio works:
wxalerts/nws/v1/same/012113/# one county, every hazard
Or by office and phenomenon:
wxalerts/nws/v1/alert/KMOB/# everything one office issues
wxalerts/nws/v1/alert/+/TO/W/+ every tornado warning in the country
wxalerts/nws/v1/alert/KMOB/TO/W/# one office's tornado warnings
alert/# also matches text products, because messages are filed under the
office that issued them. The two stay separable by shape: a hazard is the office
plus three levels (alert/+/+/+/+), a message is the office plus two
(alert/+/Messages/+). The shapes never collide.
Retention
A live hazard is retained, so subscribing delivers it immediately, and every resubscribe replays the whole retained set for that filter. A hazard ends with a tombstone, an empty payload retained on its topic. Both are explained in Retained alerts, and tombstones.
The payload
{
"published_at": "2026-08-11T20:15:04.318204+00:00", // when the ingest sent it
"id": 1234,
"vtec": "KMOB.TO.W.0012.2026", // null for alerts without VTEC
"event": "Tornado Warning",
"office": "KMOB",
"phenomena": "TO", // null without VTEC
"significance": "W", // W warning, A watch, Y advisory, S statement
"etn": 12,
"action": "NEW", // NEW CON EXT EXA EXB CAN EXP UPG
"status": "active", // active | expired | cancelled
"severity": "Extreme", // Extreme | Severe | Moderate | Minor | Unknown
"urgency": "Immediate",
"certainty": "Observed",
"issued_at": "2026-08-11T20:15:00+00:00",
"onset": "2026-08-11T20:15:00+00:00",
"expires": "2026-08-11T20:45:00+00:00",
"ends": "2026-08-11T20:45:00+00:00",
"ugc": ["FLC113", "ALC003"],
"same": ["012113", "001003"],
"headline": "TORNADO WARNING IN EFFECT UNTIL 345 PM CDT",
"description": "At 315 PM CDT, a severe thunderstorm capable of…",
"instruction": "TAKE COVER NOW! Move to a basement…",
"geometry": { "type": "MultiPolygon", "coordinates": [] }, // GeoJSON, WGS84
"geometry_source": "polygon", // polygon | ugc | none
"sources": ["api", "nwws", "emwin"] // which feeds carried it
}
Timestamps are ISO 8601 with an offset. published_at is stamped as the
message leaves the ingest, so it is the one field you can measure the broker
hop against; issued_at is when NWS wrote the product. geometry is a GeoJSON
Polygon or MultiPolygon in WGS84, or null for a hazard that has no shape.
Notes on individual fields:
expiresis not the end of the hazard. It is CAP’s “expect an update by” deadline and routinely sits in the past on a live alert.endsis the end.actionis the latest VTEC action, not the state. A hazard that has been cancelled for some of its counties staysactive, on its remaining counties, withactionset toCAN.statusis the state.geometry_sourcetells you how precise the shape is.polygonis a tight storm-based warning polygon;ugcis a coarser union of county boundaries. Geometry is simplified for the wire.- One hazard arrives more than once. A warning covering three counties is
published on three
same/topics plus its office topic, with an identical payload each time.idis the same on every copy.
Text products
Raw products are published under the office that issued them, keyed by a content hash so the same product arriving on two feeds lands on one topic instead of appearing twice:
wxalerts/nws/v1/alert/{office}/Messages/{hash}
They carry published_at, wmo_id, awips_id, office, issued_at,
source, hash and the raw body. {hash} in the topic is the first 16
characters of hash. awips_id is null for products that have none, and
source is the feed that delivered the product first, nwws or emwin. They are retained for about an hour and then tombstoned, so a
subscriber joining mid-hour sees what an office has put out recently.
Surface observation collectives are not published here. At roughly 350 bulletins an hour they would swamp the tree with instrument data nobody subscribed to it for.
Broker limits
| limit | value |
|---|---|
| subscriptions per client | 20. The 21st subscribe is refused with a failure code in the SUBACK while the connection stays up |
| new WebSocket connections | at most 50 a second across all clients |

