Tareas programadas en HubSpot Free (script en Python y cron)

En este artículo de blog, se explica cómo crear tareas programadas en HubSpot utilizando la API del CRM, un script en Python y una programación cron.

Tareas programadas en HubSpot Free (script en Python y cron)
🏴󠁧󠁢󠁥󠁮󠁧󠁿

Recientemente he empezado a utilizar el CRM de HubSpot para organizarme. Si bien está pensado para empresas y para usarlo de forma colaborativa, es muy adaptable a su uso personal.

La versión gratuita es realmente increíble. Pero, en mi opinión, tiene la gran desventaja de no poder configurar tareas para que se repitan cada cierto tiempo. Muchas de las tareas que realizamos en nuestro día a día son de este tipo:

  • Comprobar la evolución de las ventas.
  • Informes diarios/semanales/mensuales, o con cualquier otra periodicidad.
  • Ver si hay tareas o tickets bloqueados que necesiten nuestra atención.
  • Y un sinfín de otros ejemplos, que dependerán de nuestra casuística concreta.

Para poder crear estas tareas repetitivas, sin (de momento) mejorar el CRM de HubSpot a una versión de pago, he creado un script en Python que de forma conjunta con las tareas “cron” en Linux, realiza la misma tarea. Utiliza la API de HubSpot mediante peticiones HTTP.

💡
Este desarrollo me ha sido útil, por mi situación. Pero puede no serlo para todas las situaciones; puede no ser seguro y/o demasiado complejo según qué situaciones. Hay otras opciones como Zapier, que pueden cumplir el mismo propósito de forma más sencilla.

Configuración del script

El script (en Python) se encuentra al final del artículo y disponible en este enlace. Es recomendable familiarizarse con él para entender mejor el funcionamiento.

Todas las variables que han de ser cambiadas están indicadas como "XXXXXXXXXX" en el código. La referencia de la API de HubSpot para la creación de tareas se puede encontrar en la web oficial.

La petición HTTP es enviada usando “request” vía POST, combinando los detalles de la tarea, el Bearer Token de autorización y otras cabeceras adicionales.

Las diferentes tareas configuradas, pues se puede utilizar el mismo script para crear diferentes tareas, se guardan en el objeto “task”. Más información sobre esta sección en el apartado Adición de tareas del artículo.

Dependiendo de si la tarea tiene configurada, o no, una asociación, la petición variará. El script usa el primer parámetro pasado al ser ejecutado para decidir qué tarea necesita ser creada.

Por las necesidades personales, el script está configurado para asociar las tareas únicamente a objetos del tipo Persona. En caso de necesitar asignar tareas a otros objetos, habría que modificar el código. Entre otros cambios, puede ser necesario actualizar associationCategory y associationTypeId. Así como lógicas, y/o guardar también en el objeto “tasks” otros detalles necesarios.

Clave de API (Private app access tokens)

Para autenticar el script se ha de crear una aplicación privada. De esta forma, podremos acceder a los datos de nuestro CRM. El artículo oficial que aborda la creación de las mismas está disponible en este enlace.

Basta con seguir las instrucciones del artículo anterior. El alcance/scope que necesita esta aplicación privada es crm.objects.contacts.write.

Veremos la Ficha de acceso/Access token en la pantalla. Empezará por pat-. También cabe destacar que desde la sección de registros/logs se podrán ver todas las peticiones realizadas; esto es realmente útil, por ejemplo, a la hora de introducir cambios en el script, y para ver que no se realizan acciones ilícitas o no deseadas.

Es recomendable rotar este access token de forma periódica.

Adición de tareas

El script almacena en el objeto “task” las diferentes variables para cada tarea configurada.

Por ejemplo, task1 tiene un título que comienza por “Informe” y los detalles para la asociación con otro objeto (una persona, en este caso). La tarea task2 no tiene ningún valor para la asociación.

Para añadir más tareas, basta con editar el objeto “task", indicando un nombre (que será el que se utilice como parámetro a la hora de llamar al script) y los detalles de la tarea, siguiendo la estructura de ejemplo.

Un ejemplo de tarea será:

"informe" : {"task_title":"Preparar informe semanal ",
"task_body":"Para la weekly programada para todos los jueves a las 10:00",
"association":"999"}

Será “informe” la clave que usaremos como primer parámetro a la hora de llamar al script para crear la tarea con estos detalles.

Configuración de cron

Una vez configuradas la(s) tarea(s) que queramos crear de forma programada, creada la aplicación privada y copiado el token en el script, podemos comenzar la configuración del cron.

