mqtt.pskreporter.info republishes every reception report sent to PSK Reporter as an MQTT message, as it arrives. The spot’s details are in the topic, so you can use MQTT wildcards to subscribe to just the spots you want: by band, mode, callsign, grid square, DXCC entity, or any mix of those. It also carries WSPR spots and the details receiving stations publish about themselves.

There’s no account and no API key. The feed comes from Philip Gladstone at PSK Reporter, used with his permission. Part of the hosting is donated.

Connecting

Point any MQTT client at mqtt.pskreporter.info. There are four ports:

PortTransport
1883MQTT over TCP
1884MQTT over TLS
1885MQTT over WebSockets
1886MQTT over WebSockets with TLS, for pages served over https

MQTT 3.1, 3.1.1 and 5 all work. Connect without a username or password. Use a client id without + or # in it, and a keepalive of something like 60 seconds. WebSocket clients have to offer the mqtt subprotocol, which the usual MQTT libraries do.

To try it from a terminal with the Mosquitto client:

mosquitto_sub -h mqtt.pskreporter.info -t "pskr/filter/v2/20m/FT8/#"

Topics

flowchart LR
    P["pskr/"] --> V2["filter/v2/ (8 levels)"]
    P --> VR["filter/v2raw/ (8 levels)"]
    P --> V1["filter/v2raw_1pc/ (8 levels)"]
    P --> VF["filter/v2_field/ (4 levels)"]
    P --> H["heard"]
    P --> R["rxinfo/{rx call}"]
    W["wspr/"] --> WF["filter/ (8 levels)"]
    W --> WH["heard"]

Most people want pskr/filter/v2. Each spot is published to a topic with eight levels after that prefix:

pskr/filter/v2/{band}/{mode}/{sender call}/{receiver call}/{sender grid}/{receiver grid}/{sender DXCC}/{receiver DXCC}

For example, a 20m FT8 signal from G4ABC in IO91 heard by K1XYZ in FN42 goes to pskr/filter/v2/20m/FT8/G4ABC/K1XYZ/IO91/FN42/223/291. The grids in the topic are the 4 character square, in upper case; the payload has the full locator. DXCC entities are ADIF entity numbers. A / in a callsign would add a topic level, so it is written as . in the topic: G4ABC/P becomes G4ABC.P.

+ matches one level and # matches the rest. Some filters:

FilterGets you
pskr/filter/v2/20m/FT8/#every 20m FT8 spot
pskr/filter/v2/+/+/G4ABC/#everyone who heard G4ABC
pskr/filter/v2/+/+/+/G4ABC/#everyone G4ABC heard
pskr/filter/v2/+/+/+/+/+/IO91/#everything heard in square IO91
pskr/filter/v2/+/CW/+/+/+/+/+/223CW heard by stations in England

The other topic trees:

  • pskr/filter/v2 only has spots where every field is present and valid. PSK Reporter’s data has gaps, so some spots are left out.
  • pskr/filter/v2raw has every spot with both callsigns. A missing field is written as UNKNOWN in the topic, and grids are as reported rather than cut to 4 characters.
  • pskr/filter/v2raw_1pc is a random 1% of v2raw. It’s handy for testing, and the live views on the website use it.
  • pskr/filter/v2_field/{band}/{mode}/{sender field}/{receiver field} is keyed on the 2 character Maidenhead field, so pskr/filter/v2_field/+/+/IO/+ is every spot sent from the IO field.
  • pskr/heard gets the sender’s callsign, as plain text, for every spot.
  • pskr/rxinfo/{receiver call} has each active receiver’s antenna, rig, decoding software and locator, taken from PSK Reporter’s list of active receivers. These are retained, so subscribing to pskr/rxinfo/# gives you the whole current set straight away, then changes as they happen.
  • wspr/filter and wspr/heard are the same thing for WSPR, with the same eight levels.

If a spot matches more than one of your filters, you get one copy per matching filter. Each pskr spot has a unique sequence number, sq, which you can use to drop repeats.

Message format

Each spot is one JSON object:

{
  "sq": 30142870791,
  "f": 21074653,
  "md": "FT8",
  "rp": -5,
  "t": 1662407712,
  "t_tx": 1662407700,
  "sc": "SP2EWQ",
  "sl": "JO93fn42",
  "rc": "CU3AT",
  "rl": "HM68jp36",
  "sa": 269,
  "ra": 149,
  "b": "15m"
}
FieldMeaning
sqPSK Reporter’s sequence number
ffrequency, Hz
mdmode
rpsignal report, dB
ttime the receiver reported, Unix seconds
t_txwhen the transmission started, worked out by the service; can be absent
sc, slsender callsign and locator
rc, rlreceiver callsign and locator
sa, rasender and receiver ADIF DXCC entity
bband

