Paho Python MQTT Client – Understanding Callbacks

Callbacks are functions that are called in response to an event.

The events and callbacks for the Paho MQTT client are as follows:

  • Event Connection acknowledged Triggers the on_connect callback
  • Event Disconnection acknowledged Triggers the on_disconnect callback
  • Event Subscription acknowledged Triggers the  on_subscribe callback
  • Event Un-subscription acknowledged Triggers the  on_unsubscribe callback
  • Event Publish acknowledged Triggers the on_publish callback
  • Event Message Received Triggers the on_message callback
  • Event Log information available Triggers the on_log callback

The Paho client is designed to only use the callback functions if they exist, and doesn’t provide any default callback functions.

However; it does provide a mechanism to set them.

To use a callback you need to do two things

  1. Create the callback function
  2. Assign the function to the callback.

Callback Function Example

As an example we will use the on_connect callback.

First I create my function. I’ve called it on_connect but I could have called it anything I wanted.

Here is the function.

def on_connect(client, userdata, flags, rc):"Connected flags"+str(flags)+"result code "\
     +str(rc)+"client1_id ")

The function simply logs a message, and sets a flag.

Now to associate my function with the On_connect callback I use the following:

client.on_connect= on_connect

Note: If my function had been called myfunction then I would use the following:

client.on_connect= myfunction

Callbacks and the Client Loop

Callbacks are dependent on the client loop as without the loop the callbacks aren’t triggered.

Below is the code of a simple python script that creates a connection and then waits in a loop for the on_connect callback to be triggered.

The on_connect callback then sets the flag that terminates the loop and the script ends.

This means that if the callback isn’t processed then the while loop never ends as we shall see later.


Here is the output generated by the script.

Notice how the loop executes waiting for the callback and stops once the callback gets processed.callback-on_connect-result-example

In the script above the client..loop_start() method starts a loop to process callbacks.

So let’s run the above script again, but this time I won’t start the loop. To do that I simply comment out the client.start_loop() line.


Here is what happens when I run the script.

callback-loop stopped

You can see that I had to manually terminate the script as it never processed the callback, and the callback is what  terminates the while loop.

How Callbacks Work

If you dig into the Client code you will find the code for the Connect acknowledge message.

When the client receives a CONNACK message the callback is triggered if it exists.

Here is the relevant section of the code. (around line 276)


Notice the if on_connect statement. This is what checks for the callback, and if it exists it calls the callback- self.on_connect().

The self.on_connect function is the function we assigned earlier using.

client.on_connect= on_connect

You should see from the code that the function call passes several arguments, and so the function we create needs to be able to handle those arguments.

If you look back at the function I created you can see that it handles 4 arguments. The documentation has a description of those arguments


Asynchronous or Synchronous?

If you use the loop_start() or loop_forever functions then the loop runs in a separate thread, and it is the loop that processes the incoming and outgoing messages.

In this case the callbacks can occur any time in your script and are asynchronous.

However if you call the loop() function manually in the script then the callbacks are synchronous with your script as they can only occur when you call the loop() function.

See Understanding the Loop Function

Video- The Loop and Callbacks.

Returning Values from Callbacks

Due to the way they operate It is not possible to return values from a callback using the standard return command.

Therefore if you need to get status information from a callback you need to use some form of global variable in the callback.

If you refer back to the on_connect call back example I used the connected_flag which is a property of the client object.

Because the client object is available throughout the script it can be accessed anywhere in the script.

In my scripts my general approach is extend the main mqtt client class with flags and other variable I need.

When I create client object it has these flags already assigned.

This is what it looks like:

import paho.mqtt.client as mqtt #import mqqt client
### extend main class to include flags etc
client=mqtt.Client("myclient") #create client object
client.client.connected_flag=True #set flag to true somewhere in script

Common Questions and Answers

Q- My callback is not being triggered?

A- Have you assigned the function to the Callback? Are you running a loop for that client?

Q-My callback returns a value but I don’t see it?

A- Because of the asynchronous nature of callbacks it doesn’t make sense to return a value from them.

Video- Python MQTT Client The Loop and Callbacks Explained


Script used in this Tutorial

