Insights

10 minutes de lecture

Coder votre propre intégration vs utiliser une API Unifiée

Dans cet article, nous allons explorer deux approches différentes pour ajouter des intégrations à une application, en utilisant Hubspot comme exemple. Nous commencerons par écrire un exemple minimal d’une application capable de :

  • Récupérer les jetons d’accès et de rafraîchissement via OAuth2,
  • Obtenir de nouveaux jetons d’accès et de rafraîchissement à l’aide du jeton de rafraîchissement fourni,
  • Récupérer les contacts disponibles dans Hubspot.

Tout le code utilisé dans cet article est disponible ici : https://github.com/chift-oneapi/hubspot_example

Objectifs

À la fin de cet article, vous serez capable de :

  • Implémenter un exemple minimal d’authentification OAuth2 et de récupération d’informations depuis Hubspot,
  • Réaliser le même scope avec l’API unifiée de Chift,
  • Expliquer les avantages et les inconvénients d’utiliser une API unifiée par rapport à une intégration traditionnelle.

Préparation à l’implémentation

Pour suivre cette section, vous aurez besoin :

  • D’un compte Hubspot, que vous pouvez créer ici,
  • D’un compte développeur Hubspot, que vous pouvez créer ici.

Une fois que vous disposez des deux comptes, vous pouvez passer à la section suivante où nous allons créer une application permettant d’obtenir les informations nécessaires pour l’authentification OAuth2.

Créer votre application Hubspot

Créer une application pour Hubspot peut se faire en quelques étapes simples. Il est assez facile de la configurer comme vous le souhaitez, mais pour simplifier, nous nous concentrerons sur les éléments obligatoires du processus.

Accédez à votre compte développeur et rendez-vous sur la page des applications. De là, cliquez sur le bouton "Create app" Créer une application pour commencer.

Tout d’abord, définissez un nom pour l’application (champ obligatoire):

Formulaire de création de l’application

Puis, choisissez les domaines (scopes) accessibles via le jeton que nous générerons lors du processus OAuth2. Nous aurons besoin de trois autorisations :

  • %%oauth%% : sélectionné par défaut, il autorise OAuth2 pour cette application,
  • %%crm.objects.companies.read%% : donne un accès en lecture aux objets "entreprises",
  • %%crm.objects.contacts.read%% : donne un accès en lecture aux objets "contacts".

Vos domaines devraient ressembler à cela :

Domaines OAuth2 de l’application

Ensuite, nous devons définir une URL de redirection pour le processus OAuth2. Dans cet exemple, mon application fonctionnera en local sur le port 8000 et le point de rappel (callback) sera %%callback%%. Vous pouvez utiliser n’importe quelle URL qui correspond à votre configuration et même en ajouter plusieurs. Voici à quoi ressemble ma configuration :

URL de redirection de l’application

Une fois cela terminé, vous pourrez finaliser la création de l’application en bas de l’écran :

Confirmation de la création de l’application

Une fois l’application créée, vous devriez pouvoir récupérer les identifiants de l’application dans la section dédiée de la page :

Identifiants de l’application

Option 1 - Écrire une application capable de récupérer des informations depuis Hubspot

L’application sera écrite en Python pour cet exemple, et nous utiliserons FastAPI comme framework. Nous utiliserons également requests pour générer et envoyer des requêtes HTTP et %%python-dotenv%% pour charger les variables d’environnement depuis nos fichiers %%.env%%.

Première étape : Démarrer le processus OAuth2

La mise en place initiale est assez simple :

from fastapi import FastAPI
app = FastAPI()

Création de l’objet de l’application

Ensuite, nous devons ajouter un point de terminaison pour recevoir la demande d’authentification d’un client souhaitant utiliser l’application. Nous appellerons ce point de terminaison %%auth%%. Ce point de terminaison initiera le flux OAuth2. Il générera d’abord un état (state) à partir d’une concaténation aléatoire de caractères ASCII, créera ensuite la requête HTTP à partir des données stockées dans nos variables d’environnement. Enfin, elle enverra la requête, stockera l'état dans l'application et suivra l'URL de redirection reçue. Cette URL doit être celle que nous avons fournie dans la requête, donc un autre point de terminaison.

