Skip to content

Activity Publishing

Vexillum can publish a live activity feed from your reflector. The feed says which instances are running, who is connected, and which calls start and end. It can go to two kinds of destination:

  • the VexDV public directory, which lists reflectors and their live activity on activity.vexdv.io;
  • your own MQTT 5 broker, if you want the feed for your own dashboards, bots, logging, or anything else, with or without the public directory.

You can use either, both, or neither.

The feed has two kinds of message:

  • Events, as they happen: an instance starting or stopping, a client or peer connecting or disconnecting, a call starting or ending.
  • State documents, retained on the broker and re-sent about once a minute. They give the complete current picture, so a newcomer doesn’t have to replay history.

It covers:

  • your installation’s name, status (online or offline), software version, and running instances;
  • for each instance: its state, connected callsigns, active calls, and bridges. Only designated bridges are reported as bridges, and their calls are marked with the bridge’s label. When a bridge relays a call between two instances on this server, the relayed call also names the call it came from and the first call of the chain, so every leg of one transmission can be grouped. An undesignated bridge looks like an ordinary client;
  • each instance’s descriptor, meaning how to connect to it. This includes its display name, description, protocol, modules or talkgroup, public endpoints, and whether a VAFM passphrase is required. It comes from settings you already have: the reflector callsign (M17, D-Star), YSF room name and description, DMR name and talkgroup, the NXDN or P25 talkgroup (shown as a name like TG 31665), and each mode’s public endpoint.

Never published, anywhere: client IP addresses or ports, listen or bind addresses, passwords and passphrases, file paths, internal error messages, and DMR GPS position or talker alias.

Each destination publishes one of two profiles:

Private profile Public profile
Used by your own brokers (the default) the VexDV public directory, and any of your own brokers you set to profile = "public"
Instances all of them only instances you advertise (see below)
Endpoints all configured public endpoints endpoints with private, loopback, or link-local addresses removed
Extra detail yes (for example transport, repeater and software IDs, per-call source IDs, frame counts) no

Every Vexillum installation that publishes has a permanent installation ID, a UUID. It identifies your reflector in every feed, and in the public directory it is what your publishing certificate is issued for.

It is created the first time you enable any destination, and stored in Vexillum’s database. To see it:

vexillum publisher id -config /etc/vexillum/vexillum.toml

Before enabling the public directory, decide which instances should appear in it. In the admin interface, open Publishing:

  • Public Advertising lists every instance with an Advertised switch. Everything is off by default, and only advertised instances reach the public directory.
  • The Public Endpoints column shows exactly which connection addresses would be published for each instance. Endpoints with private, loopback, or link-local addresses are withheld automatically and shown as withheld.

Set each instance’s public endpoint in its configuration (on the instance’s page in the admin interface). This is the address users should connect to, as a hostname or public IP and port:

Mode Setting
M17, DMR, YSF, NXDN, P25 public_endpoint
D-Star (DExtra) dextra_public_endpoint
VAFM udp_public_endpoint, tcp_public_endpoint, tls_public_endpoint, dtls_public_endpoint, ws_public_endpoint, wss_public_endpoint (one per transport you run)

An instance without a public endpoint can still be advertised. It then appears with its activity but no connection details.

The public directory authenticates each reflector with its own TLS client certificate. Vexillum generates the private key on your server, and the key never leaves it. To get the first certificate, you file an enrollment request from your server and confirm your email address; a directory administrator checks your callsign and approves it, and Vexillum then installs the certificate by itself. After that, Vexillum renews the certificate on its own.

Add to vexillum.toml, then restart Vexillum:

[activity.public]
enabled = true
display_name = "My Reflector"   # optional: your installation's name in the directory

display_name is how your installation is labelled in the directory; it can be up to 64 characters. Without it, the directory uses runtime.name. Set it if your runtime name is something you’d rather keep internal.

Vexillum finds the directory’s servers by itself. Until you enroll, it waits and says so, both in the log and on the Publishing page:

the public service requires enrollment: run "vexillum publisher request" (installation 63fa6e4e-…)

Run this as the account Vexillum runs as, so the service can read what it writes. Use your own callsign and an email address you can read now:

sudo -u vexillum vexillum publisher request -config /etc/vexillum/vexillum.toml \
  -callsign N0CALL -email [email protected]
Filed enrollment request enrq_Tq3v0zS8H1cD6aKXfUJp2m9L for installation 63fa6e4e-cb9e-43ea-b613-ec16e438c24d.
  call sign:        N0CALL
  email:            [email protected]
  key fingerprint:  F3B2 980E 0225 90A3
