Confirmed downlinks and a retry policy

My team and I are interested in adding some retry functionality to the downlinks on Chirpstack Application server, specifically for class C devices. We would like to send a confirmed downlink to a device, and if the end device does not respond we send another confirmed downlink if it does.

To my knowledge, when one sends a confirmed downlink, the server will wait for an uplink (with an {“ack”:“true”} event associated) or it will generate an {“ack”:“false”} event. But it does not attempt to send the downlink again on failure.

I had a few questions …

  1. Has anyone attempted anything similar, or does this already exist on Chirpstack?
  2. Are there any big drawbacks to not doing this? Besides taking up airwave space.
  3. I am trying to make sense of the /ack and /txack MQTT event topics. In the LoRaWAN Frames tab on the UI, each uplink has the ack flag in its data structure.
    However, the event types have this info is separate. Is there a good way to associate the uplink topic message with the ack topic message? For our implementation we require this information to be available together.
  4. How could I determine if the uplink is confirmed or not through the MQTT application topics? This information seems to be distributed to just the Chirpstack Network server topics.

Any advice is appreciated, thank you.

When you schedule a (confirmed) downlink thought the gRPC / REST API, the response contains the downlink frame-counter of the scheduled frame.

In case of Class C, you have configured the timeout in which you expect the device to confirm the downlink frame, else the NS will assume it was not received.

When the NS receives an uplink with or without ACK (after a confirmed downlink was sent), then a /ack event is published. It contains a boolean if the downlink was confirmed or not and it contains the downlink frame-counter related to this ack or nack. So with this frame-counter you can correlate in your application which downlink was acked or nacked.

The /txack only indicates if a downlink was transmitted by the gateway. E.g. in case of a Class A application with a device which only transmits irregular ever few days, it could be used as an indication if a downlink was popped from the queue.

You are right that currently the uplink confirmed flag is not available in the event payloads (

Could you please create an issue with your request at

With regards to a retry-policy, that is really up to your application how to handle this. In some cases packet-loss is not an issue, in others it is :slight_smile:


Hi brocaar,

Thanks for the feedback. I’ve made a github issue, let me know if it needs adjustment.

When you schedule a (confirmed) downlink thought the gRPC / REST API, the response contains the downlink frame-counter of the scheduled frame.

I see, we are currently publishing a message to the /tx topic, would you recommend moving towards using the API?

Lastly, would it be reasonable to add the ack flag to the /rx topic?

would you recommend moving towards using the API?

Yes, the advantage of the API is that you receive a response with the frame-counter.

Lastly, would it be reasonable to add the ack flag to the /rx topic?

I don’t think so, since in the case of a downlink time-out (e.g. Class-B or Class-C), there is a negative ACK but no /rx.

Hi Stormon
I am also very interested in this type of functionality. I am implementing a 60 node class C network for irrigation control and it is very important that any packet loss is picked up by the app and downlinks are resent if the node has not ACKed.

My app is currently based on NodeRed and uses MQTT API with Chirpstack. I’ve yet to use the gRPC/REST API.

Do you mind sharing some more details of your application architecture and ideas around the retry functionality?


I’ve looked into this a bit more and here is my current thinking. Would welcome thoughts or alternatives.

Program steps would be:

  1. Store in var current fcnt for device using activation API
  2. Send confirmed downlink to class C device
  3. Wait a period (e.g. 15 secs) to allow for any retries
  4. Code in my device sends back the command it received as uplink message
  5. Check for latest fcnt by calling API again - if greater than var stored in step 1 then all ok, if not then assume class c command wasn’t received and executed.