# Build a reliable MQTT client

> What to handle in a client for the alerts and lightning feeds so it stays correct for months.

Developers · How-to guide · https://wxalerts.org/docs/developers/mqtt/best-practices/

This is for a client you mean to leave running. A first client is in
[Subscribe to live alerts over MQTT](/docs/developers/mqtt-quickstart/); the
topics and payloads are in the [alerts feed](/docs/developers/mqtt/alerts/) and
[lightning feed](/docs/developers/mqtt/lightning/) references.

## Give every client its own ID

Two clients sharing a client ID kick each other off in a loop. Derive it from
something stable and per-installation rather than hardcoding a constant.

## Subscribe narrowly and stay under 20 subscriptions

There is a 20-subscription cap per client. The 21st subscribe is refused with a
failure code in the SUBACK while the connection stays up, so check the return
codes.

On the alerts feed, a subscription per county-per-phenomenon will hit the cap.
Subscribe to `same/{code}/#` and filter in your own code.

On the lightning feed, cover a large area with shorter geohash prefixes rather
than many long ones.

## Reconnect with exponential backoff

Every resubscribe replays the whole retained set. A hot reconnect loop is a
bandwidth amplifier pointed at the broker, which is why it accepts at most 50
new WebSocket connections a second across all clients. Use exponential backoff, and note
that holding an MQTT session open without reading the socket does not detect a
dead connection.

## Handle alert tombstones

A hazard ends with an empty retained payload on the topic it lived on. Test for
a zero-length payload before you parse JSON, and drop the alert when you see one.
[Retained alerts, and tombstones](/docs/developers/mqtt/retained-and-tombstones/)
explains why.

## Decide an alert's state from `status` and `ends`

**`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.

**`action` is the latest VTEC action, not the state.** A hazard that has been
cancelled for some of its counties stays `active`, on its remaining counties,
with `action` set to `CAN`. Go by `status`.

## Dedupe alerts on `id`

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`.

## Style alerts by `geometry_source`

`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.

## Read the lightning feed

This client subscribes to a geohash box, roughly the Dallas-Fort Worth area,
and prints each flash.

```python
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()
```

## Age lightning flashes out yourself

Flashes are not retained, so there is no tombstone when a flash is over. Age
flashes out yourself, and ten minutes is a reasonable ceiling.

## Time lightning by observation, not arrival

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, `t`;
animate on arrival.

## Keep your own history

The REST API keeps 24 hours of flashes. For anything finer over a longer
period, keep the flashes yourself as they arrive.