A verification code is on its way to [email protected] (valid until 2026-09-28T00:41:35Z). When it arrives, run:

  vexillum publisher verify -code <code>

The email shows the same key fingerprint; if it doesn't match, don't verify.

The directory administrators use the callsign and email to check that the request comes from a licensed amateur, typically against your QRZ or HamQTH listing. Using the address shown there makes approval quicker.

The email, from [email protected], has an 8-character verification code, valid for 24 hours. Check that its key fingerprint matches the one request printed, then run:

sudo -u vexillum vexillum publisher verify -config /etc/vexillum/vexillum.toml -code 4F7K-9QX2
Email verified for enrollment request enrq_Tq3v0zS8H1cD6aKXfUJp2m9L.
A VexDV administrator will review it (it lapses if not decided by 2026-10-27T00:43:26Z), and you'll get an email with the decision.
While Vexillum runs, it installs the certificate by itself within about 10 minutes of approval. "vexillum publisher status" shows progress.

The code is typed as shown, but case, spaces and hyphens don’t matter. It only works on the server that filed the request. Five wrong codes cancel the request; file a new one.

There’s nothing more to do. An administrator reviews the request, usually within a day or two, and emails you the decision. Once it is approved, Vexillum (checking about every 10 minutes) installs the certificate and connects; the public row on the Publishing page turns healthy, and your advertised instances appear on activity.vexdv.io. Until then, the Publishing page shows where the request stands.

To see it yourself, or to install an approved certificate straight away:

sudo -u vexillum vexillum publisher status -config /etc/vexillum/vexillum.toml

If the request is rejected, the email and publisher status give the reason. Correct the details and run publisher request again.

The directory administrators can also give you a one-time enrollment code, for example if you can’t receive email at the address you’d like to use. Email your installation ID (vexillum publisher id), callsign and reflector name to , then:

sudo -u vexillum vexillum publisher enroll -config /etc/vexillum/vexillum.toml

Paste the code when prompted and press Enter. You can also pipe it in, or use -code-file with a file only you can read. The command never accepts the code as an argument, so it doesn’t end up in your shell history or the process list. Vexillum never stores it.

Certificates are valid for 90 days. While Vexillum runs, it renews automatically about 30 days before expiry (with some random spread), then switches to the new certificate without dropping out. There’s nothing to schedule.

To renew immediately, for example before a long maintenance window:

sudo -u vexillum vexillum publisher renew -config /etc/vexillum/vexillum.toml

If Vexillum is switched off for so long that the certificate expires, renewal is no longer possible. Enroll again with publisher request -replace (see below).

By default the credential is stored in a publisher directory next to Vexillum’s database. To keep it elsewhere, such as /var/lib/vexillum/publisher, set:

[activity.public]
enabled = true
credentials_dir = "/var/lib/vexillum/publisher"
  • Only the Vexillum service account should be able to read it. Vexillum creates it with mode 0700, and the private key 0600. It refuses to use a key that other users can read.
  • Back it up with the database. The credential belongs to that database’s installation ID. It’s no use to any other installation.
  • Don’t run two servers with the same credential. The directory allows one connection per installation, so two copies keep disconnecting each other.

Old credentials are kept for a week after they expire, then cleaned up automatically.

  • To stop publishing an instance, switch its Advertised toggle off.
  • To stop publishing altogether, set [activity.public] enabled = false and restart. The directory then shows your reflector as offline.
  • To leave the directory for good, ask the directory administrators to remove your installation.

If your certificate is lost, has expired, or has been revoked (for example after a server compromise), Vexillum logs that the broker refused it, and renewal fails with credential_revoked or credential_expired. Enroll again:

sudo -u vexillum vexillum publisher request -config /etc/vexillum/vexillum.toml -replace \
  -callsign N0CALL -email [email protected]

then verify the emailed code as in step 3. A running Vexillum installs the new certificate once it is approved and reconnects on its own.

To send the feed somewhere of your own, add one [[activity.mqtt]] block per broker. Any MQTT 5 broker works (Mosquitto, EMQX, HiveMQ, and others).

