Bonjour à tous,
Je suis en train de construire un flux de synchronisation dans n8n (version Cloud 2.22.6) pour mettre à jour les données de ventes de notre ERP vers les profils métier de GoHighLevel (GHL) (Entreprises).
Mon objectif :
Nous traitons un fichier CSV contenant les totaux des factures divisés par marque. Nous devons rechercher l’entreprise dans GHL en utilisant un identifiant externe immuable de l’ERP (codice_cliente) stocké à l’intérieur d’un champ personnalisé de l’entreprise GHL (business.id_gestionale), puis :
-
Si l’entreprise existe : Mettre à jour ses champs personnalisés financiers via PUT.
-
Si l’entreprise est nouvelle : Créer un nouveau profil métier via POST.
Configuration actuelle du flux :
-
Déclencheur / Lecture du fichier : Importe le CSV avec des colonnes comme codice_cliente, ragione_sociale, totale_ecopots, totale_lechuza, etc.
-
Requête HTTP (GET) : Appelle l’API GHL V2 en utilisant l’authentification par en-tête (Authorization: Bearer pit-... et Version: 2021-04-15).
-
Nœud If : Vérifie si l’entreprise a été trouvée.
-
Requête HTTP (POST / PUT) : Nœuds HTTP standard pour router les données.
Les obstacles auxquels nous sommes confrontés :
Problème 1 : L’échec de la recherche GET (limitation de l’API V2)
Nous ne pouvons pas filtrer la requête GET directement par notre champ personnalisé.
-
Si nous utilisons https://services.leadconnectorhq.com/businesses/search?locationId=XXXX&query={{ $json.codice_cliente }}, elle retourne un tableau vide [] car la requête globale GHL n’indexe pas les champs personnalisés numériques.
-
Si nous essayons d’ajouter le filtre de champ personnalisé dans la chaîne de requête (&customFields=[...]), GHL retourne une erreur 422 : "property customFields should not exist".
-
Si nous passons à une récupération globale (/businesses/?limit=100), n8n récupère une énorme charge utile de 1,2 Mo contenant toutes les entreprises, ce qui écrase le contexte binaire/JSON des lignes CSV entrantes d’origine, rendant la cartographie ultérieure extrêmement complexe.
Problème 2 : La boucle d’autorisation POST (erreur 401)
Quand un enregistrement emprunte le chemin POST pour créer une nouvelle entreprise (https://services.leadconnectorhq.com/businesses/), GHL retourne :
401 - {"message":"LocationId is not specified","error":"Unauthorized","statusCode":401}
Bien que locationId soit explicitement passé à l’intérieur du corps JSON, et que le même token Bearer pit-... fonctionne parfaitement pour les requêtes GET et les requêtes PUT sur des ID existants. On a l’impression que les clés API au niveau de la location GHL sont complètement restreintes concernant l’utilisation de POST sur le point de terminaison /businesses/ sauf en utilisant une application Marketplace OAuth complète.
Alternative proposée (passage aux Contacts + nom de l’entreprise) :
Etant donné que l’écriture sur le point de terminaison /businesses/ semble très restreinte avec les clés API standard, nous évaluons un passage au point de terminaison Contacts.
Nous pensons créer/mettre à jour des Contacts à la place, en passant l’ID de l’ERP et les champs de facture à l’intérieur des champs personnalisés du Contact, tout en cartographiant le champ companyName à l’intérieur de l’objet contact pour le relier à l’entreprise.
Mes questions à la communauté :
-
Quelle est la meilleure pratique dans n8n pour interroger une entreprise GHL en utilisant une valeur de champ personnalisé sans récupérer l’ensemble de la base de données de 1,2 Mo ou sans rencontrer d’erreurs 422 ?
-
Comment contourner le comportement de remplacement des données après un nœud de requête HTTP pour que les nœuds PUT/POST finaux aient toujours accès aux données des lignes CSV d’origine (totaux des factures) ? Devrions-nous utiliser le nouveau nœud Comparer les ensembles de données / Fusionner (enrichir les données existantes) immédiatement après l’ingestion du fichier ?
-
Quelqu’un a-t-il déjà exécuté avec succès un POST vers /businesses/ en utilisant une clé API de location standard, ou l’intégration OAuth complète est-elle obligatoire pour créer des entreprises ?
-
Concernant notre alternative : Pensez-vous que passer au point de terminaison Contacts (tout en remplissant le champ companyName à l’intérieur du contact) est une solution de contournement solide et fiable pour contourner les restrictions de l’API Business, ou y a-t-il de meilleures approches architecturales que vous suggéreriez ?
Tout conseil, contournement ou extrait JSON du flux serait très apprécié !
Dedi, cela ressemble à deux blocages distincts, non pas un seul bug GHL : trouver la bonne entreprise par codice_cliente, puis la créer/mettre à jour sans que le corps de la POST soit rejeté. Si cet ID ERP ne figure que dans un champ personnalisé d’entreprise que la recherche GHL ne peut pas filtrer, GHL peut ne pas être une source de recherche fiable ; conservez une petite map codice_cliente -> businessId dans vos propres données et mettez à jour par businessId.
Avant de reconstruire autour de Contacts, effectuez une vérification sécurisée : envoyez une demande minimale de création d’entreprise avec le même token et le même locationId, mais sans la charge utile de facture/champ personnalisé. Retourne-t-elle toujours LocationId is not specified, ou uniquement quand le corps complet mappé depuis le CSV est envoyé ?
C’est un problème très bien documenté et vous avez déjà fait la plupart du travail de diagnostic vous-même. Laissez-moi examiner chaque problème.
Sur la limitation de la recherche GET — la solution la plus propre est de récupérer toutes les entreprises une seule fois au début du flux de travail en utilisant la pagination, puis d’utiliser un nœud Code pour filtrer en mémoire selon la valeur de votre champ personnalisé. Oui, c’est une charge utile volumineuse, mais vous l’appelez une seule fois par exécution du flux, pas une seule fois par ligne CSV. Quelque chose comme :
javascript
const allBusinesses = $input.all();
const target = allBusinesses.find(b =>
b.json.customFields?.find(f => f.key === 'id_gestionale' && f.value === $('CSV Node').item.json.codice_cliente)
)
return target ?
Sur la conservation des données CSV après le nœud HTTP Request — utilisez un nœud Merge défini en mode « Combine » après votre appel GET. Alimentez-le à la fois avec l’élément CSV original et la réponse HTTP. De cette façon, vos nœuds PUT/POST en aval auront toujours accès à tous les champs de facture originaux aux côtés des données de réponse GHL. C’est le modèle standard pour cette situation exacte.
Sur l’erreur POST 401 : vous avez raison et c’est une limitation confirmée de GHL. Les clés d’API Location standard (pit-...) n’ont pas la permission de créer de nouveaux enregistrements Business via POST. Ce point de terminaison nécessite soit un jeton OAuth App complet, soit un jeton Private Integration avec une portée élevée. Si un OAuth complet n’est pas une option en ce moment, votre pivot vers les Contacts est en fait le chemin le plus pratique à suivre.
Sur le pivot Contacts : c’est une bonne solution de contournement et largement utilisée. Remplissez companyName sur le contact et GHL l’associera automatiquement à l’entreprise correspondante. Stockez votre codice_cliente et les totaux des factures dans les champs personnalisés du Contact et vous contournez entièrement les restrictions du point de terminaison Business. Le principal compromis est que vos données financières résident au niveau du Contact plutôt qu’au niveau de l’entreprise, ce qui peut affecter les rapports selon la configuration de votre compte GHL.
Si le maintien des données au niveau Business est important à long terme, la correction appropriée est la mise en place d’une GHL Marketplace App avec OAuth, mais c’est un investissement plus important et l’approche Contacts vous permettra d’avancer dès aujourd’hui.
Vous avez deux erreurs distinctes ici et il est utile de les corriger une à la fois, car un 401 et un 422 signifient des choses très différentes.
Le 401 concerne l’authentification ou la portée. Les jetons GHL API V2 sont limités par ressource, et l’écriture de champs personnalisés Company/Business nécessite spécifiquement la portée d’écriture sur les entreprises, ce qui est facile à oublier si votre jeton a été configuré pour les contacts. Confirmez que le jeton (ou l’application OAuth derrière) dispose vraiment de la portée d’écriture sur les entreprises (companies), et que vous êtes sur l’URL de base V2 avec l’en-tête Version correct que GHL exige ; un en-tête de version API manquant ou incorrect seul peut déclencher un 401 sur V2.
Le 422 concerne la charge utile. Les champs personnalisés GHL doivent être référencés par leur ID de champ, pas par le nom du champ, et la structure du corps pour les champs personnalisés est spécifique (un tableau d’objets avec l’ID du champ et la valeur, pas une clé plate). Si vous envoyez codice_cliente par son nom ou comme propriété de niveau supérieur, c’est là que vous obtenez votre 422. Récupérez d’abord les définitions de champs personnalisés de l’entreprise pour obtenir l’ID de champ exact, puis écrivez en utilisant cet ID. Confirmez également que le type de valeur correspond au type du champ ; envoyer une chaîne dans un champ numérique génère aussi un 422.
Donc : corrigez la portée et l’en-tête de version pour le 401, puis faites correspondre l’ID exact du champ personnalisé et la structure du corps pour le 422. Lequel se déclenche en premier quand vous l’exécutez, le 401 ? Celui-ci doit être réglé avant que le 422 soit même accessible.
[RÉSOLU] Synchronisation Incrémentielle des Contacts Commerciaux d’un CSV vers GoHighLevel sur Plusieurs Contacts Dupliqués avec n8n
Bonjourà tous ! Je voulais partager une solution à un problème que nous avons rencontré concernant la mise à jour incrémentielle des données commerciales annuelles spécifiques à chaque marque à partir d’un fichier CSV vers GoHighLevel (GHL). C’est particulièrement utile lorsque votre CRM contient plusieurs contacts en doublon (par exemple, différents employés ou directeurs de succursale) appartenant au même compte entreprise.
Après plusieurs tests, nous avons construit avec succès un flux de travail linéaire et résilient qui agrège les données de manière nette en amont et les déplie en aval pour mettre à jour simultanément chaque contact en doublon correspondant dans GoHighLevel.
Ce que le flux de travail fait et comment il fonctionne
Le flux de travail est conçu pour automatiser les importations de données à partir de systèmes comptables ou de gestion documentaire. Il calcule les métriques de ventes progressives d’une année à l’autre décomposées par marques de produits individuels et met à jour dynamiquement la « Date du dernier achat ».
Voici l’architecture logique exacte étape par étape :
1. Extraction de données et agrégation en amont (CSV)
-
Analyse résiliente : Le flux de travail ingère un fichier CSV (via Google Drive ou un déclenchement manuel). Les exports comptables enveloppent souvent les champs de données dans des guillemets doubles rigides qui cassent les parseurs CSV natifs. Le premier nœud JavaScript nettoie la mise en forme des décimales (en traitant les virgules) et utilise un script d’analyse de lignes personnalisé pour reconstruire un objet de données propre à 8 colonnes.
-
Consolidation en amont : Si le CSV contient plusieurs factures ou bons de livraison pour le même client dans le même fichier, le code additionne programmatiquement les totaux, générant un enregistrement cumulatif unique par ID de magasin unique. Cela empêche n8n d’envoyer des requêtes asynchrones parallèles pour le même client à GHL, évitant les blocages de limite de débit API ou les conditions de course (où les opérations s’écrasent mutuellement).
2. Recherche de contact sur le CRM (GET)
-
Le flux de travail envoie une requête GET au point de terminaison /contacts/ de GoHighLevel. Pour contourner les variations ou divergences dans les adresses e-mail d’entreprise sur les entrées en doublon, la requête effectue une recherche en utilisant le Nom de l’entreprise (ou un ID de magasin externe unique).
-
GoHighLevel répond avec une charge utile JSON contenant un tableau contacts listant chaque contact correspondant à cette requête d’entreprise spécifique.
3. La fourche logique (Fusionner et SI)
-
Un nœud Fusionner apparie les données CSV agrégées fraîches avec la sortie de recherche de GHL.
-
Un nœud SI évalue si l’entreprise existe déjà dans le CRM (meta.total > 0). Si le client est nouveau, le flux de travail se divise vers le chemin FAUX pour créer un nouvel enregistrement (POST). S’il existe, il avance vers le chemin VRAI pour les mises à jour.
4. Dépliage des doublons et linéarisation (Scompatta Duplicati)
-
C’est l’ingrédient secret pour traiter les enregistrements en doublon. Positionné immédiatement après la sortie VRAI du nœud SI, un nœud JavaScript dédié isole le tableau de contacts retourné par GHL et le “déplie” en lignes d’exécution indépendantes pour n8n.
-
Par exemple, si 3 profils de contact en doublon existent sous le même nom d’entreprise dans GoHighLevel, ce nœud convertit instantanément ce fil d’exécution unique en 3 éléments distincts circulant en aval en parallèle.
5. Calcul incrémentiel (Mathématiques des marques)
- Le nœud JavaScript suivant traite ces contacts dépliés un par un. Il regarde à l’intérieur des champs personnalisés de cet ID de contact spécifique (par exemple,
totale_ecopots_2026, totale_lechuza_2026), extrait la valeur numérique historique actuelle, et ajoute mathématiquement le nouveau volume commercial calculé à partir du fichier CSV actuel. Il produit une charge utile nette prête à être sauvegardée.
6. Mises à jour massives de champs personnalisés (PUT)
-
Le nœud Requête HTTP final exécute une commande PUT pointée vers une chaîne d’URL dynamique : https://services.leadconnectorhq.com/contacts/``{{ $json.id_contatto }}.
-
En désactivant le commutateur Exécuter une fois dans les paramètres avancés du nœud, n8n exécute une boucle séquentielle parfaite. Il envoie autant de requêtes PUT individuelles qu’il existe de profils en doublon générés par le code de dépliage, mettant à jour sans interruption les métriques historiques sur chaque profil de représentant lié à ce compte dans le CRM.
Merci à tous pour les indications précédentes. J’espère que cette disposition structurelle aidera quiconque traite des calculs incrémentaux complexes sur les profils de base de données en doublon !