MQTT Negative Acknowledgements: Separate Broker Rejection from Device Delivery Success

A negative PUBACK or PUBREC completes the corresponding MQTT PUBLISH as an acknowledged failure; it is not an instruction to retransmit that packet. Record the reason, close the correct protocol state and route the application record to an explicit corrective policy. A successful socket write or a callback named “publish complete” cannot replace this classification.

This guide pins the OASIS MQTT 5.0 Standard, 7 March 2019, focusing on publisher-to-server PUBACK and PUBREC interpretation. An original decoded-reason state model was executed. No broker rejection, client library callback, packet parser or device command was tested.

Correlate the acknowledgement before interpreting it

Associate the packet with the active connection/session state, Packet Identifier and expected QoS exchange. Preserve the application record identity separately because a Packet Identifier can later be reused. A reason from an unrelated or stale exchange must not complete the current application operation.

Sections 3.4.2.1 and 3.5.2.1 define the allowed reasons for PUBACK and PUBREC. Both permit success zero and the server-only value 0x10, followed by the specified failure reasons. Validate membership in the packet’s allowed set before applying the success/failure threshold. An arbitrary byte below 0x80 is not automatically a valid success code.

These packets may omit the Reason Code when Remaining Length is two; that form means success zero. A client integration that treats an omitted reason as “unknown failure” can reject a valid minimal acknowledgement. Conversely, a library that exposes only a boolean may hide information needed for reliable recovery.

A correlated legal PUBACK or PUBREC reason splits at 0x80. Negative reasons terminate the rejected publication without retransmitting it or sending PUBREL. Successful PUBACK completes QoS1, while successful PUBREC continues through PUBREL and PUBCOMP. Application completion remains a separate result.
Protocol termination and application acceptance are separate fields. A negative PUBREC ends the rejected exchange before the successful QoS 2 continuation.

Apply the correct terminal-state transition

Received acknowledgement Protocol action Application classification
PUBACK 0x00 or 0x10 QoS 1 publication acknowledged; identifier reusable Accepted at this hop
PUBACK with allowed reason at least 0x80 Close failed publication; do not retransmit it Rejected at this hop
PUBREC 0x00 or 0x10 Send PUBREL; await PUBCOMP before identifier reuse Accepted; QoS 2 exchange continues
PUBREC with allowed reason at least 0x80 Close failed publication; identifier reusable; no successful-path PUBREL Rejected at this hop

Section 4.4 explicitly treats negative PUBACK/PUBREC as acknowledgement and prohibits retransmitting the corresponding PUBLISH. Section 4.3.3 makes the identifier reusable after a failed PUBREC, unlike the successful path that continues to PUBCOMP. Keep these transitions in the protocol engine even when the application wants a later retry.

A new application attempt is a separate policy decision after the cause has been addressed. It must use a valid new-publication exchange, not resurrect the rejected packet as a reconnect retransmission. Preserve application identity or create a new operation identity according to the application’s actual retry contract.

Map reasons to corrective evidence

Reason Useful next investigation Unsafe assumption
0x87 Not authorized Check approved topic permission and authenticated identity Retrying faster will grant access
0x90 Topic Name invalid Check whether this validly formed topic is accepted by policy The UTF-8 encoding must be malformed
0x91 Packet Identifier in use Reconcile outstanding exchanges and session state Pick another identifier and ignore corruption
0x97 Quota exceeded Identify the actual administrative or implementation limit Every quota is a short transient burst
0x99 Payload format invalid Compare payload bytes with Payload Format Indicator A JSON schema was necessarily checked
0x80 / 0x83 Preserve diagnostic evidence and consult the receiver’s policy The reason reveals a specific recoverable cause

Use the numeric code for machine policy. Reason String is human-readable diagnostic text, not a stable control interface. It can be absent, and the protocol constrains its inclusion when packet-size limits would be exceeded. Redact sensitive diagnostic content before exporting logs.

Handle “no matching subscribers” as acceptance

Reason 0x10 is below the failure threshold and means the server accepted the message while reporting no matching subscribers. A server that knows this condition may use 0x10 instead of zero; it is not required to do so. Therefore zero does not prove that any subscriber existed.

For a hypothetical command publisher, 0x10 can be operationally important even though the publication was accepted. Record “accepted, no matching subscribers reported” rather than marking the target device complete. Do not assume the same meaning for telemetry intended primarily to update retained state.

A negative result also does not prove that no earlier independent attempt ever affected the device. Keep the evidence attached to this exchange. End-to-end execution, if required, needs its separately correlated application result; the MQTT reason classifies the receiving hop.

Build a controlled rejection campaign

  1. Pin the broker and client-library versions, configuration and callback-to-wire mapping.
  2. In an isolated lab, establish an accepted baseline at QoS 1 and QoS 2 with recognizable application identities.
  3. Use a documented refusal mechanism, such as a test identity lacking permission for one harmless test topic. Record the actual reason rather than assuming every broker chooses the same response.
  4. Check that negative PUBREC releases the exchange without PUBREL, and that neither negative acknowledgement enters reconnect retransmission state.
  5. Inject supported synthetic acknowledgement cases at the client’s test boundary when the broker cannot generate a needed reason predictably.
  6. Change the rejected condition deliberately, then verify any authorized new attempt remains distinct from retransmission of the failed packet.

The local model covered all nine permitted reason values for each of QoS 1 and QoS 2, eighteen classifications total, and rejected 0x81 as invalid for these acknowledgement packets. It verified the continuation, identifier-release and no-retransmission decisions. It did not test wire decoding, missing acknowledgements or reconnect recovery.

Acceptance requires a rejection to remain visible after protocol resources are released. The useful deliverable is a reason/action map plus evidence for each tested state transition, with unsupported refusal mechanisms and unproven application completion clearly distinguished.

Similar Posts