[[activity.mqtt]]
name = "home"                          # shown on the Publishing page and in logs
enabled = true
url = "mqtts://broker.example.net:8883" # mqtt:// or tcp://, mqtts:// or tls://, ws://, wss://
profile = "private"                    # "private" (default) or "public"
topic_prefix = "vexillum"              # default
username = "vexillum"
password_file = "/etc/vexillum/mqtt-password"   # or password = "..."
# ca_file = "/etc/vexillum/broker-ca.pem"       # broker certificate from a private CA
# cert_file / key_file                          # to authenticate with a client certificate
# queue_max_items = 10000                       # backlog kept while the broker is unreachable
# queue_max_age = "15m"

Restart Vexillum after changing it. Brokers are independent: one being down doesn’t affect the others or the public directory. While a broker is unreachable, messages queue, bounded by queue_max_items and queue_max_age. The latest state is re-sent in full when it comes back.

For a prefix of vexillum, everything is under vexillum/v1/<installation_id>/:

Topic Contents Retained
…/events every event, as it happens no
…/state installation state: status, version, list of instances yes
…/state/<mode>.<instance> one instance’s state, e.g. …/state/m17.main yes
…/status online / offline. Also the MQTT last will, so it reads offline if Vexillum disappears yes

All messages are JSON, with QoS 1. Each carries a schema version, which is 1. New fields can appear in later releases, so consumers should ignore fields they don’t recognize. When an instance is deleted, or stops being published, its retained state is cleared with an empty message.

Vexillum only publishes; it never subscribes. It needs publish permission on <prefix>/v1/<installation_id>/# and nothing else. For example, in Mosquitto:

# /etc/mosquitto/acl
user vexillum
topic write vexillum/v1/63fa6e4e-cb9e-43ea-b613-ec16e438c24d/#

Your consumers, such as a dashboard, a bot, or a logger, subscribe to vexillum/v1/+/#, or to a single installation’s tree.

Leave profile = "private" for your own tools: you get every instance and all the detail. Use profile = "public" for a broker whose feed you share with others. It then carries exactly what the public directory would get, and only for advertised instances.

To watch activity without any broker, set:

[activity]
log_events = true

Every event is then written to Vexillum’s log.

The Publishing page shows one row per destination. The public directory appears as public. Each row shows:

  • Status: healthy, or failing;
  • Queued, Sent, Errors, Rejected, Dropped: the delivery counters;
  • Last Error: the most recent problem, and when it happened.

The same information goes to Vexillum’s log.

You see Meaning What to do
the public service requires enrollment Public directory enabled, not enrolled yet Request enrollment
enrollment request … is waiting for email verification The request was filed but not verified Run publisher verify with the emailed code; check spam; or file a new request
enrollment request … is waiting for approval Verified; an administrator hasn’t decided yet Wait for the decision email
enrollment request … was rejected: … An administrator turned it down, for the reason given Correct the details and file a new request
enrollment request … is expired, or verification failed The code lapsed after 24 hours, the decision after 30 days, or five wrong codes were entered File a new request
HTTP 409, request_in_review This installation already has a request waiting for a decision Wait for it; to change its details, ask the administrators to reject it
HTTP 401, invalid_verification_code The code was mistyped (the message says how many tries remain) Copy it again from the newest email
HTTP 400, bad_request: callsign … or email … The callsign or email address isn’t valid Use your plain callsign (portable forms like VE3/N0CALL are fine) and a plain address
the service does not take enrollment requests The directory only accepts enrollment codes Enroll with a code
publisher credential … is unusable: … is accessible to other users The private key’s permissions were loosened chmod 600 the key, or re-enroll
publisher credential … is unusable: certificate is for "…", not this installation The credential belongs to a different database’s installation ID Restore the matching database, or re-enroll
certificate expired … re-enroll Vexillum was off past the certificate’s expiry publisher request -replace
remote error: tls: revoked certificate The directory has revoked this credential publisher request -replace
refused by the enrollment service (HTTP 401, invalid_enrollment) The enrollment code is wrong, already used, expired, or for another installation ID Check publisher id; ask for a new code, or use publisher request
already enrolled A working credential is already installed Nothing to do, or -replace to enroll again on purpose
A private broker shows failing The broker is unreachable, or refused the login Check url, credentials, TLS settings, and the broker’s log
Rejected keeps rising The broker refuses messages, often a missing publish permission Check the broker’s ACL for <prefix>/v1/<installation_id>/#
An instance is missing from the public directory It isn’t advertised Switch Advertised on under Publishing
An endpoint shows as withheld It is a private, loopback, or link-local address Set a public hostname or IP as the instance’s public endpoint

Activity publishing is new. The feed format is versioned (v1 in topics, schema: 1 in messages), and later releases only add fields within a version. Check the release notes when upgrading.