MQTT topics and message formats
This page lists the MQTT broker's connection parameters, how a device authenticates, and every topic a device can publish or subscribe to, with its payload. Use it when you write or debug the MQTT layer of your firmware.
Two supported paths. Your firmware can talk to the broker directly with any MQTT 5 client, following this page. Or it can use the Linux daemon or the MCU library, which implement this protocol for you, including key handling, heartbeats, actions and firmware updates. Some topics are configurable per registry: read them from the registry rather than hard-coding them.
Connection parameters
| Parameter | Value |
|---|---|
| Host | mqtts.fundamentum-iot.com |
| Port | 8883 (MQTT over TLS) |
| Protocol | MQTT 5 |
| Client certificate | Not required |
| Server Name Indication (SNI) | Required: send the broker hostname in the TLS handshake |
| Username | Not used. Set it to unused |
| Password | A device JWT, see Authentication |
| Client ID | Identifies the device, see Client ID |
Always connect to the host and port returned by provisioning (data.mqtt.host and data.mqtt.port), see Devices API v3.
Embedded TLS stacks must trust the root certificate authority of the broker's chain. Inspect the chain with:
openssl s_client -showcerts -verify 5 -connect mqtts.fundamentum-iot.com:8883
Authentication
The device signs a JWT with its private key, the one whose public key it registered at provisioning. The broker verifies it against that public key. No shared secret travels on the wire.
| Claim | Value |
|---|---|
aud |
The project ID |
iat |
Issue time (Unix seconds) |
exp |
Expiry time (Unix seconds) |
Sign with RS256. The broker refuses the connection if the signature is invalid, the token is expired, or aud does not match the project in the client ID.
Client ID
Long form:
projects/<project-id>/regions/<region-id>/registries/<registry-id>/devices/<serial-number>
Short form, for devices where length matters:
<project-id>.<region-id>.<registry-id>.<serial-number>
| Part | Description |
|---|---|
project-id |
Integer. Your project |
region-id |
Integer. 1 unless told otherwise |
registry-id |
Integer. The registry the device was provisioned into |
serial-number |
String. Unique in its registry. The broker lowercases it |
Topics
Every topic starts with the device's own prefix:
registries/<registry-id>/devices/<serial-number>/
A device may only publish and subscribe under its own prefix. There is no device-to-device traffic: your systems receive device data through connectors.
Device to Fundamentum
| Topic (after the prefix) | Payload | Purpose |
|---|---|---|
events |
Any format | Telemetry, sent to the project's default telemetry topic |
events/<name> |
Any format | Telemetry sent to the registry topic configured under <name> |
subdevices/<sub-serial-number>/events |
Any format | Telemetry on behalf of a sub-device. Only events is allowed under subdevices/ |
states |
Any format; Fundamentum JSON recommended | Device states and beacon readings |
heartbeat |
JSON, or protobuf with v: 1 |
Proof the device is alive |
commands/<device-command-id> |
JSON | Outcome of a command |
actions/<action-id>/response |
Protobuf | Outcome of an action |
pfwd/rx |
Binary | Reserved for the port-forwarding agent |
Fundamentum to device
| Topic (after the prefix) | Payload | QoS | Retained | Purpose |
|---|---|---|---|---|
config |
JSON | — | — | Device configuration, sent once when the device subscribes |
commands |
JSON array | 0 | Yes | Pending commands. Retained, so a device that reconnects receives them |
actions |
Protobuf | 2 | No | Actions to execute |
custom_actions |
Binary | 0 | No | Your own application messages |
custom_actions/<name> |
Binary | 0 | No | Your own application messages on a named channel |
pfwd/tx |
Binary | 2 | No | Reserved for the port-forwarding agent |
Messages on custom_actions are delivered only while the device is subscribed. Nothing is sent back to the cloud for them.
Payloads
Telemetry: events
Any bytes. Fundamentum passes telemetry through as is, so you choose your own format. Decode a proprietary format with a cloud function.
States: states
Any payload reaches the registry's states topic. When you use the Fundamentum JSON format, the platform also stores the states (history and latest values) and extracts beacon readings:
{
"timestamp": 1789167823,
"states": {
"battery_level": 87,
"temperature": 21.4
},
"beacons": [
{
"identification": "beacon-abc123",
"states": { "rssi": -67, "battery_level": 44 }
}
]
}
| Field | Description |
|---|---|
timestamp |
Unix time in seconds |
states |
Free-form object: any name, any value |
beacons[].identification |
Identifies the beacon, which is treated as a device of its own |
beacons[].states |
The beacon's states. rssi, when present, is recorded as a scan |
Heartbeat: heartbeat
The MQTT 5 user property v selects the format.
Without v, or with v: 0, send JSON with the seconds since boot:
{ "uptime": 86400 }
With v: 1, send a protobuf Heartbeat message. It can also carry the sub-devices a gateway speaks for, each with its serial number and uptime. See Gateways and sub-devices.
The protobuf schemas for heartbeats and actions are published in the fundamentum-iot-mqtt-proto crate on crates.io.
Commands: commands and commands/<device-command-id>
The device receives a JSON array of pending commands, each shaped like this:
{
"device_command_id": 1,
"command_short_name": "command-name",
"parameters": {}
}
command_short_name |
Parameters |
|---|---|
reboot |
device_id |
firmware-update |
target_device, and version with configuration_file_url (download URLs), configuration (free-form, passed through), identifier, name |
custom-configuration-update |
deep_update (apply the whole configuration when true, only the difference when false), retry_timeout (seconds), target_device.actual_configuration, target_device.new_configuration |
Example firmware update:
{
"device_command_id": 790,
"command_short_name": "firmware-update",
"parameters": {
"device_id": 296,
"command_id": 8,
"version_id": 29,
"target_device": { "serial_number": "<serial-number>", "id": 296 },
"version": {
"configuration_file_url": ["https://<download-url>"],
"configuration": {},
"identifier": "base-firmware-1-4-2",
"name": "base-firmware-version",
"id": 29
}
}
}
command_id and version.id are internal identifiers that the device can ignore.
Report progress on commands/<device-command-id>:
{ "status": "success", "message": "firmware updated to 1.4.2" }
| Field | Values |
|---|---|
status |
ongoing, success or failure |
message |
Free text describing the change of status |
Actions: actions and actions/<action-id>/response
Actions are protobuf messages. The device reports the outcome on actions/<action-id>/response, also in protobuf. Actions are defined per device type and translated for each firmware version by encoders. See Actions and custom encoders.
Configuration: config
The device's stored configuration, as JSON. It has no fixed shape: it is whatever you stored for the device, for example with POST .../configuration on the Devices API.