HubSpot
Extension payante. Chaque soumission crée ou met à jour un contact HubSpot. L’inscription à une liste statique, elle, ne part qu’avec un consentement donné par la personne.
Ce document décrit ce que le module garantit et ce qu’il refuse. Le guide utilisateur en donne la version courte, dans la section « Envoyer les réponses vers HubSpot ».
Un CRM qui est aussi une plateforme marketing
Tout le module tient dans cette particularité.
Chez Zoho ou Salesforce, poser une fiche est un acte de gestion commerciale : on note que quelqu’un s’est manifesté. Chez HubSpot, le même portail envoie les campagnes. Les deux gestes se ressemblent — ce sont deux appels d’API à la suite — et ne relèvent pas du tout du même fondement juridique.
Le module les sépare, et ne les sépare pas un peu :
Le contact part dès que le formulaire est relié. Quelqu’un a écrit au site ; le CRM est l’endroit où cela se note, et c’est l’intérêt légitime qui le porte.
La liste ne part qu’avec le consentement. Une liste statique est précisément ce à quoi HubSpot envoie des campagnes. Sans accord explicite, personne n’y entre.
Confondre les deux ferait d’un formulaire de contact un formulaire d’inscription, ce que personne n’a demandé.
Le consentement est un champ du formulaire, et un champ précis
Pas une case cochée en administration : une case cochée par l’exploitant dirait « ces soumissions consentent », ce qui n’est pas un consentement mais une affirmation à la place des gens.
Le réglage désigne donc quel champ du formulaire porte la case, et seul le champ Consentement du cœur est acceptable. Ce n’est pas une préférence d’interface. Ce champ-là enregistre avec la soumission le texte exact qui a été accepté et l’horodatage. Une case à cocher ordinaire n’enregistre qu’un « oui » — et un « oui » sans la mention qu’il approuvait ne démontre rien, puisque la mention affichée évolue et que c’est celle en vigueur au moment de la coche qui compte.
Sans champ désigné, il n’y a aucun état où le module suppose un accord. L’inscription ne part jamais. C’est « non cochée par défaut » poussé jusqu’au bout.
Le consentement est figé à la soumission, et c’est la décision qui compte
La ligne de suivi porte une colonne consent, écrite une fois, à l’arrivée de la
soumission. Rien ne la relit ensuite.
C’est la seule colonne de ce genre dans tout le plugin, et la raison est précise. Les autres modules reconstruisent tout à l’envoi : le réglage a pu changer, et c’est l’état courant qui fait foi. Un consentement n’est pas un réglage — c’est un fait daté, accompli par une personne. Le relire à l’envoi le ferait dépendre de ce qu’un administrateur aura coché entre-temps dans l’écran de la soumission : autrement dit, un consentement pourrait être fabriqué après coup, par quelqu’un d’autre, sans que rien ne le distingue du vrai.
Conséquence directe : cocher la case dans l’écran d’administration après la soumission n’inscrit personne. Et un renvoi manuel rejoue la soumission avec le consentement qu’elle portait, jamais avec un autre.
Seules les propriétés associées partent
L’association va de la propriété HubSpot vers le champ du formulaire. Ce qui n’y figure pas reste local : ce n’est pas une restriction, c’est la définition — l’envoi est un tableau de correspondances.
Les champs fichier, signature, paiement et mot de passe ne partent jamais, même associés de force : une adresse de téléversement est un accès permanent au fichier, et la coller dans une fiche de CRM la recopie chez un tiers hors de toute expiration. Les champs marqués sensibles sont déjà écartés en amont, par la mise en forme commune à toutes les intégrations.
Une valeur vide n’est pas envoyée. HubSpot distingue « propriété absente » de « propriété vide », et une propriété envoyée vide efface celle de la fiche. Une seconde soumission sans numéro de téléphone supprimerait celui que la première avait posé.
Les propriétés proposées à l’écran viennent de HubSpot, jamais d’une liste écrite d’avance : un portail porte des propriétés personnalisées dont les noms internes ne s’inventent pas. Les propriétés calculées et en lecture seule en sont retirées — les proposer ferait écrire une association dont chaque envoi serait refusé.
L’adresse n’est pas une propriété comme les autres
Elle a son propre réglage, et non une ligne dans le tableau d’association.
C’est elle que HubSpot emploie pour rapprocher un contact existant d’un nouveau. Sans elle il n’y a pas d’upsert, donc pas de défense contre le doublon. La traiter comme une propriété parmi cent l’aurait laissée se désassocier d’un clic sans que rien ne le signale.
Une adresse absente ou malformée est un refus définitif, écrit avant tout appel : HubSpot refuserait la fiche en parlant d’une propriété, pas du champ du formulaire, et son message ne serait pas lisible par la personne qui doit corriger.
Une soumission, un contact — garanti à trois niveaux
UNIQUE (soumission) sur la table de suivi, et une prise de ligne —
UPDATE … WHERE état <> 'sent' avec un bail de temps — qui décide laquelle de deux
tâches qui se croisent envoie réellement. Deux tâches se croisent dès qu’une
réponse de HubSpot tarde plus que l’intervalle de reprise, ce qui arrive
précisément quand le CRM est lent.
Le troisième niveau est chez HubSpot : POST /crm/v3/objects/contacts/batch/upsert
avec idProperty: email crée le contact s’il n’existe pas et le met à jour sinon.
C’est la seule défense contre le cas qu’aucun garde local ne voit — la requête
aboutit, la réponse se perd en route, la tâche est rejouée. Avec une création, ce
scénario produit deux fiches ; avec un upsert, il en met une à jour.
HubSpot dit « réussi » avec un corps qui dit « refusé »
L’upsert par lot rend 207 Multi-Status quand une partie seulement du lot a
abouti — y compris pour un lot d’un seul élément. Le sort du contact est dans
results et errors, jamais dans le code HTTP seul.
Lire le second aurait compté comme envoyés des contacts que HubSpot a refusés, et le client n’aurait rien vu jusqu’à ce qu’il compare ses chiffres.
Les portées, dont une facultative
Trois portées obligatoires : écrire et lire les contacts, lire le schéma des
propriétés. Une quatrième, crm.lists.write, est demandée en portée
facultative.
La distinction n’est pas cosmétique. Chez HubSpot, une portée obligatoire absente du portail fait échouer l’installation entière : un portail gratuit, qui n’a pas les listes, ne pourrait pas se connecter du tout — alors qu’envoyer des contacts lui irait très bien.
Le portail qui l’a l’accorde, celui qui ne l’a pas se connecte quand même, et
l’écran dit laquelle a été accordée. Le dire avant la première soumission évite un
403 découvert trois semaines plus tard dans un journal d’envois.
Les portées affichées sont celles que HubSpot a réellement accordées, lues sur le jeton — pas celles qui ont été demandées. C’est la seule source qui dise quelque chose de vrai.
Les étiquettes n’existent pas chez HubSpot
Il faut le dire plutôt que le contourner en silence : HubSpot n’a pas de tags. Ce qui s’en approche est une propriété à choix multiple, dont la valeur est une liste séparée par des points-virgules.
Le réglage désigne donc une propriété de ce type et des valeurs fixes, qui décrivent l’origine de la fiche — quel formulaire, quelle campagne. Elles suivent le contact et ne sont pas commandées par le consentement : une valeur sur une fiche de CRM n’envoie rien à personne, là où une liste est précisément ce à quoi HubSpot envoie.
Une étiquette contenant un point-virgule est écartée, pas coupée en deux : elle deviendrait deux valeurs dont aucune ne correspondrait à une option existante, et HubSpot refuserait alors tout le contact.
Ce qui n’est pas tenté, et ce qui est rejoué
Rien n’est programmé si la connexion manque, si aucun champ d’adresse n’est désigné, ou si la condition du formulaire n’est pas remplie. Une soumission marquée indésirable ne part pas, même si la tâche était déjà en file.
Les refus définitifs ne sont pas rejoués : VALIDATION_ERROR pour une propriété
inconnue ou une valeur refusée, MISSING_SCOPES pour une portée manquante,
OBJECT_NOT_FOUND pour une liste supprimée. Chaque reprise consommerait le quota
d’API du client pour obtenir le même refus, et l’écran de la soumission porte le
motif.
Les 429 et les 5xx le sont, à 1, 5 puis 30 minutes.
Un 401 donne droit à un renouvellement de session immédiat, puis à un second
essai — une fois, pas en boucle.
Une inscription à une liste en échec ne reprend pas un contact acquis. HubSpot ne sait pas poser un contact et l’inscrire dans la même requête : si la seconde échoue, la première reste. L’envoi est réussi, avec une inscription manquante que l’écran nomme. Rejouer tout l’envoi pour une liste reviendrait à réécrire une fiche qui n’en a aucun besoin.
Une panne HubSpot ne bloque jamais la soumission. Elle est enregistrée, confirmée au visiteur et notifiée par courriel avant que ce module ne soit sollicité.
Ce que le journal ne contient pas
Ni les propriétés envoyées, ni l’adresse, ni les jetons.
C’est une différence avec les autres modules, et elle est voulue. Ailleurs, le corps rendu par le service est consigné parce qu’il aide à diagnostiquer. Ici, ce corps est l’écho des propriétés écrites : HubSpot rend la fiche telle qu’il l’a enregistrée, adresse comprise. Le consigner ferait du journal des envois une seconde copie de la soumission, qui s’en irait dans les sauvegardes et les exports — où elle se lirait comme une trace technique alors qu’elle décrit une personne.
Ce qui est gardé tient en trois choses : le code HTTP, l’identifiant du contact, et le motif du refus le cas échéant. C’est assez pour retrouver la fiche et comprendre un échec, et cela ne décrit personne.
Base légale, en deux lignes
Le module n’en décide pas à votre place, et ne peut pas : c’est le responsable du traitement qui répond de la sienne. Ce qu’il garantit, c’est que les deux traitements restent distincts et traçables — le contact d’un côté, l’inscription marketing de l’autre, avec pour la seconde le texte accepté et la date, conservés avec la soumission et consultables sur son écran.
C’est cette ligne que l’on vient chercher le jour où quelqu’un demande pourquoi il reçoit — ou ne reçoit pas — une lettre d’information.
Schéma
slf_hubspot_contacts : une ligne par soumission, avec l’identifiant du contact,
le consentement figé, l’inscription effective, l’état, le motif du dernier problème
et le nombre de tentatives. Elle disparaît avec la soumission ; la fiche chez
HubSpot, elle, reste — elle appartient au CRM, et l’effacer d’ici serait décider à
la place de son propriétaire.
La connexion vit dans une option, jetons chiffrés avec une clé propre à l’intégration. Elle compte double ici : un jeton de rafraîchissement HubSpot ne périme pas, et reste valable tant que personne ne le révoque. C’est pourquoi la déconnexion le révoque chez HubSpot avant de l’effacer ici.
Ce que la V1 ne fait pas
Pas d’affaire, pas de ticket, pas d’entreprise. Pas de lecture de contacts, pas de synchronisation en sens inverse : le flux va du site vers le CRM, et l’inverse ferait d’un site WordPress public une porte d’entrée sur le fichier client.
Pas d’écriture du statut « contact marketing » non plus. Ce serait une propriété de plus dans le même appel, et un portail sans cette option la refuserait — faisant échouer l’écriture du contact tout entière pour un réglage accessoire. Ce statut appartient aux réglages du portail.
Pas de préférences d’abonnement non plus : l’API de HubSpot demande un identifiant de type d’abonnement et une base légale déclarée. Les écrire depuis un formulaire, c’est inscrire dans le registre du client une qualification juridique que le plugin n’est pas en position de faire — et une base légale fausse est pire qu’absente.