Commençons par charger les variables d'environnement :

from dotenv import load_dotenv
from fastapi import FastAPI

load_dotenv()
app = FastAPI()

Chargement du fichier .env

Ensuite, nous pouvons commencer à écrire la fonctionnalité du point de terminaison. Nous devrons importer %%RedirectResponse%% depuis %%fastapi.responses%% pour la valeur de retour.

from fastapi.responses import RedirectResponse

@app.get("/auth")
def get_auth():
	"""Start OAuth2 process"""
	state = generate_state()
	response = requests.get(
		    f"{os.getenv('HUBSPOT_URL')}/oauth/authorize?"
        f"client_id={os.getenv('HUBSPOT_CLIENT_ID')}&"
        f"scope={os.getenv('HUBSPOT_SCOPE')}&"
        f"redirect_uri={os.getenv('HUBSPOT_REDIRECT_URI')}&"
        f"state={state}"
	)
	app.state = state
	return RedirectResponse(response.url)

Première implémentation du endpoint /auth

Tout d'abord, nous générons l'état (state) qui sera utilisé pour vérifier que la requête que nous recevons dans le second point de terminaison provient bien de notre application. Cet état est généré en concaténant des lettres majuscules et minuscules ASCII à l'aide du code suivant :

import random
import string

def generate_state():
    """Generates random string of letters to use for auth"""
    return "".join(random.choices(string.ascii_letters, k=15))

Génération du “random state”

Ensuite, nous récupérons l’URL de base de Hubspot depuis la variable d’environnement %%HUBSPOT_URL%%, à laquelle nous ajoutons %%/oauth/authorize%% pour pointer vers le point d’entrée OAuth2 de Hubspot. Il ne reste alors qu’à assembler les informations nécessaires en tant que paramètres de requête :

  • %%client_id%% : récupéré depuis %%HUBSPOT_CLIENT_ID%%
  • %%scope%% : récupéré depuis %%HUBSPOT_SCOPE%%
  • %%redirect_uri%% : récupéré depuis %%HUBSPOT_REDIRECT_URI%%
  • %%state%% : généré juste avant

Cependant, nous rencontrons déjà un problème avec notre implémentation naïve : les scopes pour Hubspot sont séparés par des espaces, ce qui ne fonctionne pas bien avec l’encodage des URL. Ajoutons donc un encodage correct pour nos paramètres. Pour ce faire, nous utiliserons la fonction %%quote%% fournie par %%urllib.parse%% :

from fastapi.responses import RedirectResponse
from urllib.parse import quote

@app.get("/auth")
def get_auth():
	"""Start OAuth2 process"""
	state = generate_state()
	response = requests.get(
        f"{os.getenv('HUBSPOT_URL')}/oauth/authorize?"
        f"client_id={quote(os.getenv('HUBSPOT_CLIENT_ID'))}&"
        f"scope={quote(os.getenv('HUBSPOT_SCOPE'))}&"
        f"redirect_uri={quote(os.getenv('HUBSPOT_REDIRECT_URI'))}&"
        f"state={quote(state)}"
	)
	app.state = state
	return RedirectResponse(redirect.url)

Final /auth endpoint

Première étape terminée ! Passons maintenant au point de terminaison de redirection.

Étape 2 : Récupérer les jetons d’accès et de rafraîchissement

Ce point de terminaison sera chargé de finaliser le processus OAuth2 en récupérant les jetons d’accès et de rafraîchissement. Pour cet exemple, ce point de terminaison sera nommé %%/callback%%. À cette étape, nous devrons valider que l’état (state) reçu correspond à celui attendu, récupérer le code fourni par Hubspot, puis envoyer une requête %%POST%% à l’URL dédiée aux jetons de Hubspot pour obtenir nos jetons.

Pour cette nouvelle requête, nous devrons construire un payload et l’envoyer sous forme de données encodées dans un formulaire. Heureusement, la bibliothèque requests se charge de configurer les en-têtes nécessaires si nous fournissons le payload dans un format correct. Cette fois, nous voulons renvoyer les données au format JSON, et nous aurons donc besoin d’importer %%JSONResponse%% depuis %%fastapi.responses%%.

