Skip to main content

Message Envelopes and Correlation

ReferenceAudience: API consumerUse: build a command, locate the result in a reply, and recognize an event from its root shape

Every MQTT API message is a JSON object with the content type application/json. Commands and responses share an envelope. Events don't: each event has its own root shape and carries no requestId.

Commands​

A command is a JSON object with these top-level members:

MemberTypeRequiredMeaning
commandstringYesThe command name, for example get_status
requestIdstring, 1 to 64 charactersYesAn identifier you choose. The reader returns it unchanged in the reply.
The command's payload keyobjectRequired where the command defines oneThe command's parameters. See Command Payload Keys.

A command carries no apiVersion. The command, response, and event envelopes are defined in Common Objects.

control_operation request (contract example: Stop inventory)
{
"command": "control_operation",
"requestId": "abcd1432",
"ctrlOprPayload": {
"controlType": "RFID",
"operation": "STOP"
}
}

Command Payload Keys​

CommandPayload keyRequired
get_version, get_status, get_current_region, get_eth, get_wifi, get_installed_certificates, get_operating_mode, get_post_filter, rebootNone—
get_endpoint_configendpointDetailsNo
set_wifiwifiConfigYes
delete_wifi_profilewifiProfileInfoYes
config_endpointepConfigYes
install_certificatecertDetailsYes
delete_certificatecertificateInfoYes
config_eventseventConfigurationYes
set_osOSUpdateDetailsYes
set_operating_modeoperatingModeYes
set_post_filterpostFilterPayloadYes
control_operationctrlOprPayloadYes

operation Values​

Four commands take an operation member. Each uses its own set of values, with its own casing. Send them exactly as listed.

CommandMemberValues
config_endpointepConfig.operationadd, delete, update
set_wifiwifiConfig.operationCREATE, MODIFY
set_post_filterpostFilterPayload.operationADD, DELETE, MODIFY
control_operationctrlOprPayload.operationSTART, STOP

What Happens to a Malformed Command​

ConditionWhat the sources state
The command value is not a recognized command nameThe reader drops the message without replying and without a response code.
The payload fails a command's validationSome commands return code 2 or 23; see Codes by Command.
A top-level member that the command does not defineThe contract does not close its messages, so a schema validator accepts it. What the reader does with it is not specified in the available sources.
More commands than the reader can queueThe reader's command queue holds 10 commands. Commands beyond that are not queued and can be lost.
A command larger than the reader acceptsFirmware reports a limit of 16,384 bytes on the command topic, not yet confirmed. The reader drops an oversized message and keeps the connection open.

Responses​

The reader answers each command it carries out with one response on the endpoint's response topic. A response contains:

MemberTypeMeaning
commandstringThe command being answered
requestIdstringThe requestId from the request
apiVersionstringThe version reported by the command handler. Optional only on delete_wifi_profile.
responseobjectcode and description: the outcome of the command
The command's result keyobjectThe data the command returns, for commands that return data

How to pair a response with its request and decide the outcome is specified in Correlation and Request/Response Pairing.

Result Keys​

CommandResult key
get_versionreaderVersion
get_statusdeviceStatus
get_current_regioncurrentRegion
get_ethethConfig
get_wifiwifiProfiles; with Wi-Fi disabled, a top-level wifiConfig
get_endpoint_configendpointResponse
get_installed_certificatesinstalledCerts
get_operating_modeoperatingMode
get_post_filterpostFilterPayload
All other commandsNone: the response object is the whole result

Events​

Events have no shared envelope and no member that names the event. Recognize each event by the members it requires, and by the topic it arrives on.

EventIdentified byRequired membersBodyTopic slot
heartBeatEVT"eventName": "heartbeat"timestamp, eventName, eventNumberdatapublishTopics[1]
alertsstate, id, and prioritytimestamp, type, state, id, priorityalertDetailspublishTopics[1]
mqttConnEVTconnectionStateconnectionState, deviceModel, deviceSerialNo, apiVersion, mqttVersionNone; all members are at the rootpublishTopics[2]
dataEVTdata containing tagData or barcodeData, and no eventNametimestampdatapublishTopics[1] on a data endpoint. When inventory output goes to a management or control endpoint, its rfid topic; the slot is not specified in the available sources.