t means different things for different software. Some programs report when the transmission started and others when they decoded it, which for FT8 is 11 to 14 seconds later. PSK Reporter keeps a list of about 2,000 software versions saying which is which. The service reads it every hour and uses it to fill in t_tx: the reported time if the software reports the start, the start of the 15 second slot for FT8 software that reports the decode time, and nothing otherwise. If you want one time per spot, use t_tx and fall back to t. The spot timing page has the details.

WSPR spots have the same fields apart from sq and t_tx, plus pw (claimed power, dBm), dt (time offset), dr (drift) and src (where the spot came from).

How it works

flowchart TD
    S["PSK Reporter<br/>live spot stream"] --> I
    L["PSK Reporter<br/>software and receiver lists"] -. hourly .-> I
    W["WSPR sources"] --> I
    I["Ingest service<br/>check, enrich, build topics"] --> H["Central broker<br/>not open to the public"]
    H --> F["Fan-out brokers"]
    F --> C["Your MQTT client"]
    F --> B["Web pages<br/>in your browser"]

There’s one ingest service. It holds a single streaming connection to PSK Reporter and reads spots off it as they arrive. For each spot it checks the fields, upper-cases the callsigns, works out t_tx, builds the topics and publishes the spot to each tree it qualifies for. One spot becomes four or five MQTT messages.

The same service fetches PSK Reporter’s software list and active receiver list every hour. Changes to a receiver are published to pskr/rxinfo when they’re seen, and every receiver is republished once a day. When a receiver drops off PSK Reporter’s list its retained message is cleared. Each of those messages also expires after 7 days, in case a clear is missed.

WSPR has no live stream to subscribe to, so the ingest service collects WSPR spots from wsprnet.org, wsprnet.eu, wspr.live and the WSPR spots already on the PSK Reporter stream, and keeps whichever copy of each decode arrives first. WSPR spots don’t come with DXCC entities, so those are worked out from the callsigns using the Big CTY country file.

flowchart TD
    I["Ingest service"] -->|publishes| H["Central broker"]
    H -->|bridge| A["Fan-out broker A"]
    H -->|bridge| B["Fan-out broker B"]
    A --> C["Clients of<br/>mqtt.pskreporter.info"]
    B --> C

The ingest service publishes to a central broker that no public client connects to. Two fan-out brokers, on separate machines, each connect to the central broker over an MQTT bridge, subscribe to the public topic trees and republish everything locally. The name mqtt.pskreporter.info points at both, so clients are split between them. The central broker only ever has a handful of subscribers, and the fan-out brokers do the work of delivering to thousands of clients. Adding capacity means adding another fan-out broker and pointing DNS at it; nothing changes on the central machine.

The website is static HTML and JavaScript. The pages connect to the broker from your browser over WebSockets and build their maps and tables from the feed itself, mostly from the 1% sample. Besides the landing page there are views for a station, a Maidenhead field, a DXCC entity and a band, plus a propagation radar centred on one station.

Everything is QoS 0 from end to end: the ingest service publishes at QoS 0, the bridges carry QoS 0, and clients receive QoS 0. Nothing is retransmitted, so a dropped message is gone rather than late. The ingest service and the central broker count any messages they have to drop, and a small watcher on each broker looks for gaps in the sequence numbers, so loss shows up somewhere I can see it.

Scale and limits

These are rough figures from the second half of 2026.

  • About 300 PSK Reporter spots a second come in, more in busy minutes. That is around 2,000 MQTT messages a second, or 28 GB a day, on the pskr trees alone.
  • About 2,500 clients are connected at once, split roughly evenly between the two fan-out brokers.
  • Together the fan-out brokers send about 100,000 messages a second, around 16 MB/s.
  • Several thousand receivers are listed on pskr/rxinfo at any time.

If your client stops reading and about 1 MB of messages queues up for it, new messages are dropped until it catches up. You aren’t disconnected, so a slow client sees a delay and then a gap. Subscribe to only what you need: a filter for one callsign or one band is a few messages a second, while pskr/filter/v2raw/# is the whole firehose.

A gap in PSK Reporter’s own stream, for instance when the ingest service has to reconnect, is not filled in afterwards. The spots from those seconds don’t appear on the feed.