Skip to main content

Delivery and Retry Semantics

ReferenceAudience: API consumerUse: design retries, duplicate handling, and loss handling for commands, events, and tag data

MQTT delivery guarantees apply between each client and the broker, not end to end. This page states what the contract declares for each message, and what that means for retries, duplicates, and loss.

Declared QoS and RETAIN​

The contract declares these values on its operations. On the dedicated data channels, it leaves QoS to the topic's own configuration:

MessageDirectionContract channelQoSRETAIN
Commands (all 20)Application → readercommand1false
Command responsesReader → applicationresponse1false
alertsReader → applicationevent1false
heartBeatEVTReader → applicationevent0false
mqttConnEVTReader → applicationrfidData0Not settled: the operation declares true, the channel false
dataEVTReader → applicationdata1EventSet per topic: 0 or 1false
dataEVTReader → applicationdata2EventSet per topic: 0 or 1false
dataEVTReader → applicationrfidData0false
Last Will (the DISCONNECTED form of mqttConnEVT)Broker → applicationserver binding0true
  • QoS 0 (at most once): a message can be lost, and MQTT does not resend it.
  • QoS 1 (at least once): the sender resends until the receiver acknowledges, so the receiver can get the same message more than once.

RETAIN is covered in Retained Messages.

Per-Topic Settings​

Each entry in an endpoint's publishTopics carries its own qos and retain, and the contract requires both. The reader applies them to that topic, and it supports QoS 0 and 1. What it does with an entry that omits them is not specified in the available sources. The endpoint's qosCommon sets a QoS level for the whole endpoint, and how it interacts with the per-topic values is not specified either. In the reader's default endpoint configurations, every topic uses QoS 0 and only the Last Will topic is retained.

On a DATA1 endpoint, the reader publishes tag data at the qos of its tag-data topic. How the per-topic settings relate to the other per-message values in the table above is not specified in the available sources.

Subscriptions​

Under MQTT 3.1.1, the broker delivers a message to a subscriber at the lower of the publish QoS and the QoS granted for the subscription, and it can grant less than was requested. Two consequences:

  • Your application's subscriptions. Subscribe at QoS 1 to receive responses and alerts at QoS 1.
  • The reader's subscription. Commands reach the reader at the QoS of its subscribeTopics[0] entry, or lower. The contract's endpoint examples subscribe at QoS 0. At QoS 0, a command can be lost between the broker and the reader.

MQTT Acknowledgments Are Not Outcomes​

A PUBACK for your command means that the broker received it, not that the reader did. Only the reader's response reports what happened. When no response arrives, you can't tell whether the command was lost, is still queued, or ran and its response was lost. See MQTT Acknowledgments and Reader Responses.

Duplicates and Retries​

A command can reach the reader more than once: MQTT can redeliver it, and your application can retry it. The reader handles every copy the same way:

  • No duplicate detection. The reader keeps no request history, so it carries out every copy that arrives, whatever its requestId.
  • The last configuration wins. For a configuration command, the copy that arrives last takes effect.
  • Work in progress can refuse a repeat. If a copy arrives while the first is still in effect, some commands refuse it with a code instead of running it again.

The contract marks each command with x-iotc-idempotent:

MarkerMeaning
trueThe command only reads. Repeating it changes nothing.
falseRepeating it is not safe. Where refusal codes are listed, a repeat sent while the first is still in effect is refused with one of them.
unknownThe command changes state, and firmware has not stated whether a repeat is harmless. Treat a repeat as a second real request.
Commandx-iotc-idempotentRefusal codes for a repeatCompletion
get_versiontrue—The reply is the result
get_statustrue—The reply is the result
get_current_regiontrue—The reply is the result
get_ethtrue—The reply is the result
get_wifitrue—The reply is the result
set_wififalse18The reply is the result
delete_wifi_profileunknown—The reply is the result
get_endpoint_configtrue—The reply is the result
config_endpointfalse10 · 33The reply is the result
get_installed_certificatestrue—The reply is the result
install_certificateunknown—Code 1, then an alerts event with id: FILE_DOWNLOAD; read alertDetails.downloadInfo.status for DOWNLOAD SUCCESS, then SAVE SUCCESS
delete_certificateunknown—The reply is the result
config_eventsunknown—The reply is the result
set_osfalse4Code 1, then an alerts event with id: FIRMWARE_UPDATE; read alertDetails.fwUpdateStatus.updateStatus for FW update success
rebootfalse5Code 1; no terminal event
get_operating_modetrue—The reply is the result
set_operating_modefalse11The reply is the result
get_post_filtertrue—The reply is the result
set_post_filterunknown—The reply is the result
control_operationfalse11The reply is the result

Before You Retry​

  • Read commands: retry freely.
  • State-changing commands with no reply: read the state back first, for example with get_endpoint_config after config_endpoint. A missing reply does not mean the command did not run.
  • After an error code: the contract's rule is to retry only when the code's recommended action names a condition that can change without changing your request, such as waiting, stopping an inventory, or charging the reader. For other codes, change the request first. Response Codes lists each code's recommended action.
  • One command at a time: the reader's command queue holds 10 commands, and commands beyond that can be lost. The contract doesn't say whether you must serialize commands to one reader; waiting for each reply before sending the next is the conservative choice.

Commands Queued While the Reader Is Offline​

With the default cleanSession: true, the broker discards the reader's session when the reader reconnects, so commands published while it was offline are not delivered to it. With cleanSession: false and a stable client ID, the broker can queue QoS 1 commands for the reader, which carries them out when it reconnects. See Clean Session.

