Communicating with Fostrom over MQTT
MQTT is a lightweight messaging protocol that is ideal for IoT applications. It is a publish/subscribe protocol that allows devices to send messages to each other. Fostrom supports MQTT 3.1.1 over TCP/TLS and Secure WebSockets and requires the device to send packets with specific parameters.
The device can send the payload in two serialization formats: JSON and MsgPack.
To use JSON, the CONNECT packet's client_id field must be set to JSON.
To use MsgPack, the CONNECT packet's client_id field must be set to MSGPACK.
Connecting to Fostrom
A TCP/TLS connection can be opened at device.fostrom.dev at port 8883. The MQTT client should then send a specifically crafted CONNECT packet.
The following URL can be used to connect from an MQTT client:
mqtts://device.fostrom.dev:8883
Note that Fostrom does not support any unencrypted connection. You must use TLS.
WebSockets
Fostrom also supports MQTT over Secure WebSockets. The URL for connecting to Fostrom over WebSockets is:
wss://device.fostrom.dev
The WebSockets connection should be opened with the mqtt protocol header:
Sec-WebSocket-Protocol: mqtt
The CONNECT Packet
The MQTT CONNECT packet carries the following fields, and they should be set accordingly:
-
client_id- The client ID usually exists to identify a device, but we do that through theusernamefield. Instead, set theclient_idto"MSGPACK"to use MsgPack as the serialization format. Otherwise, set it to"JSON". -
username- The username follows the following format:<fleet_id>::<device_id>. A Fleet ID is 8 characters long, and a Device ID is 10 characters long. They are separated by two colons::, essentially creating a 20-character string. -
password- The Device Secret is used as the password. The Device Secret starts withFOS-with 32 characters after it (totaling 36 characters). -
keep_alive- The keep alive interval in seconds. Ifkeep_aliveis less than 11 seconds, Fostrom assumes the MQTT connection should be terminated after sending a response packet. This is useful for devices that don't need a persistent connection to Fostrom. If you want a persistent connection, setkeep_aliveto 30 seconds or higher (preferably 30-60 seconds). -
clean_session- This flag is ignored by Fostrom. -
will- You cannot set a Will in the connect packet at this time. The connection will fail if awillis specified.
Once you send a connect packet, either a CONNACK (0) -> ACCEPTED approving the connection will be returned, or one of two errors will be returned and the connection will be closed:
-
CONNACK (3) -> SERVER UNAVAILABLE- This is a transient error, the device should retry connecting after a short delay. Devices should use an exponential backoff strategy when trying to reconnect in face of network or server errors. -
CONNACK (5) -> UNAUTHORIZED- This is a fatal error, retrying with the same credentials will not help. The device should not attempt to reconnect.
Once the device is connected, it can send subsequent packets to Fostrom.
Note that devices should use an exponential backoff strategy when trying to reconnect in face of network or server errors.
CONNECT Packet:
-> CONNECT
client_id: "MSGPACK" | "JSON"
username: "<fleet_id>::<device_id>" # total 20 characters
password: "<device_secret>" # starts with `FOS-`, 36 characters
keep_alive: 0 | 30 | 60
clean_session: true
will: nil
<- CONNACK
code: 0 | 3 | 5
# 0 = ACCEPTED
# 3 = SERVER UNAVAILABLE
# 5 = UNAUTHORIZED
Heartbeats
Fostrom shows whether a device is connected or not, based on heartbeats and open sockets.
MQTT has a PINGREQ packet that acts as a heartbeat. The Fostrom server responds with a PINGRESP packet. It is normal for devices to connect to Fostrom and send a PINGREQ packet, and then disconnect. This is useful for devices that don't need a persistent connection to Fostrom but to indicate that they are functioning normally.
-> PINGREQ
<- PINGRESP
There is also another way to send a heartbeat by publishing a packet. This exists because many MQTT clients do not support the user manually triggering a PINGREQ packet. If you need manual control over when the heartbeat should be sent, you can use the publish method, as documented below.
Publishing to Fostrom
Fostrom ignores the dup and retain flags in PUBLISH packets. It supports QoS 0 and QoS 1, however we highly recommend using QoS 1 for all packets. There is only one topic a device can publish to: fostrom. The client should send a packet id between 1 and 65535 for QoS 1 packets. Fostrom does not support QoS 2 at this time. The payload should be an object/map, and needs to be serialized with your configured serializer (MsgPack or JSON).
-> PUBLISH
topic: "fostrom"
qos: 0 | 1 # we recommend QoS 1
id: 1-65535 # only for QoS 1
payload: <msgpacked_payload | json_encoded_payload>
<- PUBACK (for QoS 1 only)
id: <packet_id>
Heartbeats through Publishing
To send a heartbeat by publishing a packet, the payload should be:
{
"type": "heartbeat"
}
Send a Datapoint
To send a datapoint, you need to send the following fields in the payload:
{
"type": "datapoint",
"name": "<packet-schema-name>",
"payload": {
...fields
}
}
Send a Message
To send a message, you need to send the following fields in the payload:
{
"type": "msg",
"name": "<packet-schema-name>",
"payload": {
...fields
}
}
Receiving Messages from Fostrom
Step 1: Subscribe to topic fostrom
MQTT mandates that a client should subscribe to a topic before a server can send a PUBLISH packet to that topic. The MQTT spec mandates that a client should terminate the connection if it receives a PUBLISH packet on a topic it has not subscribed to. Since many MQTT clients are written to adhere to the spec, Fostrom requires that a client subscribe to the topic fostrom before Fostrom can send messages to the device.
Once a device sends a SUBSCRIBE packet for topic fostrom, Fostrom will send a SUBACK confirming the subscription.
-> SUBSCRIBE
topic: "fostrom"
qos: 1
id: 1-65535
<- SUBACK
id: <packet_id>
code: 1 # 1 = SUCCESS with QoS 1
Step 2: Receive Push Notifications for New Mail
Once subscribed, whenever new mail arrives in the device's mailbox, Fostrom will automatically send a push notification to inform the device. This notification contains:
{
"new_mail": true
}
The device should send a PUBACK to acknowledge receipt of this notification. This notification is purely informational - the device can then decide when to fetch the actual mail content.
Step 3: Fetch Mail from Mailbox
To retrieve mail from the mailbox, the device must explicitly request it by publishing a fetch request:
{
"type": "next_mail"
}
Or, to fetch only the mail header without the payload (useful for checking mail metadata before downloading full content):
{
"type": "next_mail",
"include_payload": false
}
Fostrom will respond with a PUBLISH packet containing the mail details:
{
"type": "mail",
"request_txn_id": <txn_id_from_your_request>,
"mailbox_size": <current_mailbox_size>,
"mail": {
"id": "<mail_id>",
"name": "<packet-schema-name>",
"payload": {
...fields
}
}
}
Note
The mail_id is always a UUIDv7 string with dashes, exactly 36 characters long.
If requesting header-only (with include_payload: false), the response will not include the payload field:
{
"type": "mail",
"request_txn_id": <txn_id_from_your_request>,
"mailbox_size": <current_mailbox_size>,
"mail": {
"id": "<mail_id>",
"name": "<packet-schema-name>"
}
}
If the mailbox is empty, the response will be:
{
"type": "mail",
"request_txn_id": <txn_id_from_your_request>,
"mailbox_size": 0
}
Flow Summary
# Initial subscription
-> SUBSCRIBE
topic: "fostrom"
qos: 1
id: 1-65535
<- SUBACK
id: <packet_id>
code: 1
# When new mail arrives (push notification)
<- PUBLISH
topic: "fostrom"
qos: 1
id: 1-65535
payload: {"mailbox_size": 3, "id": "00000000-0000-7000-8000-000000000000"}
-> PUBACK
id: <packet_id>
# Device requests next mail
-> PUBLISH
topic: "fostrom"
qos: 1
id: 100
payload: {"type": "next_mail"}
<- PUBACK
id: 100
# Server sends mail content
<- PUBLISH
topic: "fostrom"
qos: 1
id: 1-65535
payload: {
"type": "mail",
"request_txn_id": 100,
"mailbox_size": 2,
"mail": {
"id": "00000000-0000-7000-8000-000000000000",
"name": "command",
"payload": {...}
}
}
-> PUBACK
id: <packet_id>
Acknowledging, Rejecting or Requeueing Messages
It is crucial for the device to either acknowledge, reject, or requeue any message that it receives from Fostrom. This tells Fostrom how to handle the message in the mailbox. To perform these operations, the device should send a PUBLISH packet on the topic fostrom with the appropriate payload.
Acknowledging Messages
To acknowledge a message (remove it from the mailbox as successfully processed):
{
"type": "ack_mail",
"id": "<mail_id>"
}
Rejecting Messages
To reject a message (remove it from the mailbox as unable to process):
{
"type": "reject_mail",
"id": "<mail_id>"
}
Requeueing Messages
To requeue a message (put it back in the mailbox for later processing):
{
"type": "requeue_mail",
"id": "<mail_id>"
}
All three operations will receive a PUBACK response if successful. If the operation fails (e.g., invalid mail ID), the connection will be closed.
Important Notes
Error Handling
The MQTT Specification mandates immediately closing the connection if a malformed packet is received. Hence, Fostrom will close the connection abruptly if any malformed payload is sent, such as:
- Incorrectly serialized payloads (invalid JSON or MsgPack)
- Incorrect packet types or missing required fields
- Publishing or subscribing to topics other than
fostrom - Packets larger than 64 KB
Unsubscribe Support
Although not commonly used, the device can send an UNSUBSCRIBE packet for the topic fostrom, at which point Fostrom will send an UNSUBACK packet confirming the removal of the subscription. Once unsubscribed, Fostrom will stop sending any push notifications for new mail arrivals. The device can subscribe again at any time to resume receiving notifications.
Streaming Decoder
Fostrom implements a robust streaming decoder to handle chunked packets and multiple packets concatenated together. Some MQTT clients such as MQTT.js write bytes in small chunks, which works perfectly with Fostrom. This means you don't need to worry about packet fragmentation at the network level.
Transaction IDs
When using QoS 1 (recommended), always use unique transaction IDs between 1 and 65535 for your PUBLISH packets. The server will also use transaction IDs in this range for its PUBLISH packets to you. These IDs help correlate PUBACK responses with their corresponding PUBLISH packets.