Community. Driven. Weather. Data. | Always Ad-Free | Developer FriendlyChecking statusDiscord
WxAlerts.org, Community Driven Weather Data
Support us
Sign in
API

MQTT alerts feed

Live NWS hazards over MQTT-on-WebSocket, retained per hazard, tombstoned when they end, and filed by both issuing office and SAME code.


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.

Connecting

URL wss://mqtt.wxalerts.org/mqtt
Transport MQTT over WebSocket
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.

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

The levels are:

  • {office}: the issuing WFO, like KMOB. Alerts that arrive without VTEC carry the sender’s name instead, uppercased with non-alphanumerics replaced by underscores (NWS_MEDFORD_OR).
  • {phen}: the VTEC phenomenon, so TO tornado, SV severe thunderstorm, FF flash flood, and so on. Without VTEC, the slugged event name stands in.
  • {sig}: significance, so W warning, A watch, Y advisory, S statement. X when there is no VTEC to read it from.
  • {etn}: the VTEC event tracking number, zero-padded to four digits. Alerts without VTEC use id followed 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.

Retained alerts, and tombstones

A live hazard is retained, so subscribing delivers it immediately.

A hazard ends by way of a tombstone: an empty payload, retained, on the topic it lived on. Drop the alert when one arrives.

This matters more than it sounds. Expiring alerts on local clock arithmetic means a cancelled warning hangs around until its original end time, and a client whose clock has drifted gets it wrong in both directions. Trust status and the tombstones over anything you compute yourself.

The payload

{
  "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
  "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": ["emwin", "api"]
}

Timestamps are ISO 8601 with an offset. geometry is GeoJSON in WGS84, or null for a hazard that has no shape.

Reading the feed

import json
import paho.mqtt.client as mqtt

def on_connect(client, userdata, flags, rc):
    # One county: Mobile, Alabama.
    client.subscribe("wxalerts/nws/v1/same/012113/#", qos=1)

def on_message(client, userdata, msg):
    if not msg.payload:
        print("ended:", msg.topic)     # tombstone, not JSON
        return
    alert = json.loads(msg.payload)
    print(alert["event"], alert["headline"], alert["ends"])

client = mqtt.Client(client_id="my-unique-client", transport="websockets")
client.username_pw_set("wxalerts", "wxalerts")
client.tls_set()
client.on_connect = on_connect
client.on_message = on_message
client.connect("mqtt.wxalerts.org", 443)
client.loop_forever()

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 wmo_id, awips_id, office, issued_at, source, hash and the raw body. 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.

Things that will bite you

expires is 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. Use ends, and trust status and tombstones over your own arithmetic.

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. If your filter overlaps, dedupe on id.

There is a 20-subscription cap per client. A subscription per county-per-phenomenon will hit it. Subscribe to same/{code}/# and filter in your own code.

Every resubscribe replays the whole retained set. A hot reconnect loop is a bandwidth amplifier pointed at the broker. Use exponential backoff, and note that holding an MQTT session open without reading the socket does not detect a dead connection.

geometry_source tells you how precise the shape is. polygon is a tight storm-based warning polygon; ugc is a coarser union of county boundaries. Worth styling differently. Geometry is simplified for the wire.