IoT Cloud to Device Message Delivery Sequence

John Wong Yek Hon 295 Reputation points
2026-01-16T07:32:51.5+00:00

I've observed some potential risk on the IoT Hub, and would like to clarify here.

We have 2 applications communicate via IoT Hub, one on the cloud and the other serve as IoT Device. The are some incidents where the "Device" received the C2D messages in wrong sequence. Our cloud application sending C2D messages in the sequence of Message_A, and then Message_B, however some how our Device application processed the Message_B first.

As far as I understand, the device will have to consume the message one by one? So by right it should be on FIFO basis? However that's not the case based on our observation.

I found this from Azure documentation https://learn.microsoft.com/en-us/azure/iot-hub/how-to-cloud-to-device-messaging?pivots=programming-language-node

Redelivery attempts If something happens that prevents the device from completing, abandoning, or rejecting the message, IoT Hub will, after a fixed timeout period, queue the message for delivery again. For this reason, the message processing logic in the device app must be idempotent, so that receiving the same message multiple times produces the same result.

A message will be queued again if IoT Hub didn't receive a "completion" from device. However please advice if this message will be queued at the bottom (last) or remain at top (first)? I am wondering if this explain the issue I am facing.

Thanks.

Azure IoT Hub
Azure IoT Hub

An Azure service that enables bidirectional communication between internet of things (IoT) devices and applications.


Answer accepted by question author
Manas R Mohanty 17,275 Reputation points Moderator
2026-01-16T10:35:32.13+00:00

Hi John Wong Yek Hon

We might face out-of-sequence issue could be due to redelivery and message acknowledgment as you mentioned in description above.

"When IoT Hub puts a message in a device message queue, it sets the message state to Enqueued. When a device thread takes a message from the queue, IoT Hub locks the message by setting the message state to Invisible. This state prevents other threads on the device from processing the same message. When a device thread successfully completes the processing of a message, it notifies IoT Hub and then IoT Hub sets the message state to Completed.

A device application that successfully receives and processes a message is said to Complete the message. However, if necessary a device can also:

  • Reject the message, which causes IoT Hub to set it to the Dead lettered state. Devices that connect over the Message Queuing Telemetry Transport (MQTT) protocol can't reject cloud-to-device messages.
  • Abandon the message, which causes IoT Hub to put the message back in the queue, with the message state set to Enqueued. Devices that connect over the MQTT protocol can't abandon cloud-to-device messages.

"

Reference -

https://learn.microsoft.com/en-us/azure/iot-hub/how-to-cloud-to-device-messaging?pivots=programming-language-python#understand-the-message-queue

Below are recommendation to remediate out of sequence issues.

  1. Enable retry logic and adjust the TTL, retry setting and other properties for successful completion of messages
  2. Using a sequence number in messages through code might arrange the sequence of received message.

Sample code (using sequence number) for reference.

import asyncio
from azure.iot.device.aio import IoTHubDeviceClient
from azure.iot.device import Message

# Environment variable IOTHUB_DEVICE_CONNECTION_STRING must be set
# e.g., export IOTHUB_DEVICE_CONNECTION_STRING="HostName=...;DeviceId=...;SharedAccessKey=..."

# Keeps track of last sequence processed
last_processed_sequence = -1

# Buffer for out of order C2D messages
pending_messages = {}

async def process_in_order_messages(client):
    global last_processed_sequence, pending_messages

    while True:
        # If the next expected message is buffered, process it
        next_seq = last_processed_sequence + 1
        if next_seq in pending_messages:
            msg = pending_messages.pop(next_seq)

            # Business logic for this message
            print(f"[In order] Seq={next_seq}, Body={msg.data.decode('utf-8')}")

            # Complete the message so IoT Hub removes it
            await client.complete(msg)
            print(f"Completed Seq={next_seq}")

            last_processed_sequence = next_seq
        else:
            # Nothing to process now – break and wait for new messages
            break

async def main():
    global pending_messages

    # Create the client
    client = IoTHubDeviceClient.create_from_connection_string(
        connection_string=None  # Uses environment variable
    )
    await client.connect()

    print("Connected to IoT Hub, waiting for C2D messages...")

    try:
        async for msg in client.receive_messages():  # async receive loop
            # Extract sequence number property if present
            seq_prop = msg.custom_properties.get("sequenceNumber")
            seq = int(seq_prop) if seq_prop is not None else None

            print(f"Received C2D: Seq={seq}, Body={msg.data.decode('utf-8')}")

            if seq is None:
                # No sequence property: just complete immediately
                await client.complete(msg)
                print("Completed unsequenced C2D message")
            else:
                # Buffer and order
                pending_messages[seq] = msg

                # Try to process any in‑order messages
                await process_in_order_messages(client)

    except Exception as e:
        print(f"Error receiving messages: {e}")

    finally:
        await client.disconnect()

if __name__ == "__main__":
    asyncio.run(main())

Hope it helps address the out of sequence issue.

Thank you.

Was this answer helpful?

1 person found this answer helpful.
0 comments No comments

0 additional answers

Sort by: Most helpful

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.