GOES-19 GLM flashes are published to a public broker as they arrive. This is the same feed the live map draws from, and the browser subscribes directly, with no API in the middle.
The same broker also carries NWS hazards on a separate topic tree, documented on the MQTT alerts feed page.
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 on the public trees and the broker’s ACL denies publishing.
Flash batches are published at QoS 0.
The topic tree is the index
Flashes publish to one topic level per geohash character:
wxalerts/glm/v1/{g}/{g}/{g}/{g}/{g}
So you pick your own resolution with a wildcard, and the message rate follows:
wxalerts/glm/v1/9/# ~5000 km
wxalerts/glm/v1/9/x/# ~1250 km
wxalerts/glm/v1/9/x/j/# ~156 km
wxalerts/glm/v1/9/x/j/5/# ~39 km
wxalerts/glm/v1/9/x/j/5/e ~4.9 km (the published leaf)
The payload
Each message is a batch: every flash one 20-second GLM granule put in one five-character geohash cell, oldest first. A busy cell is split across several messages of at most 500 flashes each.
{
"geohash": "9vg4m",
"satellite": "goes19",
"window_start": "2026-11-09T19:09:20+00:00",
"window_end": "2026-11-09T19:09:40+00:00",
"count": 2,
"flashes": [
{ "t": "2026-11-09T19:09:19.127184+00:00", "lat": 32.7731, "lon": -96.8075, "energy_j": 7.53e-14, "area_m2": 412000000.0 },
{ "t": "2026-11-09T19:09:31.840211+00:00", "lat": 32.7802, "lon": -96.7911, "energy_j": 2.1e-13, "area_m2": 655000000.0 }
]
}
t is the flash’s observation time. lat and lon are rounded to four
decimals, more than the instrument’s few kilometres of accuracy. energy_j is
the optical energy seen at the cloud top, not a measure of what reaches the
ground; the flash rate is the more honest signal of a storm’s strength.
Feed health
wxalerts/glm/v1/status is a retained heartbeat, refreshed every 30 seconds,
so a quiet sky can be told apart from a dead feed:
{
"at": "2026-11-09T19:09:39.387311+00:00",
"healthy": true,
"feed": true,
"mqtt": true,
"last_granule_age_seconds": 19,
"stale_after_seconds": 900,
"satellite": "goes19",
"geohash_precision": 5,
"backfill": true
}
Judge it by at, not by when it arrived, because a retained message can be
old. If the publisher drops off the broker, the broker replaces it with
{"healthy": false, "reason": "publisher disconnected"}. The geohash wildcards
above never match this topic, so subscribe to it on its own.
Reading the feed
import json
import paho.mqtt.client as mqtt
def on_connect(client, userdata, flags, reason_code, properties):
# Roughly the Dallas-Fort Worth area.
client.subscribe("wxalerts/glm/v1/9/v/g/#")
def on_message(client, userdata, msg):
batch = json.loads(msg.payload)
for flash in batch["flashes"]:
print(flash["lat"], flash["lon"], flash["t"])
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, 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()
Things that will bite you
Flashes are not retained. Lightning is an event, not a state, so there is no opening burst when you subscribe and no tombstone when a flash is over. Age flashes out yourself, and ten minutes is a reasonable ceiling. The status heartbeat is the only retained topic in this tree.
Observation time is not arrival time. A batch reaches the broker about 20 seconds after its granule’s window closes, and the window itself is 20 seconds long, so a flash is typically 20 to 40 seconds old when it reaches you. That is older than any sensible animation window. Fade on the observation timestamp; animate on arrival.
There is a 20-subscription cap per client. The 21st subscribe is refused with a failure code in the SUBACK. Cover a large area with shorter geohash prefixes rather than many long ones.
History is a day, not an archive. The flash table behind
GET /lightning/flashes keeps 24 hours, and one
request covers at most 180 minutes of it. Per-day counts are at
GET /v1/stats/lightning/daily, which needs an
API key. For anything finer over a longer period, keep the
flashes yourself as they arrive.

