El cambio de la callback API version en Paho MQTT 2.0 es, con diferencia, el motivo más común por el que los scripts MQTT en Python dejan de funcionar de repente después de un pip install --upgrade. Esta guía explica exactamente qué cambió en paho-mqtt 2.0, te da la corrección del error en una línea y después recorre la migración de cada callback a las firmas recomendadas de VERSION2. Terminarás con un ejemplo completo y funcional conectado a broker.connectmq.com que puedes usar como plantilla.
Los errores que probablemente estás viendo
Si has llegado aquí desde un buscador, lo más probable es que tengas uno de estos:
ValueError: Unsupported callback API version: version 2.0 added a callback_api_version, see docs/migrations.rst for details
TypeError: Client.__init__() missing 1 required positional argument: 'callback_api_version'
DeprecationWarning: Callback API version 1 is deprecated, update to latest version
TypeError: on_connect() takes 4 positional arguments but 5 were given
Los dos primeros vienen de crear el cliente al estilo 1.x. El ValueError aparece cuando pasas un client ID como primer argumento posicional, como en mqtt.Client("my-device"), porque esa posición pertenece ahora a la versión de la API de callbacks. El tercero es un aviso de que has elegido explícitamente la API antigua. El cuarto aparece después de cambiar a VERSION2 si dejas un callback con el número de parámetros antiguo.
Qué cambió en paho-mqtt 2.0
La versión 2.0 del cliente Python de Eclipse Paho, publicada a principios de 2024, fue la primera versión mayor en años. Su cambio principal es que los callbacks ahora están versionados. En 1.x, los parámetros que recibían los callbacks diferían entre MQTT 3.1.1 y MQTT 5, y algunos usaban enteros simples mientras otros usaban objetos de reason code. La versión 2 los unifica: cada callback recibe los mismos parámetros sea cual sea la versión del protocolo, y los códigos de resultado son siempre objetos ReasonCode.
Para no romper código en silencio con las nuevas firmas, los mantenedores te obligan a elegir explícitamente. El primer argumento de Client() es ahora callback_api_version, con dos valores:
| Valor | Comportamiento | ¿Recomendado? |
|---|---|---|
CallbackAPIVersion.VERSION1 |
Firmas de callback antiguas de 1.x, emite un aviso de obsolescencia | Solo como puente temporal |
CallbackAPIVersion.VERSION2 |
Nuevas firmas unificadas, objetos ReasonCode en todas partes |
Sí, para todo el código nuevo y migrado |
Otros cambios destacados de la 2.0 son la retirada del soporte para Python 2.7 y para versiones muy antiguas de Python 3, y el cambio de nombre de la clase ReasonCodes a ReasonCode. La lista completa está en las notas de migración oficiales de paho-mqtt.
Solución 1: el puente de una línea (VERSION1)
Si producción está caída y la necesitas de vuelta ya, dile a paho que mantenga el comportamiento antiguo:
import paho.mqtt.client as mqtt
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION1, client_id="my-device")
Todos tus callbacks existentes siguen funcionando sin cambios. Verás el aviso de obsolescencia, que te recuerda que VERSION1 se eliminará en una futura versión mayor. Trátalo como una tirita, no como una solución.
Otra tirita alternativa es fijar la librería antigua con pip install "paho-mqtt<2.0". Funciona, pero bloquea las correcciones de seguridad y de errores y suele entrar en conflicto con otros paquetes que ya requieren la 2.x. Es preferible el flag VERSION1 a fijar la versión, y planifica la migración real.
Solución 2: migrar a la versión 2 de la callback API de Paho MQTT 2.0
La solución de verdad es pasar VERSION2 y actualizar las firmas de los callbacks. Aquí tienes todos los callbacks, uno al lado del otro:
| Callback | Firma en 1.x / VERSION1 | Firma en VERSION2 |
|---|---|---|
on_connect |
(client, userdata, flags, rc) |
(client, userdata, flags, reason_code, properties) |
on_disconnect |
(client, userdata, rc) |
(client, userdata, disconnect_flags, reason_code, properties) |
on_subscribe |
(client, userdata, mid, granted_qos) |
(client, userdata, mid, reason_code_list, properties) |
on_unsubscribe |
(client, userdata, mid) |
(client, userdata, mid, reason_code_list, properties) |
on_publish |
(client, userdata, mid) |
(client, userdata, mid, reason_code, properties) |
on_message |
(client, userdata, message) |
sin cambios |
on_log |
(client, userdata, level, buf) |
sin cambios |
Con VERSION1 y MQTT 5, algunos de ellos ya tenían parámetros adicionales; VERSION2 elimina esa diferencia, que es precisamente el objetivo del cambio.
Lista de comprobación de la migración
Recorre tu código en este orden:
- Cambia cada llamada
mqtt.Client(...)pormqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=..., ...), pasando todos los demás argumentos por nombre. - Añade
propertiesaon_connecty renombrarcareason_code. Sustituyerc == 0pornot reason_code.is_failure(la comparación con enteros sigue funcionando, pero el atributo se lee mejor). - Sustituye
flags["session present"]porflags.session_present. - Dale a
on_disconnectsu nuevo parámetrodisconnect_flagsyproperties. - En
on_subscribe, sustituyegranted_qosporreason_code_listy comprueba cada entrada: los códigos de éxito tienen valores de 0 a 2 (el QoS concedido) y los fallos tienenis_failureactivado. - Añade
reason_codeypropertiesaon_publishyon_unsubscribe. - Busca
ReasonCodesen el código y cámbialo porReasonCode. - Ejecuta tus pruebas convirtiendo los avisos en errores (
python -W error::DeprecationWarning) para demostrar que no queda ningún cliente VERSION1.
Un ejemplo completo con VERSION2
Este script usa todos los callbacks habituales con las nuevas firmas. Cambia k7f3q2 por tu propia cadena aleatoria para no compartir topics con otros usuarios del servidor MQTT público gratuito:
import time
import uuid
import paho.mqtt.client as mqtt
BROKER = "broker.connectmq.com"
PREFIX = "connectmq/demo/k7f3q2"
def on_connect(client, userdata, flags, reason_code, properties):
if reason_code.is_failure:
print(f"Connect failed: {reason_code}")
return
print(f"Connected (session present: {flags.session_present})")
client.subscribe(f"{PREFIX}/#", qos=1)
def on_subscribe(client, userdata, mid, reason_code_list, properties):
for rc in reason_code_list:
print("Subscribe rejected:" if rc.is_failure else "Granted QoS:", rc)
def on_publish(client, userdata, mid, reason_code, properties):
print(f"Publish {mid} acknowledged: {reason_code}")
def on_message(client, userdata, msg):
print(f"{msg.topic}: {msg.payload.decode()}")
def on_disconnect(client, userdata, disconnect_flags, reason_code, properties):
print(f"Disconnected: {reason_code}")
client = mqtt.Client(
mqtt.CallbackAPIVersion.VERSION2,
client_id=f"migrate-{uuid.uuid4().hex[:8]}",
protocol=mqtt.MQTTv5,
)
client.on_connect = on_connect
client.on_subscribe = on_subscribe
client.on_publish = on_publish
client.on_message = on_message
client.on_disconnect = on_disconnect
client.connect(BROKER, 1883, keepalive=60)
client.loop_start()
time.sleep(1)
for i in range(3):
client.publish(f"{PREFIX}/test", f"hello {i}", qos=1)
time.sleep(1)
client.disconnect()
client.loop_stop()
Ejecútalo y deberías ver la conexión, el QoS concedido, tres confirmaciones de publicación y los tres mensajes devueltos por el broker público de ConnectMQ. Cambia protocol a mqtt.MQTTv311 y los mismos callbacks siguen funcionando sin modificaciones, que es exactamente lo que promete VERSION2. Si eres nuevo con la librería, empieza primero por el tutorial de Paho MQTT en Python.
Problemas tras la migración
Librerías de terceros fijadas a 1.x. Algunos paquetes que envuelven paho declararon paho-mqtt<2.0 en sus requisitos. Compruébalo con pip show y pip check. Las versiones recientes de la mayoría de los envoltorios mantenidos admiten la 2.x; actualízalos o aísla las herramientas heredadas en su propio entorno virtual.
Argumentos posicionales. Cualquier llamada mqtt.Client("id", False, None, mqtt.MQTTv311) queda ahora desplazada una posición. Conviértelas todas en argumentos por nombre; así las futuras actualizaciones serán indoloras.
Callbacks registrados con decoradores. El código que usa @client.connect_callback() y decoradores similares también debe actualizar las firmas de las funciones decoradas.
Excepciones silenciadas. Por defecto, paho registra las excepciones lanzadas dentro de los callbacks en lugar de tumbar tu programa. Una firma incorrecta puede parecer, por tanto, que "no pasa nada". Conecta un logger con client.enable_logger() durante la migración para que el TypeError sea visible.
Lógica de reconexión. Si comparabas enteros rc en on_disconnect para decidir si reconectar, reescríbelo en torno a reason_code. La guía de reconexión de paho-mqtt muestra patrones robustos, y la guía de TLS para MQTT en Python cubre la conexión cifrada que deberías usar en producción.
¿Deberías plantearte aiomqtt en su lugar?
Si tu aplicación está construida sobre asyncio, migrar los callbacks puede ser un buen momento para pasar directamente a una API asíncrona. aiomqtt envuelve paho y sustituye los callbacks por async for message in client.messages. Nuestro tutorial de aiomqtt y asyncio muestra el mismo flujo de publicación y suscripción escrito de esa forma. Para una comparación más amplia de clientes de Python y de otros lenguajes, consulta la visión general de librerías cliente MQTT.
Prueba tu código migrado en ConnectMQ
ConnectMQ ofrece un servidor MQTT público gratuito en broker.connectmq.com, ideal para verificar una migración, y un broker MQTT gestionado para producción con MQTT 3.1.1 y 5.0, TLS en el 8883 y credenciales generadas. Su portal incluye un explorador de topics en vivo para que confirmes que los clientes migrados siguen publicando lo que esperas. Cuando estés listo para dejar el broker público, ejecuta tus clientes en un broker MQTT en la nube privado: regístrate en el plan Serverless gratuito sin tarjeta de crédito.
Preguntas frecuentes
¿Qué es callback_api_version en paho-mqtt?
Es el primer argumento de mqtt.Client() desde paho-mqtt 2.0. Selecciona qué firmas de callback usa el cliente: VERSION1 para el estilo antiguo de 1.x o VERSION2 para el nuevo estilo unificado.
¿Debo usar VERSION1 o VERSION2?
Usa VERSION2. VERSION1 solo existe para que el código antiguo siga funcionando y muestra un aviso de obsolescencia, así que acabará eliminándose.
¿Por qué obtengo "Unsupported callback API version" al pasar un client ID?
Has pasado el client ID como primer argumento posicional, que ahora está reservado para la versión de la API de callbacks. Pasa primero mqtt.CallbackAPIVersion.VERSION2 y el ID como client_id=....
¿Tengo que cambiar on_message al migrar?
No. on_message(client, userdata, message) mantiene la misma firma en ambas versiones. Los cambios afectan a los callbacks de conexión, desconexión, suscripción, cancelación de suscripción y publicación.