from fastapi.responses import JSONResponse

@app.get("/callback")
def callback(request: Request, code: str, state: str):
    """Callback endpoint for OAuth2"""
    if state != app.state:
        return JSONResponse(
            "State received is different from the one generated in first authentication step",
            400,
        )
    data = {
        "grant_type": "authorization_code",
        "client_id": os.getenv("HUBSPOT_CLIENT_ID"),
        "client_secret": os.getenv("HUBSPOT_CLIENT_SECRET"),
        "redirect_uri": os.getenv("HUBSPOT_REDIRECT_URI"),
        "code": code,
    }
    response = requests.post(f"{os.getenv('HUBSPOT_TOKEN_URL')}", data).json()
    return JSONResponse(
        {
            "access_token": response["access_token"],
            "refresh_token": response["refresh_token"],
        }
    )

Implémentation de l’endpoint callback

Tout d'abord, nous vérifions que l’état (state) reçu correspond à celui attendu : FastAPI analyse automatiquement la requête reçue et traduit le paramètre de requête %%state%% en une variable nommée %%state%%. Il suffit ensuite de vérifier si %%app.state%% et %%state%% sont identiques.

L’étape suivante consiste à construire le payload. Pour que %%requests%% configure correctement les en-têtes, ce payload doit être défini sous forme de dictionnaire Python et contenir les informations suivantes :

  • %%grant_type%% : une constante qui doit être définie sur %%authorization_code%%,
  • %%client_id%% : l’identifiant client Hubspot, récupéré depuis la variable d’environnement %%HUBSPOT_CLIENT_ID%%,
  • %%client_secret%% : le secret client Hubspot, récupéré depuis la variable d’environnement %%HUBSPOT_CLIENT_SECRET%%,
  • %%redirect_uri%% : notre URL de redirection, récupérée depuis la variable d’environnement %%HUBSPOT_REDIRECT_URI%%,
  • %%code%% : le code reçu dans la réponse à la requête précédente.

Nous envoyons ensuite ce payload à l’URL dédiée aux jetons de Hubspot, récupérée depuis la variable d’environnement %%HUBSPOT_TOKEN_URL%%. La réponse à cette requête sera au format suivant :

{
	"access_token": "the_access_token_to_use_hubspots_api",
	"refresh_token": "token_to_get_a_new_valid_access_token_after_expiry",
	"expires_in": time_left_before_expiry_in_seconds
}

Réponse du endpoint du token de Hubspot

Dans cet exemple simple, nous ne rafraîchirons pas automatiquement le jeton lorsqu'il expire, ni ne le stockerons. Nous le renverrons simplement à l'utilisateur pour qu'il puisse le stocker et le réutiliser lui-même. Cependant, le stockage et le rafraîchissement automatique de ces jetons ne sont pas triviaux, car ils doivent être stockés de manière sécurisée (chiffrement) et ne pas être rafraîchis trop fréquemment, au risque d'impacter les performances de votre application.

Deuxième étape terminée !

Étape bonus : Rafraîchir le jeton d’accès

Nous allons maintenant ajouter un point de terminaison permettant à l'utilisateur d'utiliser son %%refresh_token%% pour obtenir un nouveau %%access_token%% valide. Ce point de terminaison est très similaire au précédent, donc je ne couvrirai ici que les différences.

@app.get("/refresh-token")
def get_new_token(request: Request, refresh_token: str):
    """Retrieves a new access token and refresh token derived from provided refresh token."""
    data = {
        "grant_type": "refresh_token",
        "client_id": os.getenv("HUBSPOT_CLIENT_ID"),
        "client_secret": os.getenv("HUBSPOT_CLIENT_SECRET"),
        "redirect_uri": os.getenv("HUBSPOT_REDIRECT_URI"),
        "refresh_token": refresh_token,
    }
    response = requests.post(f"{os.getenv('HUBSPOT_TOKEN_URL')}", data).json()
    return JSONResponse(
        {
            "access_token": response["access_token"],
            "refresh_token": response["refresh_token"],
        }
    )

Endpoint pour le renouvellement du token