Planning for Loss​

MessageWhat loss meansHow to plan for it
Command or responseYou can't tell from silence what happenedSet a deadline, then read state back before a state-changing retry
heartBeatEVTQoS 0: a single heartbeat can be lost in transitDecide how many missed intervals your application treats as an outage. See Reader Availability.
mqttConnEVTQoS 0: a transition can be missedConfirm connection state with a command when it matters
dataEVTAt QoS 0, which the default endpoint configurations use, tag reads can be lostTreat the stream as a statistical view of the tag population, not a guaranteed log. See Tag Data.
alertsQoS 1: redelivery is possibleHandle a repeated alert without acting twice

Tag Data​

dataEVT is published at the QoS set on the data endpoint's tag-data topic, 0 or 1, and the reader's default endpoint configurations use 0. It is not retained. The contract describes the stream as a statistical view of the tag population, not a guaranteed log of every read. It says an application that needs guaranteed capture must reconcile against inventoryStatus, which heartBeatEVT carries. Reconciliation can reveal a discrepancy, but it can't prove that capture is complete: what the inventoryStatus counters count is not specified in the available sources, and post filters and read aggregation can make counts differ legitimately.

eventNum on each tag record is the only sequence signal in the stream. The contract documents no reset or wrap-around rule for it, so a gap is a reason to investigate, not proof of loss.

Related: 📕 Retention and Last Will · 📕 Correlation and Request/Response Pairing · 📕 Response Codes · 📕 dataEVT · 📙 Recover from Disconnects

Retention and Last Will​

A retained message is stored by the broker and delivered to every new subscriber. A Last Will is a message that a client registers when it connects, for the broker to publish if the client's session ends without a clean disconnect. This section covers how the MQTT API uses both.

Retained Messages​

MessageRETAINWhat a late subscriber receives
CommandsfalseNothing. Never publish a command with RETAIN set: the broker would deliver it again whenever the reader resubscribes, and the reader would carry it out again.
ResponsesfalseNothing
alerts, heartBeatEVTfalseNothing
dataEVTfalseNothing
mqttConnEVTNot settled. See Retained Connection State.Possibly the last connection state
Last WilltrueThe last DISCONNECTED Last Will published for the endpoint, if any

Because almost nothing is retained, subscribe before the message you need is published. A response, event, or tag read published before your subscription is active is not delivered to you later.

Each publishTopics entry also carries a retain flag, which the contract requires; its examples set it to false on every topic except the Last Will topic. How it relates to the values above is not specified in the available sources; see Per-Topic Settings.

The retain member of a subscribeTopics entry has no effect: MQTT 3.1.1 has no retain flag on a subscription. The contract nonetheless requires it, and its own examples omit it.

Retained Connection State​

The contract does not settle whether mqttConnEVT is retained. Its operation declares RETAIN true, and its channel declares false; each cites a different Zebra source, and the question is open with the device owners. The RFD40/RFD90 IoT API specification also records both the connected message and the Last Will as retained.

Until it is settled, write subscribers that work either way:

  • Don't wait for a retained message. If none arrives when you subscribe, the reader's state is unknown, not disconnected.
  • Don't trust a retained message as current. A retained DISCONNECTED Last Will has no timestamp, so you can't tell how old it is.

Last Will​

PropertyValue
Registered byThe reader, in its CONNECT packet, for each endpoint session
Published byThe broker
Published whenThe session ends without a clean MQTT disconnect, including when the broker receives nothing from the reader for one and a half keep-alive intervals
Suppressed byA clean MQTT disconnect
PayloadThe DISCONNECTED form of mqttConnEVT, without a timestamp
TopicThe endpoint's publishTopics[2] topic. The contract's server binding shows <tenantId>/MGMT/clients/rfid/<serial> for a management endpoint.
QoS and RETAINQoS 0, RETAIN true
ConfigurableNo. The endpoint configuration has no Last Will fields.
Last Will payload (contract example: Disconnected event)
{
"connectionState": "DISCONNECTED",
"deviceModel": "RFD40",
"deviceSerialNo": "RFD40-211545201D0034",
"apiVersion": 1.21,
"mqttVersion": 3.0999999
}

What the Last Will does and does not tell you:

  • Its arrival means the broker detected that the session ended without a clean disconnect. Because detection can depend on the keep-alive interval, it can arrive some time after the connection was lost.
  • Its absence means nothing. A clean disconnect suppresses the Last Will, and whether the reader reports a clean disconnect in any other way is not specified in the available sources.
  • Whether the reader registers a Last Will for an endpoint without a publishTopics[2] entry is not specified in the available sources.

Reader Availability​

No single message proves that a reader is available. Combine these signals:

SignalWhat it shows
heartBeatEVT arriving at its intervalThe reader and the path to your application are working. This is the primary liveness signal.
heartBeatEVT no longer arrivingThe reader, its session, or the path to your application needs attention
A CONNECTED mqttConnEVTA new session was established for that endpoint
A DISCONNECTED mqttConnEVTThe session ended without a clean disconnect
A reply to a commandThe endpoint that carried the command is connected and processing commands

The reader publishes heartBeatEVT at the configured interval, at most 65,535 seconds, while the heartbeat is enabled with config_events; a reboot applies the setting. heartBeatEVT is published at QoS 0, so the MQTT layer does not resend a heartbeat lost in transit. Choose how many missed intervals your application treats as an outage.

Related: 📕 Delivery and Retry Semantics · 📕 Session Lifecycle · 📕 mqttConnEVT · 📕 heartBeatEVT · 📕 MQTT Connectivity

Was this helpful?