Two members need care:

  • type has two meanings. On alerts, it is the alert category. On dataEVT, it is the operating-mode profile that was active. Don't dispatch on type alone.
  • timestamp is not required on mqttConnEVT. The DISCONNECTED form is the Last Will, registered with the broker before the disconnect happens, so it carries no timestamp. heartBeatEVT, alerts, and dataEVT require one.

Dispatching Events​

This order of checks is consistent with the required members above. It is a recommendation, not a rule the contract states:

  1. If connectionState is present, the message is mqttConnEVT.
  2. If eventName is "heartbeat", it is heartBeatEVT.
  3. If state, id, and priority are present, it is alerts.
  4. If data is present, it is dataEVT.

Treat any other message as unrecognized, and log it.

Event Size​

A dataEVT message is limited to 6,144 bytes. Firmware reports 10,240 bytes as the largest message the reader publishes, not yet confirmed. The reader drops an oversized message instead of reporting an error.

Related: 📕 Correlation and Request/Response Pairing · 📕 Topics and Channels · 📕 Common Objects · 📕 Response Codes

Correlation and Request/Response Pairing​

Commands and their replies travel on a shared pair of topics, and MQTT itself does not pair them. The requestId in the JSON payload is the only thing that links a reply to its request.

A command and its reply each make two hops through the broker, and only the requestId pairs them:

Diagram

Response Envelope​

get_status response (contract example: Device status info)
{
"command": "get_status",
"requestId": "abcd123",
"apiVersion": "V1.21",
"deviceStatus": {
"powerSource": "USB",
"radioActivity": "INACTIVE",
"radioConnection": "DISCONNECTED",
"systemTime": "2026-06-15T09:26:00.443Z",
"ntp": {
"offset": 0,
"reach": 0
},
"terminalConnection": {
"status": "CONNECTED",
"type": "USB"
},
"batteryStatus": {
"mfgDate": "22DEC2024",
"cycleCount": 8,
"fullChargeCapacity": 5739,
"temperature": 24,
"designCapacity": 6400,
"batteryType": "LI-ION",
"capacity": 2467,
"stateOfHealth": "GOOD",
"chargePercentage": 46,
"chargeStatus": 1
}
},
"response": {
"code": 0,
"description": "Success"
}
}
MemberTypeRequiredMeaning
commandstringYesThe command being answered, copied from the request
requestIdstring, 1 to 64 charactersYesThe request's requestId, copied unchanged
apiVersionstring: V1.0, V1.1, V1.2, V1.21, or V1.22Yes, except on delete_wifi_profileThe version reported by the command handler. See API Version Reporting.
response.codeintegerYesThe outcome. See Response Codes.
response.descriptionstring, at least 1 characterYesHuman-readable text. Its wording can change between firmware builds.
Result keyobjectNoThe data a command returns. See Result Keys.

Correlating Responses​

  1. Choose a new requestId for every request. It must be 1 to 64 characters. Treat it as opaque: the reader does not parse it.
  2. Subscribe to the response topic before you publish. Responses are not retained, so a reply published before your subscription is active is lost.
  3. Match on requestId, and check command. Replies arrive out of order and interleaved with other traffic, including replies to other clients. Ignore replies whose requestId you did not send.
  4. Expect a reply to arrive more than once. The response channel uses QoS 1, so the broker can deliver the same reply twice. See Delivery and Retry Semantics.
A repeated requestId does not prevent a repeat

The reader keeps no request history. If the same command reaches it twice, with the same requestId or a different one, it carries out both copies. See Duplicates and Retries.

Deciding the Outcome​

Read response, and decide in this order:

ConditionOutcomeWhat to do
code is 0SuccessRead the result key, if the command has one
code is absent and description is "Success"SuccessAs above. See Success Without a Code.
code is 1Accepted, not finishedWait for the outcome. See Asynchronous Commands.
Any other codeThe command did not succeedLook up the code in Response Codes