Pour cet endpoint, nous demanderons à l'utilisateur de fournir son %%refresh_token%% en tant que paramètre de requête pour cet endpoint. Nous construirons ensuite la charge utile pour l'endpoint de jeton de Hubspot, comme précédemment, avec les différences suivantes :

  • %%grant_type%% : cela reste une constante, mais cette fois, elle doit être définie sur %%refresh_token%%.
  • %%code%% : ce paramètre n'est pas nécessaire et nous ne serons pas en mesure de le fournir, il est donc supprimé.
  • %%refresh_token%% : le jeton d'actualisation fourni par l'utilisateur.

Nous recevons ici le même modèle de réponse que pour la première négociation des jetons, et à nouveau nous renvoyons simplement les jetons d’accès et de rafraîchissement nouvellement générés.

Étape finale : Récupération des données depuis Hubspot

Passons maintenant à l’étape finale : la récupération des données. Maintenant que nous avons notre %%access_token%%, nous pouvons enfin accéder à l’API de Hubspot pour récupérer des données. Dans cet exemple, nous allons récupérer les contacts stockés dans notre entreprise, qu'il s'agisse d'individus ou d'entreprises.

Pour que Hubspot autorise l'accès à ces données, nous avons précédemment défini le scope de l'application, que nous avons également utilisé lors de la négociation des jetons. Désormais, nous devons prouver que nous avons accès en fournissant ce jeton dans la requête. Pour Hubspot, cela se fait via les en-têtes de la requête. Les en-têtes doivent inclure l'élément suivant :

http
Copy code

{
	"Authorization: Bearer <access_token>"
}

Nous devons ensuite effectuer les requêtes appropriées sur les points de terminaison désignés. Les données que nous souhaitons récupérer se trouvent sur deux points de terminaison différents du côté de Hubspot : %%/companies%% et %%/contacts%%. Il faudra effectuer deux requêtes pour récupérer les données souhaitées.

Pour cet exemple, le point de terminaison dans notre application sera nommé %%/contacts%%.

@app.get("/contacts")
def get_contacts(request: Request, token: str):
    """Retrieves companies and individuals (i.e. clients, prospects and suppliers) from Hubspot"""
    headers = {
        "Authorization": f"Bearer {token}",
    }
    response = requests.get(
        f"{os.getenv('HUBSPOT_API_URL')}/companies", headers=headers
    )
    contacts = response.json().get("results", [])
    response = requests.get(f"{os.getenv('HUBSPOT_API_URL')}/contacts", headers=headers)
    contacts.extend(response.json().get("results", []))
    return JSONResponse(contacts)


Ce point de terminaison nécessitera que l'utilisateur fournisse son %%access_token%% en tant que paramètre de requête. Ensuite, nous construisons l'en-tête comme décrit précédemment. Avec requests, les en-têtes sont fournis sous la forme d’un dictionnaire. Enfin, nous effectuons les requêtes %%GET%% sur les deux points de terminaison concernés et stockons toutes les données dans une liste que nous renvoyons ensuite sous forme de réponse JSON.

Conclusion

Comme nous l'avons vu dans cette section, configurer votre intégration prend beaucoup de temps, car même après avoir récupéré les identifiants depuis Hubspot, nous devons mettre en place le processus d'authentification pour obtenir des secrets utilisables. Nous avons contourné les difficultés liées au stockage et au rafraîchissement des accès en laissant l'utilisateur gérer lui-même son %%access_token%% et son rafraîchissement au besoin. Cependant, ce n'est pas la meilleure expérience utilisateur, car l'utilisateur doit soit suivre l'expiration de son jeton, soit constater un échec d'authentification lors de la récupération des données pour savoir qu'il doit rafraîchir ses accès.

En revanche, chaque étape de l'intégration est modifiable et personnalisable pour répondre parfaitement à nos cas d'utilisation, ce qui peut être un facteur décisif. En résumé, cette solution est parfaite pour un produit nécessitant peu d'intégrations et prêt à gérer la maintenance et le développement chronophage en échange d'une intégration parfaitement adaptée.

Option 2 - Utiliser les API unifiées de Chift

Essayons maintenant de construire le même périmètre avec Chift. Le code présenté ici n’est pas directement intégré dans notre application, mais je décrirai comment cela pourrait être fait à la fin de cette section.

