Wiki SDR SA PRO
Webhook, MQTT and local REST
PRO The Scanner hands its alarms to other programs in three ways: it sends every alarm as an HTTP request to your address (a webhook), publishes it to your MQTT broker, and shows its state through a read-only local REST server on this very computer. All of this is off by default, and the app connects only to addresses you entered yourself. This page covers where the switches are, what exactly goes over the network, and the limits and refusals. The alarms themselves are described on the page Frequency table and alarms, the other alarm actions on the page Alarm zones and alarm actions.
What goes where 0.9.58
- Webhook
- The Webhook (HTTP POST) box and the address field are in the Alarm actions fold of the Frequency table: N list. Every new alarm sends a POST request with JSON to that address. See below.
- MQTT
- The MQTT block in the How far to trust the list fold of the Signals: N list: the Publish alarms box, the broker, topic, user name and password. Every alarm becomes a message in a topic of the broker. See below.
- Local REST
- The Local REST block in the same fold: the Serve the state over HTTP box and the port. This server sends nothing by itself — it only answers requests from programs on this computer. See below.
- Everything is off by default. The boxes, addresses, port and topic are saved between runs in the Scanner’s
settings.jsonfile (see What is saved between runs). A webhook and MQTT left on at exit work again after the next start; the REST server does not: after every start it stays down until you check its box again. - The app itself does not go on the Internet. SDR SA PRO connects over the network only where you set it up: to a Pluto receiver, to the webhook address and to the MQTT broker. See also The network.
- Only the alarm goes over the network. The webhook, MQTT and the
/alarmspath of the local REST carry it as the same JSON (below): frequency, width, level, time, reason and the class from the frequency table. - The air only. A recording from a file and the demo signal give no alarms, so there is nothing to send.
- Each station has its own. When the window holds several stations, each has its own Scanner with its own settings, and so its own webhook, its own MQTT connection and its own REST server. The alarm body does not say which station it came from. Two stations need different REST ports — a server will not start on a busy port — and preferably different MQTT topics, or the online/offline state of both lands in one topic. Two stations on one broker have not been tested. See The Scanner on several stations.
The alarm body
An alarm is a JSON object in UTF-8. Its fields are the same as in the event log, only as numbers:
| Field | What it holds |
|---|---|
event | alarm; for “Test” — test |
program | SDR-SA-PRO |
time_utc | The time of the measurement in which the row first fell under the rule, UTC to the second: 2026-11-03T10:40:00Z |
frequency_hz | The centre of the row, Hz |
f_lo_hz, f_hi_hz | The lower and upper edge of the row, Hz |
width_hz | The width of the row, Hz |
level_db | The level in dB above the local floor, one decimal place; null — no level |
reason | The reason: guard, watch, kind or video |
name, kind | The class by the frequency table: the name and type of the range; empty — the signal lies in no range |
number | The serial number of the alarm since the list started, from one |
channel | The channel number; 0 — the row is not a channel |
video | PAL or NTSC when the video channel search confirmed video; empty otherwise |
The body has no what field of the event log (a signal or an activity range).
Webhook 0.9.58
On the Scanner tab go to the Frequency table: N list, unfold Alarm actions and check Webhook (HTTP POST); type the address into the field next to it, for example http://192.168.1.20:8080/alarm. While the box is checked, a hint below it says what exactly goes where.
- What is sent. Every new alarm sends a POST request with the alarm body to this address and nowhere else. The headers are
Content-Type: application/json; charset=utf-8andUser-Agent: SDR-SA-PRO. - No more than 8 per look. The other alarms of the same look are not sent but counted as skipped: webhooks skipped: N (cap 8 per look).
- In the background. The reply is awaited for up to 5 s, and the Scanner’s look does not wait for it. Success is a 2xx reply; any other code is the refusal HTTP N; no reply in time is timed out (5 s).
- No redirects and no retries. A 3xx reply is a refusal with its code, for example HTTP 302: the data never goes to an address other than the one you typed. A request that failed is not repeated.
http://andhttps://.http://travels over your network as plain text;https://has its certificate checked through Windows. Any other address is refused with the webhook address must start with http:// or https://, an empty one with the webhook address is not set.
Test and counters
The Test button of the same fold is active while the voice, the siren or the webhook is on. With the webhook on, it sends the address an event without an alarm — just three fields: {"event": "test", "program": "SDR-SA-PRO", "time_utc": "…"}.
What came of it is stated by the line below the action switches (see What came of the actions):
- webhook: N — how many requests got a 2xx reply since the program started, tests included; when something failed or was skipped, (N failed, N skipped) is added;
- in Last — webhook: N on the way or webhook: test on the way;
- the latest refusal — at the end of the line, for example webhook: HTTP 500.
While the webhook is on, the Actions line in the “How far to trust the list” fold names it among the actions that are on.
MQTT 0.9.58
The MQTT block sits in the How far to trust the list fold of the Signals: N list, right after the “Local REST” block.
- Publish alarms
- Turns the connection to the broker on. Off by default. If the box was checked at exit, the app connects again as soon as it starts.
- broker
host,host:port,mqtt://host[:port]— TCP, port 1883 by default — ormqtts://host[:port]— TLS, 8883 by default, the broker’s certificate checked by Windows. An address with a path, another scheme or a port outside 1…65535 is not taken: the box is unchecked, and the line below the block says why.- topic
- The root of the topics,
sdrsaby default. An empty topic or one with+or#is not accepted. - user and password
- As the broker requires them; empty — none. The password field shows dots.
Every change of a field reconnects.
What is published
- Every new alarm goes to the topic
<topic>/alarmwith the same body as the webhook, at QoS 0, not retained. - The program’s state goes to the topic
<topic>/status:onlineright after connecting andofflinebefore exit or when the box is unchecked; both are retained messages.offlineis also set as the will: when the connection breaks without warning, the broker publishes it by itself. - There are no subscriptions: the app reads nothing from the broker except the protocol’s own replies, and takes no commands over MQTT.
- The client is the app’s own, MQTT 3.1.1, with no third-party libraries. The client identifier is
sdrsa-pro-and five digits; every session is clean.
The link and retries
- A connection attempt waits up to 5 s. If it fails, the next attempt comes after 1, 2, 5, 10, 30 s, then every minute.
- When the broker itself refuses, the pause is longer: 10 s, then 30 s, then every minute.
- When nothing has been sent for 30 s, the client asks the broker whether it is still there; with no answer within another 30 s the connection counts as lost, and a second later the client connects again.
- While there is no link, alarms wait in a queue of up to 500 messages; when the queue is full, the oldest is dropped and counted.
The line below the block shows the state: connected, with sent N, queued N, dropped N; connecting to mqtt://…; a failed connection or a broker refusal with the pause before the next attempt; or a lost link. While the box is unchecked, the line holds a short hint about the client.
In build 0.9.58 the words about the connection, the refusals of the broker and the refusals of the broker address and topic come in Ukrainian even with the English interface. The table below gives their meaning.
The app names a broker’s refusal in the protocol’s terms:
| Refusal code | What it means |
|---|---|
| 1 | the broker does not accept this protocol version |
| 2 | the broker rejected the client identifier |
| 3 | the broker service is unavailable |
| 4 | wrong user name or password |
| 5 | not authorised |
| other | the refusal code itself |
The app keeps the broker’s user name and password in the Scanner’s settings.json file as plain text — just as you typed them. Anyone who can read that file can read the password too. Give the app its own user on the broker, allowed only to publish to its topic.
Local REST 0.9.58
The Local REST block is in the How far to trust the list fold, before the “MQTT” block. The Serve the state over HTTP box brings the server up, the port field sets its port: from 1024 to 65535, 8765 by default. Next to it stands the address, for example http://127.0.0.1:8765/.
- This computer only. The server listens on
127.0.0.1only, and that address cannot be changed: the receiver’s state cannot become visible on the network because of one checkbox. Requests come only from programs on this same computer. - Read only. The server answers
GETandHEAD;POST,PUT,DELETEandPATCHget code 405 with the headerAllow: GET, HEAD. It takes no commands, changes nothing in the app and connects nowhere by itself. - The box tells the truth. The server does not share a port another program already listens on: the box is unchecked, and the line below the block says could not open port 8765: …. A number outside the limits gives the port must be from 1024 to 65535. A new port, while the server runs, moves it there at once.
- After the program starts, the server is down. The port is saved, but the server comes up only when you check the box.
- While the server runs, the line below the block says running at http://127.0.0.1:8765/; requests: N.
Paths
Replies are JSON in UTF-8 with the header Cache-Control: no-store. The state is updated on every change of it, the list and the alarms after every look.
| Path | What it returns |
|---|---|
/ | The index: program, read_only (true), endpoints — the list of paths, events_query — how to ask for the log. |
/state | The Scanner’s state: on — whether the search is on; state — the state line in words, as on the tab; why — its code (empty — running; stopped, paused, tx, passport, power, eof, closed, failed); mode — sweep or tuned; driver; f_lo_hz, f_hi_hz, df_hz — the edges and step of the measurement; looks — spectra per cell; rows, tentative, alarms_unacked; method and method_used; video_search; sweep_bands_hz — the sweep bands; events_log — whether the event log is being written; time_utc; requests — how many requests since the program started. |
/list | rows and count. A list row: rowId — the row’s steady number; centerHz, widthHz; levelDb (dB above the floor, null — none), levelP90Db (the 90th percentile of the hit levels), levelN; firstMs, lastMs — the time of the first and the latest hit, milliseconds UTC since 1970; hits; state (1 — there, 2 — gone); className, classKind; activity — an activity range; alarm (0 — none, 1 guard, 2 watch, 3 type, 4 video) and alarmNo; channel; video (0 — not checked yet, 1 PAL, 2 NTSC, 3 not video, 4 sync without a field, 5 not checked). |
/alarms | alarms — the last 200 alarms since the program started, oldest first, with the same body; count; kept (200). |
/days | days — the days for which there is an event-log file, newest first. |
/events?day=YYYY-MM-DD | The event log of a day: day, rows (up to 5000), total — rows in the file, skipped — rows not read, error — what failed, in words; empty — all is well. Without a valid day — code 400. |
An unknown path gets code 404 and {"error": "unknown path; see /"}.
Limits
- The webhook and MQTT speak only of alarms. The Scanner’s list and state are visible only through the local REST.
- Webhook — up to 8 requests per look, no retries: a request that did not get through is lost, and the counter says so.
- MQTT — QoS 0 only: the broker does not confirm delivery; while there is no link, up to 500 messages are kept in the queue.
- The alarm body does not say which station it came from.
- Local REST —
127.0.0.1only, and read only. - The MQTT password lies in
settings.jsonas plain text. - In build 0.9.58 the MQTT state line and the broker’s refusals come in Ukrainian even with the English interface.
- The webhook has not yet been tried against a live server, MQTT against a live broker, or the REST with third-party client programs; a
mqtts://connection has not been tried at all.