Don't branch on description except to recognize "Success" when code is absent.

Success Without a Code​

A successful reply to any command may omit response.code, for example "response": { "description": "Success" }. The contract requires code on every reply, but readers have been reported to omit it on success for control_operation, get_operating_mode, set_operating_mode, get_post_filter, and set_post_filter, and the device owners state that it can happen on any command. Error replies always carry a numeric code.

The firmware team also states the intent that every reply carry a code. Handle both forms, and don't depend on the code being absent.

MQTT Acknowledgments and Reader Responses​

MQTT acknowledgments and reader responses answer different questions. Don't treat one as the other.

SignalSent byWhat it tells you
CONNACKBrokerWhether the broker accepted your client's connection
SUBACKBrokerWhich QoS the broker granted for your subscription
PUBACK, for a QoS 1 publishBrokerThat the broker received your command. It says nothing about the reader.
Reader responseReaderThat the reader received the command, and the outcome in response
mqttConnEVTReader, or the broker for the Last WillThat a reader endpoint connected, or lost its session without a clean disconnect

MQTT 3.1.1 defines return codes for CONNACK and SUBACK. Those codes, and errors raised by your MQTT client library, are separate from the reader's response codes.

Asynchronous Commands​

Most commands finish when they reply. Three reply with code 1, "Command payload is accepted", which means only that the reader took the request.

CommandHow you learn the outcome
install_certificateAn alerts event with id: "FILE_DOWNLOAD". Read alertDetails.downloadInfo.status: DOWNLOAD SUCCESS, then SAVE SUCCESS.
set_osAn alerts event with id: "FIRMWARE_UPDATE". Success is alertDetails.fwUpdateStatus.updateStatus equal to FW update success. Other updateStatus values are free text: don't parse them.
rebootNo event reports the outcome. The reader replies, then drops its session.

For install_certificate and set_os:

  1. Subscribe to the endpoint's event topic before you publish the command. Events are not retained, so an outcome published before your subscription is active is lost.
  2. Make sure the matching event category is enabled with config_events: fileDownload for install_certificate, and firmwareUpdate for set_os.
  3. Choose your own deadline. No timeout is specified.

For reboot, the next CONNECTED mqttConnEVT shows that a new session was established. On its own, it does not prove that a restart happened, because a network reconnect produces the same message. The upTime text in heartBeatEVT shows a restart to a person reading it; it is not machine-readable.

Timeouts​

No timeout is specified for any command. Reply time depends on network and reader conditions, so set a deadline that suits your application. If the deadline passes, don't assume the command failed: read the reader's state before you send a state-changing command again.

Known Device Deviations​

These are the places where readers are known to send a different form. Handle them as shown.

CaseWhat to expectHow to handle it
Success reply without code"response": { "description": "Success" }See Success Without a Code
apiVersion and mqttVersion on mqttConnEVTNumbers, such as 1.21 and 3.0999999, where command replies use strings such as V1.21.Accept a number or a string. Don't compare 3.0999999 with 3.1 for equality.
fwUpdateStatus.updateStatus on alertsFree text during a firmware updateTest only for FW update success
Connection status in network-interface repliesConnected and Disconnected on the Ethernet linkStatus.status; CONNECTED and DISCONNECTED on every other status memberCompare case-insensitively
apiVersion on delete_wifi_profileAbsent on success; the contract does not declare it for this replyTreat apiVersion as optional on every reply
Events on a MGMT_EVT endpointPublished on the endpoint's subscribe topicSee Topic Slots

API Version Reporting​

apiVersion is reported by each command handler, not once for the reader. Two commands on the same firmware can report different values. The contract declares one set of values for all commands and does not say which command reports which value.

  • Don't use apiVersion for feature detection, and don't compare two values to decide which is newer.
  • Log it with the requestId when you report an issue.
  • To identify the IoT Connector version, read readerVersion.detailedVersions.iotcVersion from get_version.

See API Version Support.

Related: 📕 Message Envelopes and Correlation · 📕 Response Codes · 📕 Delivery and Retry Semantics · 📙 Send Commands and Handle Responses

Was this helpful?