Pour construire l'intégration avec Chift, nous aurons besoin de :

  • Un compte Chift avec l'intégration Hubspot activée.

Configuration des paramètres OAuth2

Une fois connecté à votre compte Chift, accédez à la configuration du connecteur Hubspot.

Accéder à la configuration du connecteur Hubspot

Sur la page de configuration du connecteur, vous pouvez saisir le %%client_id%% et le %%client_secret%% que nous avons récupérés depuis votre application Hubspot.

Configuration des informations OAuth2 dans la configuration du connecteur

Et voilà, tout est prêt !

Création d'un client

Après avoir configuré les informations OAuth2, accédez à la page des clients.

Accès à la section Clients

Ajoutez ensuite un nouveau client et nommez-le comme vous le souhaitez.

Formulaire d’ajout de client

Assurez-vous de stocker l’ID du client dans votre fichier %%.env%%, puis suivez le lien du client pour compléter le processus OAuth2.

Accès au lien du client

Ensuite, sélectionnez "Connect" sur la page de sélection du connecteur. Il vous sera demandé de donner un nom à la connexion. Cette valeur peut être arbitraire ; dans mon cas, ce sera "MyConnection". Cliquez ensuite sur "Authorize", ce qui lancera le processus OAuth2 pour récupérer le %%access_token%%.

Félicitations, il ne vous reste plus qu’une étape pour pouvoir utiliser l’API !

Création d'une clé API

Pour créer une clé API, vous devez accéder à la section dédiée sur la plateforme de Chift. Sur cette page, vous pouvez également récupérer votre %%account ID%%, alors assurez-vous de l’ajouter à votre fichier %%.env%%.

Page des clés API

À partir de là, vous pouvez donner un nom à votre clé API et, si vous le souhaitez, la limiter à un consumer spécifique.

Formulaire de création de clé API

Et voilà ! Vous pouvez maintenant vous assurer de sauvegarder toutes les informations pertinentes dans un endroit sécurisé comme un gestionnaire de mots de passe et, pour la tâche en cours, dans le fichier %%.env%%.

Page de confirmation de création de clé API

Maintenant que nous avons toutes les informations nécessaires, nous pouvons passer à l’écriture du code.

Récupération des contacts avec le SDK de Chift

Pour cette étape, nous nous appuierons à nouveau sur la bibliothèque %%python-dotenv%% pour charger l’environnement avec la configuration stockée dans notre fichier %%.env%%, ainsi que sur %%chift%%, le SDK Python pour l’application Chift.

La première chose à faire lorsque vous travaillez avec le SDK est de créer un client qui fournira l’authentification pour les autres méthodes. Commençons donc :

from chift.api.client import ChiftClient

def get_client():
    return ChiftClient(
        client_id=os.getenv("CHIFT_CLIENT_ID"),
        client_secret=os.getenv("CHIFT_CLIENT_SECRET"),
        account_id=os.getenv("CHIFT_ACCOUNT_ID"),
        url_base=os.getenv("CHIFT_URL"),
    )

Création d’un objet ChiftClient

Pour créer un objet %%ChiftClient%%, vous aurez besoin des informations suivantes :

  • %%client_id%% : l’identifiant client fourni lors de la création de la clé API,
  • %%client_secret%% : le secret client fourni lors de la création de la clé API,
  • %%account_id%% : l’identifiant du compte fourni lors de la création de la clé API, également disponible sur la page des clés API,
  • %%url_base%% : l’URL de l’API de l’application Chift,
  • %%max_retries%% (optionnel) : nombre maximal de tentatives pour chaque requête, défini par défaut à 3.

Maintenant que nous avons un client, nous pouvons l’utiliser pour récupérer l’abstraction du consommateur que nous avons créé précédemment sur la plateforme.

from chift.api.client import ChiftClient
from chift.openapi.models import Consumer

def get_consumer(client: ChiftClient, consumer_id: str) -> Consumer:
    return chift.Consumer.get(consumer_id, client)

Récupération d’un consommateur avec le SDK de Chift

Et maintenant, nous pouvons utiliser cette abstraction pour accéder aux données auxquelles le consommateur a accès. Dans notre exemple, nous souhaitons récupérer tous les contacts disponibles dans notre instance Hubspot. Avec le SDK de Chift, cela se fait en une seule ligne :