Course Links

    1. Introduction to the Paho Python MQTT Client
    2. Introduction to the Client Class
    3. Connecting to a Broker
    4. Publishing Using The Paho Python MQTT Client
    5. Subscribing using The Paho Python Client
    6. Understanding The Loop
    7. Understanding Callbacks
    8. Handling Multiple Client Connections
Please rate? And use Comments to let me know more
[Total: 10    Average: 5/5]


  1. Hey. You’re videos were extremely helpful!
    I am trying to write a code so that two systems(Rpi to Rpi) can publish and subscribe to each other.
    Suppose if I enter ‘1’, one system should publish data to the other Rpi and vice versa.
    When published, the system should simply print the data.All this should happen simultaneously.

    Here are my codes:

    If you can help me out, I’d be very grateful.
    Thank you

  2. Hi Steve,
    I am having trouble with a problem that on_connect and on_disconnect.
    I am coding for the error cases of connect to HTTPS handling with TLS/SSL has been set, e.g.: Invalid certificates, revoke certificate on AWS, or deny the IoT connect on AWS. However I cannot get on_connect callback called in these error cases, instead of on_disconnect called immediately, observe the debug log of Paho MQTT, I didn’t see any CONNACK message received, dig into the Paho MQTT code, I found that PahoMQTT would call on_disconnect itself in _loop_rc_handle() while rc is above 0. Do you have any comments about this result? MY expected result should be call on_connect() with rc is not CONNACK_ACCEPTED for these error cases.
    In normal case (with valid certificates), my client is able to connect with IoT success.
    Any comments about this issue is appreciated. Thanks in advance.

    – Sean

    1. Does the client throw an error message when it fails to connect. Can you send me a screenshot of the connection attempt and I’ll take a quick look. Use the ask steve page

  3. Steve

    Really great tutorial. This makes a complex subject easy to understand. Could you expand on the difference between loop_start() and loop_forever()?


  4. Hi Steve,
    Thank you for your last response!
    I have a problem understanding callbacks and loops.
    I just want to create a function, that returns the last sent message on a topic every time the function itself is activated. I need to send the information of actual speed of my EV3_0 to its follower EV3_1, so that the follower copies the speed or can work with it.

    e.g. for a code in the follower EV3_1

    EV3_0_ID = “” #ID of the predecessor
    client = mqtt.Client(“EV3_1”)
    client.connect(EV3_0_ID) #connect to the predecessor
    while True:
    v_0 = get_speed() #needed function here to get the speed of EV3_0, that was sent last on that topic
    v_1 = v_0 #copy the speed of the predecessor
    follow_line(v_1) #function for the EV3 to follow a line with a given speed

    The get_speed() function should work as fast as possible and do not have time to wait for any loops, because the following follow_line(v) function must be called with high frequency for a stable behavior.

    If I try it with the explained functions (on_message, on_connect, start_loop, stop_loop)on your site, the follow_line() function works too slow. So what should be inside of the get_speed() function?

    I hope you can help me with that. I think the needed code is simple but everything I try, is not working as it should work.

    Kind regards

  5. Thank you very much for this great article, please how I can stop the reconnect loop, I’m processing the callback, but I want if the connection is broken I don’t wanna the connection to be established another time so if the connection is broken then the script will throw an error, how I can do this and thank you in advance

    1. Hi
      I’m assuming that you have used the loop_start() to start the loop.
      If so use the on_disconnect callback to detect the disconnect and in the callback use loop_stop() to stop the loop.
      Stopping the loop stops re-connection attempts.
      Let me know how you get on.

  6. Hello Steve,

    is there any difference between callback and callback function?does callback is the one on the left (client.on_connect) and the callback function is one we wrote?
    also a question probably related to programming. why don’t we have parenthesis on either side of functions. e.g
    client.on_connect= on_connect

    i believe on_connect on the left side and on the right side both are functions. so how is the flow here, i means from right side normally we do assignment.
    sorry for very basic question but would help me to understand the programming a bit

  7. Hi Steve,

    great job for this tuto, i just have one question, some one give me a project and to connect to the broker he uses :

    def mqttCallbacks(self):
    subscribe.callback(self.message_callback, ‘#’, port = self.port,, auth=self.authentication, protocol=mqtt.MQTTv311, tls=self.TLS)

    def message_callback(self, client, userdata, msg):

    but i can’t find a way to get the return code, i need to store it to know in real time the connection status

  8. Hi Steve
    Thanks for your tutorial. I see you use a loop_flag which get a value within the on_connect function and then the value is used on the main program. I have tried to do the same. i.e. use a value changed within the on_message callback function. But it doesn’t work. From within the function, I can print the received value and it is ok. But when I print the value from other part of the program it does not have any value. I even used global to define the variables to be used within the callback function. The result is the same. Please help.

    1. Jimmy
      I use the client object for the flag. This makes it available across the script.
      import paho.mqtt.client as mqtt
      mqtt.Client.connected_flag=False # assign property to the Client object
      client= mqtt.Client(cname,cleansession) #create a new client

      The inside the on_connect I use:
      if rc==0:

      Note: bad connection flag aslo need to be set not shown above.

      I have two general functions that I call to set the flags and callbacks. They look like this

      def Initialise_client_object():

      def Initialise_clients(cname,mqttclient_log,cleansession=True):
      #flags set
      client= mqtt.Client(cname,cleansession)
      if mqttclient_log: #enable mqqt client logging
      client.on_connect= on_connect #attach function to callback
      client.on_message=on_message #attach function to callback
      return client


      1. Steve.
        Thanks for your explanation.
        To clarify. I am trying not to use flags or internally generated values like the clock.
        But to use values received from the outside via MQTT message within the script. An external clock for example, since the RPI will not be connected to the internet and therefore does not have a way of knowing the actual time.
        Currently I am receiving a value, lets call it external_value1. When receive this value, I add (or subtract ) a numerical value to it.
        import paho.mqtt.client as mqtt
        import time
        import sys
        import datetime
        external_value1 = 0
        added_value2 = 0.0
        topic = [“home”, “”]
        def on_message(client, userdata, message):
        global external_value1, added_value2
        topic = message.topic.split(“/”)
        if topic[1] == “mytopic”:
        external_value1 = str(message.payload.decode(“utf-8”))
        added_value2 = float(external_value1) – time.time()
        print(external_value1, added_value2) # this prints the actual received external_value1 and the resulting added_value2
        if topic[1] == “othertopic”
        …… do something else
        client = mqtt.Client(client_id=”machine”)
        client.on_connect = on_connect
        client.on_message = on_message
        client.on_log = on_log
        client.connect(broker_address, 1883, 60)
        client.suscribe(“home/mytopic”, 1)
        client.suscribe(“home/othertopic”, 1)
        while true:
        print(external_value1, added_value2) #this prints 0, 0.0 values
        except (KeyboardInterrupt, SystemExit):
        My question is why I cannot see the values received within the script. Only inside the call back function.

        1. You are using global variables so you should be able to see them.
          However the general way of dealing with the messages is to drop them in a list or queue.
          For a list you just do define the list in the main script
          You can then get the value anywhere in the script.

          The problem you might get with arrays is if they change while you are looping through them.

          queues are usually better. Here is the relevant code taken from one of my scripts
          from queue import Queue
          def on_message(client,userdata, msg):
          q.put( m_decode)
          while not q.empty():
          results = q.get()
          if results is None:
          See this tutorial for more about the queue class

          1. Steve.
            Thanks. Firt, You help me made sure my procedure is as expected. I will go through it again. Hopefully I misspelled the name of a variable. – Something that has happened to me before, more times that I would like to admit. – Second, I would examine the usage of queues. It seems that is just what I need now. Thanks for you time, your kindness and your help. May you receive blessings.

  9. Hi Steve,
    Thanks for the tutorial. I am trying to understand how the process of acknowledgement works in MQTT. Say, a publisher publishes a message with qos 1 or 2 and the subscriber receives the message. How will the publisher understand if the following message with msg ID is recieved by the subscriber? Is there any method to realize this functionality using on_publish callback method?

  10. Hi Steve,

    Can I define my on_message callback with only message argument?
    mqttc.on_message = on_message
    def on_message(self, message): #client, userdata are not used in my callback implementation

Leave a Reply

Your email address will not be published. Required fields are marked *