En mi caso, he creado un usuario nuevo en el equipo linux que se ejecutará el cron, pero no es estrictamente necesario. Incluso se puede llevar este script a una Cloud Function de Google, una Lambda de AWS o un Worker de Cloudflare.

Se guardará el script en la ruta deseada (se usará posteriormente). Y ejecutaremos “crontab -e” con el usuario que queremos ejecute el script, para editar la programación del cron. Aquí, debajo de las entradas existentes (si las hubiera), añadiremos una entrada por cada programación que se desee crear.

A modo de ejemplo, si quisiéramos que todos los miércoles a las 8 mañana cree la tarea con clave “informe” añadiremos:

0 8 * * WED /usr/bin/python /var/hubspot-task-scheduler.py informe > /var/log/hs-scheduler.log

El final, “> /var/log/hs-scheduler.log”, se utiliza para tener un registro de las ejecuciones y los posibles errores que pudieran surgir. Es opcional, pero altamente recomendable.

Para finalizar el ejemplo, si quisiéramos, además de la tarea programada previamente, añadir una a las 10 de la mañana todos los días laborables para dar de comer al canario (habiendo configurado en el objeto “tasks” los detalles), añadiremos (manteniendo el resto de entradas):

0 10 * * MON-FRI /usr/bin/python /var/hubspot-task-scheduler.py canario > /var/log/hs-scheduler.log

Código del script

# This script, executed as a cron job, enables the generation of scheduled tasks in HubSpot CRM (even using the free version, on which it is not natively supported).
#
# All the variables that need to be changed are indicated as "XXXXXXXXXX" in the code below. Essentially, the script uses the HubSpot HTTP API to perform task creation requests (engagements).
#   All the API documentation is available here: https://developers.hubspot.com/docs/api/overview
#   For in-deep task creation via API documentation, check: https://developers.hubspot.com/docs/api/crm/tasks
#
# The HTTP request is sent using “requests” via POST, combining the details of the tasks and the authorization and content-type headers.
#
# The script has saved into the ‘task’ object different variables for each configured task. For instance, task1 has a title starting with ‘informe’ and an association with another object (a person, in this case). task2 has no association configured. 
#   To add more tasks, just edit the “task” object, with a name and a object value for that entry following the structure shown as example.
#
# Depending on whether or not it has an association, the payload varies. The script uses the first param when called to decide which task needs to be created.
#   If it is the task "task" that has to be created:
#    >>  hubspot-task-scheduler.py task2  <<
#   Or add the following entry on the crontab, changing the schedule:
#    >>  0 8 * * THU /usr/bin/python /home/hubspot-api/hubspot-task-scheduler.py task2 > /var/log/hs-scheduler.log  <<


import requests, time, sys

url = "https://api.hubapi.com/crm/v3/objects/tasks"
task = {"task1" : {"task_title":"Informe XXXXXXXXXX - ","task_body":"", "association":"XXXXXXXXXX"}, "task2":{"task_title":"Revisar tickets y deals - ","task_body":"", "association":""}}

objetivo = str(sys.argv[1])
fecha_bonita = time.asctime()[4:10] + time.asctime()[19:]

if task.get(objetivo)["association"] == "":
    payload = '{"properties": {"hs_timestamp": '+ str(int((time.time()* 1000))) +',"hs_task_body": "'+ task.get(objetivo)["task_body"]+'", "hubspot_owner_id": "XXXXXXXXXX","hs_task_subject": "'+ task.get(objetivo)["task_title"]+ fecha_bonita +'", "hs_task_status": "WAITING"},"associations": []}'
else:
    payload = '{"properties": {"hs_timestamp": '+ str(int((time.time()* 1000))) +',"hs_task_body": "'+ task.get(objetivo)["task_body"]+'", "hubspot_owner_id": "XXXXXXXXXX","hs_task_subject": "'+ task.get(objetivo)["task_title"]+ fecha_bonita +'", "hs_task_status": "WAITING"},"associations": [{ "to": {"id": "'+ task.get(objetivo)["association"]+'"},"types": [{"associationCategory": "HUBSPOT_DEFINED","associationTypeId": 10}]}]}'

headers = {"authorization": "Bearer pat-XXXXXXXXXX", "content-type":"application/json"}
res = requests.post(url, data=payload, headers=headers)

print(res.text)

También disponible en este GitHub Gist.

¿Alguna duda?

No dudes en ponerte en contacto conmigo para cualquier duda, sugerencia, queja o aclaración que creas necesaria. ¡Será un placer hablar contigo!