consumer.invoicing.Contact.all(client=client)

Assemblez les différentes parties pour récupérer les contacts de Hubspot :

def main():
    client = get_client()
    consumer = get_consumer(client, os.getenv("CHIFT_CONSUMER_ID"))
    contacts = consumer.invoicing.Contact.all(client=client)


Et… c’est tout ! Avec seulement quelques lignes de code, nous avons pu couvrir le périmètre défini avec notre approche applicative. L’avantage supplémentaire ici est que nous n’avons pas à nous occuper de stocker les informations d’authentification de Hubspot ni de gérer le rafraîchissement du %%access_token%% : tout est pris en charge par Chift.

Bonus : Ajouter le code de Chift à l’application

Cela n’est pas inclus dans le dépôt contenant le code, mais si vous souhaitez ajouter le code présenté ici à votre application, il suffit de porter les fonctions utilitaires %%get_client%% et %%get_consumer%% dans un endroit accessible par le reste du code de votre application. Ensuite, déplacez le code de la fonction main dans un nouveau point de terminaison dédié, tel que :

@app.get("/contacts-chift")def contacts_with_chift():    
client = get_client()    
consumer = get_consumer(client, os.getenv("CHIFT_CONSUMER_ID"))    
return consumer.invoicing.Contact.all(client=client)

Point de terminaison d'application utilisant l'intégration de Chift

Conclusion

Le lecteur attentif aura probablement remarqué que la majeure partie de cette section consistait à configurer des éléments sur la plateforme de Chift, ce qui peut être fait en quelques clics à chaque étape et avec très peu de code écrit. C’est l’un des avantages d’utiliser une telle plateforme pour gérer vos intégrations : beaucoup moins de code est nécessaire pour atteindre le même résultat. Par souci de simplicité, la configuration du consommateur a été effectuée via l’interface, mais cela aurait également pu être fait via le SDK.

Cette solution est particulièrement adaptée aux solutions en forte croissance qui dépendent de multiples intégrations ou qui prévoient d’en ajouter. Elle est également idéale lorsque l’écriture et la maintenance des intégrations ne sont pas le cœur de la solution, mais plutôt un moyen d’atteindre un objectif. En résumé, toute solution cherchant à supporter de nombreuses intégrations ou ne disposant pas des ressources pour les développer et les maintenir devrait envisager de faire appel à un fournisseur d’API unifiée.

Ce qu’il faut retenir de cette comparaison

Dans cet article, nous avons exposé deux approches différentes pour les intégrations : la première consiste à écrire vos propres intégrations, et la seconde, à utiliser un fournisseur d’API unifiée.

La première approche a l’avantage de la personnalisation : vous écrivez vos intégrations pour qu’elles fonctionnent exactement comme vous le souhaitez. Mais cela s’accompagne également des inconvénients liés au fait de tout faire soi-même : gestion du stockage des secrets, rafraîchissement des accès, par exemple. Et ce n’est qu’un début : cette solution est difficile à faire évoluer car elle nécessite un travail à réaliser pour chaque intégration que vous voulez ajouter à votre application. Elle prend également du temps à maintenir, car tout changement dans l’API que vous utilisez peut entraîner des dysfonctionnements dans votre application.

Si vous optez pour la deuxième option, vous pouvez atténuer la plupart de ces points de friction : les secrets et les accès sont gérés par le fournisseur d’API unifiée, l’API que vous utilisez pour les intégrations est unique et identique pour toutes vos intégrations, et la maintenance devient alors minimale, puisque vous n’avez à vous soucier que d’une seule API au lieu d’une par intégration. Et bien que vous deviez configurer la plateforme, cela reste un effort initial unique plutôt qu’une contrainte répétée pour toutes vos intégrations.

Si vous souhaitez aborder vos intégrations en toute sérénité, acquérir un avantage concurrentiel et vous positionner pour réussir, optez pour les APIs Unifiées de Chift. Contactez notre équipe pour une démo.

Take the integration fast lane

Rendez vos devs heureux et scalez vos intégrations.
Demandez une démo