Events API
The control server pushes live updates over a WebSocket channel: every configuration change made through the API, the per-second status of streams, adapters and softcams, system alerts and license state changes. The built-in web interface keeps its views current with nothing else, so a custom interface can do the same: load the state once with the HTTP methods, then apply the messages of this channel. The channel is one-directional apart from a liveness probe: the server never accepts commands over it.
Connect
Section titled “Connect”Request: GET {control_path}control/event/?token=<token> with the header Upgrade: websocket
token- string, required to receive events. A one-time token issued by ws-token to the authenticated user; it is valid for 60 seconds and is consumed by the first handshake that presents it.
The credential is the token alone: a session cookie or an Authorization header on the handshake is ignored. The token is bound to the user who requested it, and both roles receive the same messages - an observer’s token admits the connection to every event listed on this page.
The handshake answers:
101- the connection is upgraded. With a valid token it is admitted into the broadcast set and receives every message below from that moment on. Without atokenparameter the connection is a liveness-only session: it is upgraded, answersping, and never receives an event.400- theUpgradeheader is missing or is notwebsocket.403- the token is unknown, expired, already used, or its user has been disabled or removed since the token was issued. No session is opened.
Sec-WebSocket-Key is optional: when present the reply carries Sec-WebSocket-Accept, when absent the 101 goes out without it. Connection, Sec-WebSocket-Version and subprotocols are not checked. The scheme is ws://, or wss:// when the control server serves TLS (crt_chain and crt_key in the settings). With the --no-web-auth command line option every handshake is admitted without a token.
Nothing is replayed: a message pushed before the handshake completed is not delivered to the new connection, so a client loads the current state with the HTTP methods after every connect and reconnect. Every reconnect needs a fresh token.
Example, in a browser with a login session:
const { token } = await fetch("/control/", { method: "POST", body: JSON.stringify({ cmd: "ws-token" }),}).then(r => r.json());const ws = new WebSocket(`ws://server:8000/control/event/?token=${encodeURIComponent(token)}`);ws.onmessage = (e) => { const msg = JSON.parse(e.data); console.log(msg.scope, msg);};Message envelope
Section titled “Message envelope”Every server message is one text frame with a JSON object. The scope field names the message type; the other fields depend on the scope:
{ "scope": "set-stream", "stream": { "id": "a001", "name": "...", "type": "spts", "enable": true, "input": [ "..." ] }}The one text frame without JSON is pong, the answer to the client’s ping. Messages are delivered in the order they were produced. A field that is unset in the configuration is absent from the message, never null, so a client checks for presence.
Client messages
Section titled “Client messages”The server accepts exactly one client message:
ping- text frame. Answered with the text framepongon the same connection. A liveness-only session (no token) gets the same answer.
A WebSocket ping control frame (opcode 9) is answered with a pong control frame carrying the same payload; it is not a client message and never closes the connection. Any other text frame closes the connection with code 1008. A binary frame, or a message larger than 256 KiB, ends the connection without a close code; a protocol violation such as an unmasked frame is answered with close code 1002.
The server sends no WebSocket ping frames of its own. A client that wants to detect a dead connection sends ping periodically and treats a late pong as a failure. Astra’s own peer monitor (the ws_monitor module, used by a fallback instance to watch its master) sends ping every second, expects pong within 3 seconds and reconnects one second after a failure.
Close semantics
Section titled “Close semantics”1001- the server ends the session: the control server is shutting down (process exit, restart or configuration reload), or the client is too slow. A slow client is one whose unsent backlog exceeds 1 MiB; it is disconnected with the log lineWebSocket client buffer full, slow client, disconnecting, and the messages that did not fit are lost.1002- a protocol violation in a client frame.1008- a client message other thanping.- A close frame from the client is not answered; the server drops the connection.
A connection stays open when its user is disabled, removed, or changes the password: those actions revoke the user’s login sessions and streaming sessions, not open WebSocket sessions. The next handshake of that user is refused with 403 at the token check.
Configuration messages
Section titled “Configuration messages”Each configuration command that changes the configuration pushes a message with the resulting object, after the change is applied and before the configuration is saved. A client that issued the command receives the message too. Messages that say "remove": true carry only the identifier.
set-stream
Section titled “set-stream”Pushed by set-stream, toggle-stream, and by set-adapter, set-softcam and set-category when a stream had to be changed because an adapter, a softcam or a category it referenced was renamed or removed.
{ "scope": "set-stream", "gid": 466561, "stream": { "id": "a001", "name": "...", "type": "spts", "enable": true, "input": [ "..." ] }}stream- the stream configuration as stored: the object from the request forset-stream, the stored object withenableflipped fortoggle-stream, the rewritten stored object when an input address changed (dvb://<adapter id>,cam=<softcam id>) or a group assignment was dropped. A stream discovered by thescanlist ofset-adapteris pushed the same way. Before pushing,set-streamadds the table version counters to the object:pmt_versionandsdt_versionfor an SPTS stream (1when the request had none, otherwise the request’s value plus one modulo 32),pat_version,cat_version,sdt_versionandnit_versionfor an MPTS stream (0when absent, otherwise plus one modulo 32).gid- number, present only in the message ofset-streamand only when the configuration holds agid. The object id counter the web interface keeps in the configuration, updated from the request’sgidwhen it carried one: a new id is this counter in base 36, and the counter is echoed so every connected interface continues from the same value.
Removal or replacement:
{ "scope": "set-stream", "stream": { "id": "a001", "remove": true, "up": false }}remove-true, the stream with thisidis no longer in the configuration.up- present when pushed byset-stream:truewhen the stream is being replaced and aset-streammessage with the new object follows,falsewhen it is removed. Absent when the removal is a side effect ofset-adapter(a stream whose only input was the removed adapter).
set-adapter
Section titled “set-adapter”Pushed by set-adapter and toggle-adapter.
{ "scope": "set-adapter", "gid": 466561, "adapter": { "id": "a002", "name": "...", "enable": true, "adapter": 0 }}adapter- the adapter configuration as stored; fortoggle-adapterthe stored object withenableflipped.gid- number, present only in the message ofset-adapterand only when the configuration holds agid(seeset-stream).
Removal or replacement: {"scope": "set-adapter", "adapter": {"id": "a002", "remove": true, "up": false}}. up is true when a message with the replacement follows, false when the adapter is removed. The dependent streams are pushed as set-stream messages after this one.
set-softcam
Section titled “set-softcam”Pushed by set-softcam.
{ "scope": "set-softcam", "gid": 466561, "softcam": { "id": "a003", "name": "...", "type": "newcamd" }}softcam- the softcam configuration as stored.gid- number, present when the configuration holds agid(seeset-stream).
Removal or replacement: {"scope": "set-softcam", "softcam": {"id": "a003", "remove": true, "up": false}}, with up as for set-adapter. Streams whose inputs referenced the softcam follow as set-stream messages.
set-cas
Section titled “set-cas”Pushed by set-cas.
{ "scope": "set-cas", "gid": 466561, "cas": { "id": "a004", "name": "..." }}cas- the CAS configuration as stored.gid- number, present when the configuration holds agid(seeset-stream).
Removal or replacement: {"scope": "set-cas", "cas": {"id": "a004", "remove": true, "up": false}}, with up as for set-adapter.
set-server
Section titled “set-server”Pushed by set-server.
{ "scope": "set-server", "id": 0, "server": { ... }}id- number, the zero-based position of the server in theserverslist of the configuration; absent when a new server was appended.server- the server entry as stored;{"remove": true}when the entry atidwas removed.
set-user
Section titled “set-user”Pushed by set-user (both the control command and the observer’s own-password variant) and toggle-user.
{ "scope": "set-user", "id": "login", "user": { "type": 1, "enable": true, "cipher": "...", "created": 1758240000 }}id- string, the login.user- the user entry as stored:type(1,2or3),enable,cipher(the password hash; the plainpasswordfrom the request is never pushed) and the other fields of the request.created(Unix time) is set when the request creates the user; on an existing userset-userkeeps onlycipherfrom the stored entry, socreatedis present only when the request repeats it. Fortoggle-userit is the stored entry withenableflipped.{"remove": true}when the user was removed.
set-category
Section titled “set-category”Pushed by the set-category command on the /control/ entry point.
{ "scope": "set-category", "id": 0, "category": { "name": "...", "groups": [ { "name": "..." } ] }}id- the zero-based index of the category the request addressed, as the request sent it (a number, or a string when the request sent a numeric string); absent when a new category was appended.category- the category as stored;groupsis always a list.{"remove": true}when the category was removed.
Streams whose group assignment in the renamed or removed category changed are pushed as set-stream messages before this one.
set-auth
Section titled “set-auth”Pushed by set-auth.
{ "scope": "set-auth", "auth": { "enable": true }}auth- the HTTP authentication settings as stored (theauthobject of the request, withhttp_ip_allowandhttp_ip_denysorted by prefix length, the longest prefix first). Absent when the request had noauthobject.
set-config
Section titled “set-config”Pushed by set-config.
{ "scope": "set-config", "key": "...", "data": { ... }}key- the configuration key that was set.data- the value stored under it; absent when the key was cleared.
set-settings
Section titled “set-settings”Pushed by set-settings. Astra restarts one second after the reply, so a client receives this message and then loses the connection.
{ "scope": "set-settings", "gid": 466561, "settings": { "control_path": "...", "http_server_name": "..." }}gid- the stored object id counter (seeset-stream).settings- the settings object as stored; absent when the request emptied it.
log_event
Section titled “log_event”Pushed by set-log.
{ "scope": "log_event", "set": { "api_access": true }}set- the log settings as stored.
post-output-group
Section titled “post-output-group”Pushed by POST /api/output-group[/{name}].
{ "scope": "post-output-group", "index": 0, "remove": false, "group": { "name": "..." }}index- number, the zero-based position of the group in theoutputlist; absent when a new group was appended.remove-truewhen the group atindexwas removed; absent otherwise.group- the group as stored; absent when the group was removed.
stream_image
Section titled “stream_image”Pushed by set-stream-image. Nothing is stored: the message relays a screenshot address to the connected interfaces.
{ "scope": "stream_image", "channel_id": "a001", "src": "http://..."}channel_id- the stream id from the request.src- theurlfield of the request.
Status messages
Section titled “Status messages”stream_event
Section titled “stream_event”Pushed once per second for every active input of every enabled stream that has an id, from the input’s analyzer, also while no data arrives (bitrate 0, onair false). Not pushed while the input is waiting for a restart (its watchdog fired).
{ "scope": "stream_event", "channel_id": "a001", "input_id": 1, "onair": true, "bitrate": 4210, "scrambled": false, "cc_error": 0, "pes_error": 0}channel_id- the stream id.input_id- the input identifier: the one-based position of the input in the stream’sinputlist as a number, unless the input address carries its ownidoption (#id=7), whose value is used instead as a string ("7").onair- boolean, the analyzer’s verdict for the last second:falsewhen the payload bitrate is under the expected minimum, a video or audio PID is scrambled or had more than 2 PES errors, or the CC error limit is reached.bitrate- number, Kbit/s over the last second.scrambled- boolean, a video or audio PID carried scrambled packets in the last second.cc_error- number, continuity counter errors in the last second.pes_error- number, PES errors in the last second.
For an MPTS stream the analyzer runs on the multiplex output, and the message carries no input_id; onair is always true, scrambled is false, and cc_error and pes_error are 0:
{ "scope": "stream_event", "channel_id": "m001", "onair": true, "bitrate": 38000, "scrambled": false, "cc_error": 0, "pes_error": 0}adapter_event
Section titled “adapter_event”Pushed once per second for every running adapter.
A DVB adapter:
{ "scope": "adapter_event", "dvb_id": "a002", "status": 31, "signal": 65535, "signal_db": -4520, "snr": 62000, "snr_db": 1380, "ber": 0, "unc": 0, "bitrate": 41000}dvb_id- the adapter id.status- number, the frontend status bits:0x01signal,0x02carrier,0x04FEC inner coding,0x08sync,0x10lock,0x20timed out,0x40reinitialized.0is pushed in the second in which the adapter was stopped by its watchdog (CC, PES, sync byte, receive timeout or PAT checksum check); while the adapter is restarting no message is pushed.signal,snr- numbers, the raw driver values.signal_db,snr_db- numbers, decibels multiplied by 100.ber,unc- numbers, bit errors and uncorrected blocks as reported by the driver.bitrate- number, Kbit/s received over the last second.
A virtual adapter (a SAT>IP, CI or address-based adapter) carries the analyzer verdict instead of frontend values:
{ "scope": "adapter_event", "dvb_id": "a006", "virtual": true, "satip": true, "bitrate": 41000, "on_air": true}virtual- alwaystruefor these adapters.satip-truefor a SAT>IP adapter, absent otherwise.bitrate- number, Kbit/s over the last second.on_air- boolean, the analyzer verdict as instream_event.
adapter_status_update
Section titled “adapter_status_update”Pushed when a DVB adapter is taken into use or released (an adapter is started, stopped, toggled, removed or reconfigured).
{ "scope": "adapter_status_update", "adapter": 0, "busy": true}adapter- number, the system adapter number (/dev/dvb/adapterN).busy- boolean,truewhile an adapter configuration uses it.
softcam_event
Section titled “softcam_event”Pushed for every newcamd softcam: once per second while it is connected and logged in, and once for every failed connection attempt. A softcam connects when the first stream that decrypts with it starts, or at once when its configuration has force set; an unused softcam pushes nothing.
{ "scope": "softcam_event", "softcam_id": "a003", "caid": 2304, "status": 3, "ecm_rate": 12, "emm_rate": 0}softcam_id- the softcam id.status- number:3- connected and logged in, the only state in which the softcam serves keys;0- a connection or protocol error (connection failed or timed out, response timed out, response not parsed, login rejected), pushed once per attempt withsoftcam_idas the only other field. No other value is pushed.caid- number, the CA system id reported by the server. Only withstatus3.ecm_rate,emm_rate- numbers, ECM and EMM messages over the last 60 seconds. Only withstatus3.
An error attempt:
{ "scope": "softcam_event", "softcam_id": "a003", "status": 0}sysinfo
Section titled “sysinfo”Pushed when the CPU power-save state changes, and repeated once a minute while power saving is active so an interface that connected later sees the alert. The message is not a periodic statistics feed: the values are a snapshot at the moment of the alert.
{ "scope": "sysinfo", "system": { "uptime": 864000, "cpu_cores": 8, "cpu_power_save": true, "cpu_total_usage": 12, "loadavg": [ 45, 40, 38 ], "mem_total": 16318452, "mem_available": 11201324 }, "app": { "uptime": 3600, "mem": 1, "cpu_usage": 3, "threads": 12 }}system.uptime- seconds since boot.system.cpu_cores- number of CPUs.system.cpu_power_save- boolean, the scaling policy holds the CPU below the clock it is capable of.system.cpu_total_usage- percent.system.loadavg- the 1, 5 and 15 minute load averages multiplied by 100.system.mem_total,system.mem_available- KiB.app.uptime- seconds since the process started.app.mem- percent of total memory used by the process.app.cpu_usage- percent.app.threads- thread count of the process.
license
Section titled “license”Pushed by the license engine when the license state changes: a license file could not be loaded, a check returned a different type, e-mail, expiry or cleared an error, or a license fault is reported.
{ "scope": "license", "license": { "id": "ABCDEF", "type": 2, "email": "user@example.com", "expire": 1790000000, "origin": "https://..." }}Every field of license is optional and absent when unknown:
id- the first six characters of the serial number.type-1demo,2subscription,3corporate,4lifetime.email- the e-mail the license is registered to.expire- Unix time; the expiration date, or the activation date for a lifetime license.error_code- number, the current license fault; absent while the license is valid.error_text- string, set when the license file could not be loaded.origin- the license server that answered the last request.offline_expire- Unix time at which the last answer stops holding while the license server is unreachable; absent while the server is reachable.