Rapport KPI Saycom · automatisation n8n

Vous tapez le nom du client et la période (3, 6, 12 mois ou dates libres). En 10 à 15 secondes, n8n va chercher les chiffres dans GA4, Google Business Profile, SayRank, la base des formulaires et Google Maps, puis renvoie le rapport HTML complet aux couleurs Saycom.

1. Le parcours d'une demande

  1. La demande arrive. Soun remplit le formulaire (nom + période), ou un outil appelle l'API directement.
  2. WF0 vérifie. La demande est-elle complète ? Le client existe-t-il dans EXPORT_SAYRANK ? Est-il branché à GA4, GBP et SayRank ? Sinon : erreur claire.
  3. Six collectes en parallèle. GA4, performances GBP, avis, SayRank, fiche Google et e-mails reçus. Les sub-workflows choisissent le bon compte Google.
  4. Merge KPIs attend les 6 réponses, même celles en panne, pour ne jamais bloquer le rapport.
  5. Les concurrents. Une recherche Google Maps « métier + ville » via SerpApi, ou le résultat gardé en cache 30 jours.
  6. Compute derived met tout en forme : chiffres en français, évolutions N vs N-1, flèches, blocs à afficher ou masquer.
  7. WF6 remplit le template du rapport aux couleurs Saycom.
  8. Le rapport HTML revient au formulaire ou à l'outil appelant, en 10 à 15 secondes.

2. Les 9 workflows, node par node

Pour chaque workflow : son ID, son rôle, ce qu'il reçoit, ce qu'il renvoie, puis chaque node dans l'ordre du flux.

Form · WF-Form - Trigger KPI Report (TEST inline HTML)

ID n8nT7xS3QcMa5lweTiN
RôleEntrée · La porte d'entrée de Soun : un formulaire web où l'on choisit le client et la période, qui appelle WF0 et rend le rapport en fichier HTML.
DéclencheurFormulaire n8n · /form/…
Entrée4 champs : Nom du client (obligatoire), Période (3, 6, 12 mois ou Personnalisé), Date de début et Date de fin (seulement pour Personnalisé).
{
  "Nom du client": "MENUISERIE VINCENT PIERRE",
  "Période": "3 derniers mois"
}
SortieÉcran « Rapport généré » avec un lien pour relancer un rapport, et le fichier rapport-kpi.html qui se télécharge.
fichier binaire "report"
  mimeType: text/html
  fileName: rapport-kpi.html

Nodes (5), dans l'ordre du flux

#NodeCe qu'il fait
1Form Trigger
Formulaire · n8n-nodes-base.formTrigger · v2.2
Affiche le formulaire « Rapport KPI Saycom » et attend qu'on le valide.
Champs
Nom du client, Période (liste), Date de debut (periode N), Date de fin (periode N).
Réglage
responseMode = lastNode : la page de fin est fournie par le dernier node.
Piège
Les libellés des champs servent de clés dans Build payload. Renommer un libellé casse la lecture. Rester en v2.2 : la v2.5 du Form Trigger échouait à l'activation sur cette instance.
2Build payload
Code JS · n8n-nodes-base.code · v2
Traduit le choix du formulaire en demande pour WF0 : « 3 derniers mois » devient period_months = 3, ou bien les deux dates libres.
Écrit
{company_name, period_months} ou {company_name, period_n, period_n1}.
Logique
Le calcul de la fenêtre 3/6/12 mois est laissé à WF0 pour que formulaire et API donnent exactement les mêmes périodes. En Personnalisé, N-1 = mêmes dates l'année d'avant.
Erreur
Lève une erreur si le nom est vide, ou si Personnalisé est choisi sans les deux dates.
3Call WF0
Requête HTTP · n8n-nodes-base.httpRequest · v4.2
Envoie la demande à l'orchestrateur WF0 et récupère le rapport HTML.
Appel
POST https://n8n.agence-saycom.ovh/webhook/kpi/report, corps = la sortie de Build payload.
Réglages
Réponse lue en texte, timeout 120 s.
À savoir
Pas de tolérance d'erreur ici : si WF0 répond une erreur, le formulaire s'arrête sur l'erreur.
4Build data URL
Code JS · n8n-nodes-base.code · v2
Ajoute au rapport un bouton flottant « Générer un autre rapport », puis transforme la page en fichier.
Lit
$json.data (le HTML renvoyé par WF0).
Écrit
Binaire report (text/html, rapport-kpi.html).
À savoir
Le bouton est masqué à l'impression et n'existe que sur le chemin formulaire : la réponse de l'API WF0 n'est pas modifiée. Le nom du node est historique, il ne produit plus de data URL.
5Show report
Formulaire · n8n-nodes-base.form · v2.5
Page de fin : titre « Rapport généré », lien de retour vers un formulaire vierge, et téléchargement du rapport.
Réglages
operation = completion, respondWith = returnBinary, champ binaire report.
À savoir
Comme le binaire porte un nom de fichier, le navigateur télécharge le rapport au lieu de l'afficher. Retirer le nom de fichier l'afficherait dans l'onglet : piste proposée, pas appliquée.
Connexions entre nodes
  • Form Trigger → Build payload
  • Build payload → Call WF0
  • Call WF0 → Build data URL
  • Build data URL → Show report

WF0 · WF0 - KPI Report Orchestrator (TEST inline HTML)

ID n8nOYzS27t6hRwFMiEd
RôleChef d’orchestre · Le chef d'orchestre : il valide la demande, retrouve le client, lance les 6 collectes en parallèle, cherche les concurrents, calcule toutes les cases du rapport et renvoie le HTML.
DéclencheurWebhook POST · /webhook/kpi/report
EntréeSoit les 4 dates (période N et N-1), soit le raccourci period_months (3, 6 ou 12 mois pleins).
{
  "company_name": "MENUISERIE VINCENT PIERRE",
  "period_months": 3
}
ou
{
  "company_name": "MENUISERIE VINCENT PIERRE",
  "period_n":  {"date_from":"2026-03-01","date_to":"2026-03-31"},
  "period_n1": {"date_from":"2025-03-01","date_to":"2025-03-31"}
}
SortieHTTP 200 avec la page HTML (text/html), ou une erreur JSON 400 / 404 / 422.
200  Content-Type: text/html
<!DOCTYPE html> … rapport complet

400/404/422  application/json
{"error":"company_not_found",
 "message":"Entreprise '…' introuvable dans EXPORT_SAYRANK.",
 "field":"company_name", "details":{…}}

Nodes (25), dans l'ordre du flux

#NodeCe qu'il fait
1Webhook
Déclencheur · n8n-nodes-base.webhook · v2.1
Reçoit la demande de rapport (depuis le formulaire ou un autre outil).
Chemin
POST /webhook/kpi/report, responseMode = responseNode : la réponse est envoyée plus loin par un node Respond.
Sécurité
Pas d'authentification pour l'instant (prévue en phase suivante dans le contrat API).
2Init params
Code JS · n8n-nodes-base.code · v2
Vérifie la demande et calcule les périodes. Si period_months est donné, il calcule les N derniers mois pleins et la même fenêtre un an plus tôt.
Contrôles
Champs requis, format YYYY-MM-DD, début avant fin, pas de date future, période N de 730 jours maximum, N et N-1 sans chevauchement, period_months dans 3/6/12.
Écrit
validation{ok,http,body}, company_name_norm (sans accents ni ponctuation), period_n, period_n1, date_rapport, year_n, year_n1.
Choix
Ne lève jamais d'erreur : il renvoie un objet validation, aiguillé ensuite par IF. Une seule erreur est renvoyée (la première).
3IF Validation OK?
Condition · n8n-nodes-base.if · v2.2
Aiguillage : demande valide, on continue. Sinon, on répond tout de suite l'erreur 400.
Condition
$json.validation.ok est vrai.
Sorties
vrai : les deux lectures de la sheet. faux : Respond Error Early.
4Read EXPORT
Google Sheets · n8n-nodes-base.googleSheets · v4.5
Lit tout l'onglet « Feuille 1 » d'EXPORT_SAYRANK : une ligne par client avec ses IDs Google, ses emails de compte et ses notes QualiAvis.
Credential
Google Sheets account - saycomtech@gmail.com
Cible
Document EXPORT_SAYRANK, onglet Feuille 1 (gid=0).
Robustesse
3 tentatives espacées de 2 s (des erreurs 503 ont été vues). Pas de tolérance au-delà : sans la sheet, pas de client.
5Read Sayrank sheet
Google Sheets · n8n-nodes-base.googleSheets · v4.5
Lit l'onglet « Liste entreprise Sayrank », qui sert de secours pour trouver le codecrm du client.
Credential
Google Sheets account - saycomtech@gmail.com
Cible
Même document, onglet en mode id : 1059020634. Le format gid= ne marche pas pour un onglet secondaire.
Robustesse
3 tentatives espacées de 2 s.
6Lookup unique
Code JS · n8n-nodes-base.code · v2
Retrouve le client dans la sheet à partir du nom tapé, puis rassemble tout ce qu'il faut savoir sur lui : comptes Google, IDs, codecrm, site, QualiAvis, activité.
Recherche
Nom normalisé comparé aux colonnes Nom, nom_client_ga4, nom_client_gbp : égalité d'abord, puis inclusion. Garde-fou : sous 6 caractères, seule l'égalité compte.
codecrm
Colonne CRM de la Feuille 1 en priorité (plusieurs intitulés acceptés), sinon jointure par ID avec la Liste entreprise Sayrank.
Écrit
ga4_property_id, ga4_email, gbp_location_id, gbp_email, gbp_email_local, codecrm, site_ga4, domain, qualiavis_note_raw, qualiavis_count_raw, activite_concurrents, sheet_id.
Erreur
Client introuvable : validation 404 company_not_found.
7IF Lookup OK?
Condition · n8n-nodes-base.if · v2.2
Client trouvé, on continue. Sinon, réponse 404.
Condition
$json.validation.ok est vrai.
8Validate integrations
Code JS · n8n-nodes-base.code · v2
Vérifie que le client est bien branché : un compte GA4, une fiche Google et un codecrm SayRank. Sinon, réponse 422.
Contrôles
ga4_email + ga4_property_id, gbp_email + gbp_location_id, codecrm.
Erreurs
ga4_not_connected, gbp_not_connected, codecrm_not_resolved. Seule la première manquante est nommée, la liste complète est dans details.missing_integrations.
9IF Integrations OK?
Condition · n8n-nodes-base.if · v2.2
Tout est branché : on lance les 6 collectes en même temps. Sinon, réponse 422.
Sortie vrai
6 branches parallèles : WF1, WF2, WF3, WF4, fiche GBP, e-mails.
10Respond Error Early
Réponse · n8n-nodes-base.respondToWebhook · v1.5
Répond l'erreur au demandeur, avec le bon code HTTP et un message en français.
Réponse
Corps = validation.body en JSON, code HTTP = validation.http (dynamique), Content-Type: application/json.
À savoir
Un seul node pour les 3 sorties « faux » (400, 404, 422).
11Call WF1 GA4
Requête HTTP · n8n-nodes-base.httpRequest · v4.2
Demande à WF1 les chiffres de fréquentation du site (GA4).
Appel
POST /webhook/kpi/ga4-period avec company_name, period_n, period_n1. Timeout 60 s.
Robustesse
onError = continueRegularOutput + alwaysOutputData : en cas de panne, le rapport sort sans GA4.
12Call WF2 GBP
Requête HTTP · n8n-nodes-base.httpRequest · v4.2
Demande à WF2 les performances de la fiche Google (apparitions, appels, clics).
Appel
POST /webhook/kpi/gbp-perf. Timeout 60 s.
Robustesse
Tolérant aux pannes (comme les 5 autres collectes).
13Call WF3 reviews
Requête HTTP · n8n-nodes-base.httpRequest · v4.2
Demande à WF3 la note et le nombre d'avis Google.
Appel
POST /webhook/kpi/gbp-reviews avec company_name seul (les avis ne dépendent pas de la période). Timeout 60 s.
Robustesse
Tolérant aux pannes.
14Call WF4 Sayrank
Requête HTTP · n8n-nodes-base.httpRequest · v4.2
Demande à WF4 les positions des mots-clés suivis dans SayRank.
Appel
POST /webhook/kpi/sayrank-positions avec codecrm et company_name. Timeout 60 s.
Robustesse
Tolérant aux pannes.
15Prepare GBP loc req
Code JS · n8n-nodes-base.code · v2
Prépare la question à poser à Google sur la fiche du client : adresse, catégorie principale, nom affiché.
Écrit
url = API Business Information v1/locations/<ID GBP> avec readMask=storefrontAddress,categories.primaryCategory.displayName,title, method = GET, gbp_email_local.
16Emails count
Base Postgres · n8n-nodes-base.postgres · v2.6
Compte les demandes reçues par les formulaires du site du client sur la période, sans le spam.
Credential
Forms_submission (Postgres)
Requête
COUNT(*) sur la table submissions : URL comparée sans protocole, sans www. ni slash final, created_at dans la période N, status IS DISTINCT FROM 'spam'.
Clé site
site_ga4, sinon domain s'il contient un point, sinon - (aucun résultat).
Piège
IS DISTINCT FROM et pas != : sinon les soumissions au statut NULL (non modérées, donc les plus récentes) disparaissent.
17Call GBP location
Appel sub-workflow · n8n-nodes-base.executeWorkflow · v1.2
Pose cette question via sub-gbp-call, qui choisit le bon compte Google. Donne la ville et l'activité du rapport.
Cible
sub-gbp-call 9ZtvIxsyFt1hY0JT.
Robustesse
Tolérant aux pannes : ville et activité passent alors à —.
18Merge KPIs
Fusion · n8n-nodes-base.merge · v3
Attend que les 6 collectes aient répondu et les met ensemble.
Entrées
0 WF1, 1 WF2, 2 WF3, 3 WF4, 4 fiche GBP, 5 e-mails.
À savoir
Compute derived relit ce node par son nom et reconnaît chaque source à la forme de sa réponse, pas à son numéro d'entrée.
19Prep concurrents
Code JS · n8n-nodes-base.code · v2
Prépare la recherche des concurrents sur Google Maps, ou ressert le résultat déjà en cache.
Requête
Colonne « activité » d'EXPORT_SAYRANK (remplie par Saycom), sinon catégorie principale de la fiche Google. La ville est ajoutée seulement si elle n'y est pas déjà.
Cache
30 jours dans la staticData du workflow, par gbp_location_id (ou codecrm). Entrées versionnées (v = '3-5') et purgées à chaque passage.
Écrit
skip (vrai si cache ou pas de requête possible), q, self (nom du client).
20IF concurrents ?
Condition · n8n-nodes-base.if · v2.2
Faut-il vraiment interroger SerpApi ? Oui si rien en cache. Sinon on passe directement au calcul.
Condition
$json.skip est faux.
Sorties
vrai : Call SerpApi. faux : Compute derived, avec le cache ou une liste vide.
21Call SerpApi
Requête HTTP · n8n-nodes-base.httpRequest · v4.2
Fait la recherche Google Maps « métier + ville » et récupère la liste des entreprises dans l'ordre de Google.
Credential
SerpApi (authentification par paramètre de requête).
Appel
GET https://serpapi.com/search.json, engine=google_maps, type=search, hl=fr, gl=fr.
Robustesse
2 tentatives espacées de 2 s, tolérant aux pannes : quota épuisé ou API absente, le bloc concurrents est masqué.
Quota
Compte en offre gratuite : 250 recherches par mois.
22Parse concurrents
Code JS · n8n-nodes-base.code · v2
Garde les 5 premiers résultats tels que Google les classe, repère le client dans la liste, et met le tout en cache.
Logique
Aucun tri ni filtre : la liste doit être celle que Soun retrouve en tapant la recherche. Le client est reconnu par son nom normalisé (le data_cid de SerpApi ne correspond pas à l'ID GBP).
Écrit
competitors[{RANK, NOM, NOTE, AVIS, YOU}], me_rank, q. Écrit aussi le cache.
23Compute derived
Code JS · n8n-nodes-base.code · v2
Le cœur du calcul : transforme les réponses brutes en toutes les cases du rapport (chiffres formatés en français, évolutions, flèches, textes selon la période, blocs à afficher ou masquer).
Lit
$('Merge KPIs').all(), $('Lookup unique'), et les concurrents en entrée directe.
Écrit
{data, _raw} : data contient les variables du template (SESSIONS_N, CALLS_N, NOTE_GOOGLE, KEYWORDS, CONCURRENTS, flags HAS_…).
Règles
Durée de période → TYPE_RAPPORT (Mensuel, Trimestriel, Semestriel, Annuel). Si le client n'est pas dans le top 5 des concurrents, sa ligne est ajoutée à la fin, avec « 20+ » s'il est absent des 20 premiers. QualiAvis affiché seulement si note et nombre d'avis sont supérieurs à 0. Ads figé à HAS_ADS = false.
24Call WF6 render
Requête HTTP · n8n-nodes-base.httpRequest · v4.2
Envoie toutes les cases à WF6, qui les place dans le template et renvoie la page HTML.
Appel
POST /webhook/kpi/render avec {data}, réponse en texte, timeout 60 s.
À savoir
Seul appel bloquant : sans HTML, il n'y a rien à répondre.
25Respond
Réponse · n8n-nodes-base.respondToWebhook · v1.5
Renvoie le rapport HTML au demandeur.
Réponse
Corps = $('Call WF6 render').first().json.data, Content-Type: text/html; charset=utf-8.
Connexions entre nodes
  • Webhook → Init params
  • Init params → IF Validation OK?
  • IF Validation OK? → Read EXPORT (sortie 0)
  • IF Validation OK? → Read Sayrank sheet (sortie 0)
  • IF Validation OK? → Respond Error Early (early)
  • Read EXPORT → Lookup unique
  • Read Sayrank sheet → Lookup unique
  • Lookup unique → IF Lookup OK?
  • IF Lookup OK? → Validate integrations (sortie 0)
  • IF Lookup OK? → Respond Error Early (early)
  • Validate integrations → IF Integrations OK?
  • IF Integrations OK? → Call WF1 GA4 (sortie 0)
  • IF Integrations OK? → Call WF2 GBP (sortie 0)
  • IF Integrations OK? → Call WF3 reviews (sortie 0)
  • IF Integrations OK? → Call WF4 Sayrank (sortie 0)
  • IF Integrations OK? → Prepare GBP loc req (sortie 0)
  • IF Integrations OK? → Emails count (sortie 0)
  • IF Integrations OK? → Respond Error Early (early)
  • Call WF1 GA4 → Merge KPIs
  • Call WF2 GBP → Merge KPIs
  • Call WF3 reviews → Merge KPIs
  • Call WF4 Sayrank → Merge KPIs
  • Prepare GBP loc req → Call GBP location
  • Call GBP location → Merge KPIs
  • Emails count → Merge KPIs
  • Merge KPIs → Prep concurrents
  • Prep concurrents → IF concurrents ?
  • IF concurrents ? → Call SerpApi (sortie 0)
  • IF concurrents ? → Compute derived (sortie 1)
  • Call SerpApi → Parse concurrents
  • Parse concurrents → Compute derived
  • Compute derived → Call WF6 render
  • Call WF6 render → Respond

WF1 · WF1 - KPI GA4 Period

ID n8n50mry2S2YpnBvAvn
RôleCollecte · Va chercher dans Google Analytics 4 les visites du site sur la période N et N-1 : visiteurs, sessions, pages vues, durée, appareils et sources de trafic.
DéclencheurWebhook POST · /webhook/kpi/ga4-period
EntréeLe nom du client et les deux périodes (envoyés par WF0).
{
  "company_name": "MENUISERIE VINCENT PIERRE",
  "period_n":  {"date_from":"2026-03-01","date_to":"2026-03-31"},
  "period_n1": {"date_from":"2025-03-01","date_to":"2025-03-31"}
}
SortieLes totaux N et N-1, la répartition par appareil et les sources (top 8).
{
  "ok": true,
  "metrics": {
    "sessions_n": <nombre>, "sessions_n1": <nombre>,
    "delta_sessions_pct": <nombre>,
    "activeUsers_n": …, "newUsers_n": …,
    "screenPageViews_n": …, "avgSessionDuration_n": …,
    "userEngagementDuration_n": …,
    "devices": {"mobile":…, "desktop":…, "tablet":…},
    "sources": [{"name":"Organic Search","sessions":…}]
  }
}

Nodes (7), dans l'ordre du flux

#NodeCe qu'il fait
1Webhook
Déclencheur · n8n-nodes-base.webhook · v2.1
Reçoit la demande de WF0.
Chemin
POST /webhook/kpi/ga4-period, réponse via le node Respond.
2Init params
Code JS · n8n-nodes-base.code · v2
Vérifie que le nom et les deux périodes sont là, et prépare le nom normalisé.
Erreur
Lève une erreur si un champ manque (WF0 a déjà tout validé en amont).
Écrit
company_name_norm, date_from_n, date_to_n, date_from_n1, date_to_n1.
3Read EXPORT_SAYRANK
Google Sheets · n8n-nodes-base.googleSheets · v4.5
Relit la Feuille 1 d'EXPORT_SAYRANK pour retrouver la propriété GA4 et le compte Google du client.
Credential
Google Sheets account - saycomtech@gmail.com
À savoir
Pas de retry ici, contrairement à WF0. C'est une lecture de plus à chaque rapport (WF2 et WF3 font de même).
4Lookup
Code JS · n8n-nodes-base.code · v2
Retrouve la ligne du client et prépare les 3 questions à poser à GA4.
Écrit
ga4_property_id, ga4_email, body.requests : (1) 6 métriques sur N et N-1, (2) sessions par deviceCategory sur N, (3) sessions par sessionDefaultChannelGroup sur N, top 8.
Métriques
sessions, activeUsers, newUsers, screenPageViews, averageSessionDuration, userEngagementDuration.
Piège
Même recherche de nom que WF0, mais sans le garde-fou des 6 caractères.
5Call sub-ga4
Appel sub-workflow · n8n-nodes-base.executeWorkflow · v1.2
Passe la demande à sub-ga4-call, qui l'envoie à GA4 avec le bon compte Google.
Cible
sub-ga4-call 5w2DBqpQPmlDOdld, entrée transmise telle quelle.
6Parse GA4
Code JS · n8n-nodes-base.code · v2
Range la réponse de GA4 : totaux N et N-1, appareils, sources.
Logique
Rapport 1 : la ligne date_range_0 = N, l'autre = N-1. Rapports 2 et 3 : période N seulement.
Écrit
metrics{…} + delta_sessions_pct.
7Respond
Réponse · n8n-nodes-base.respondToWebhook · v1.5
Renvoie le JSON à WF0.
Réponse
respondWith = firstIncomingItem.
Connexions entre nodes
  • Webhook → Init params
  • Init params → Read EXPORT_SAYRANK
  • Read EXPORT_SAYRANK → Lookup
  • Lookup → Call sub-ga4
  • Call sub-ga4 → Parse GA4
  • Parse GA4 → Respond

WF2 · WF2 - KPI GBP Perf Period

ID n8nY9c44O0aHTlZ5tFH
RôleCollecte · Va chercher dans Google Business Profile les performances de la fiche : apparitions dans Google, appels, clics vers le site, demandes d'itinéraire.
DéclencheurWebhook POST · /webhook/kpi/gbp-perf
EntréeLe nom du client et les deux périodes.
{
  "company_name": "MENUISERIE VINCENT PIERRE",
  "period_n":  {"date_from":"2026-03-01","date_to":"2026-03-31"},
  "period_n1": {"date_from":"2025-03-01","date_to":"2025-03-31"}
}
SortieLes totaux N et N-1 par métrique.
{
  "ok": true,
  "metrics": {
    "apparitions_n": <nombre>, "apparitions_n1": <nombre>,
    "calls_n": …, "website_clicks_n": …,
    "directions_n": …, "conversations_n": …
  },
  "totals_n": {…}, "totals_n1": {…}
}

Nodes (7), dans l'ordre du flux

#NodeCe qu'il fait
1Webhook
Déclencheur · n8n-nodes-base.webhook · v2.1
Reçoit la demande de WF0.
Chemin
POST /webhook/kpi/gbp-perf.
2Init params
Code JS · n8n-nodes-base.code · v2
Vérifie les entrées et calcule une seule fenêtre qui couvre N et N-1, pour ne faire qu'un appel à Google.
Écrit
gbp_range_from = plus petite des 4 dates, gbp_range_to = plus grande.
3Read EXPORT_SAYRANK
Google Sheets · n8n-nodes-base.googleSheets · v4.5
Relit la Feuille 1 d'EXPORT_SAYRANK.
Credential
Google Sheets account - saycomtech@gmail.com
4Lookup
Code JS · n8n-nodes-base.code · v2
Retrouve la fiche Google du client et construit l'adresse de la question à Google.
Écrit
url vers fetchMultiDailyMetricsTimeSeries, gbp_email_local, method = GET.
Métriques
8 dailyMetrics : 4 types d'impressions (desktop/mobile, Maps/Search), CALL_CLICKS, WEBSITE_CLICKS, BUSINESS_DIRECTION_REQUESTS, BUSINESS_CONVERSATIONS.
Piège
Les 8 dailyMetrics sont écrits en dur dans l'URL. Le mode keypair de n8n dédoublonne les clés répétées et perdait des métriques.
5Call sub-gbp
Appel sub-workflow · n8n-nodes-base.executeWorkflow · v1.2
Passe la question à sub-gbp-call, qui l'envoie avec le bon compte Google.
Cible
sub-gbp-call 9ZtvIxsyFt1hY0JT.
6Parse GBP
Code JS · n8n-nodes-base.code · v2
Additionne les valeurs jour par jour, séparément pour N et pour N-1.
Logique
Apparitions = somme des 4 métriques d'impressions.
Erreur
Lève une erreur si Google renvoie error (WF0 tolère la panne).
7Respond
Réponse · n8n-nodes-base.respondToWebhook · v1.5
Renvoie le JSON à WF0.
Réponse
Réglages par défaut (premier item reçu).
Connexions entre nodes
  • Webhook → Init params
  • Init params → Read EXPORT_SAYRANK
  • Read EXPORT_SAYRANK → Lookup
  • Lookup → Call sub-gbp
  • Call sub-gbp → Parse GBP
  • Parse GBP → Respond

WF3 · WF3 - KPI GBP Reviews

ID n8ng9KZrahbuv4Jcd4W
RôleCollecte · Récupère les avis Google de la fiche du client et calcule la note moyenne et la répartition des étoiles.
DéclencheurWebhook POST · /webhook/kpi/gbp-reviews
EntréeLe nom du client seulement.
{ "company_name": "MENUISERIE VINCENT PIERRE" }
SortieNombre d'avis lus, note moyenne, répartition 1 à 5 étoiles.
{
  "ok": true,
  "reviews": {
    "count": <nombre>, "note": <nombre>,
    "distribution": {"1":…,"2":…,"3":…,"4":…,"5":…},
    "pagination_truncated": false
  }
}

Nodes (9), dans l'ordre du flux

#NodeCe qu'il fait
1Webhook
Déclencheur · n8n-nodes-base.webhook · v2.1
Reçoit la demande de WF0.
Chemin
POST /webhook/kpi/gbp-reviews.
2Init params
Code JS · n8n-nodes-base.code · v2
Vérifie le nom et le normalise.
Écrit
company_name, company_name_norm.
3Read EXPORT_SAYRANK
Google Sheets · n8n-nodes-base.googleSheets · v4.5
Relit la Feuille 1 d'EXPORT_SAYRANK.
Credential
Google Sheets account - saycomtech@gmail.com
4Lookup
Code JS · n8n-nodes-base.code · v2
Retrouve la fiche Google du client et prépare une première question : la liste des comptes Google Business du compte Saycom.
Écrit
gbp_location_id, gbp_email_local, url = mybusinessaccountmanagement…/v1/accounts.
5Call sub-gbp (accounts)
Appel sub-workflow · n8n-nodes-base.executeWorkflow · v1.2
Demande la liste des comptes via sub-gbp-call.
Cible
sub-gbp-call 9ZtvIxsyFt1hY0JT.
6Build reviews url
Code JS · n8n-nodes-base.code · v2
Choisit le bon compte (le groupe de fiches de Saycom) et construit l'adresse des avis.
Logique
Compte de type LOCATION_GROUP, sinon le premier.
Écrit
url = API v4 …/locations/<ID GBP>/reviews?pageSize=50.
Erreur
Lève une erreur si aucun compte.
7Call sub-gbp (reviews)
Appel sub-workflow · n8n-nodes-base.executeWorkflow · v1.2
Récupère les avis via sub-gbp-call.
Cible
sub-gbp-call 9ZtvIxsyFt1hY0JT.
8Parse reviews
Code JS · n8n-nodes-base.code · v2
Compte les avis, calcule la moyenne et la répartition des étoiles.
Piège
Une seule page de 50 avis est lue, sans pagination : au-delà de 50, count plafonne à 50 et pagination_truncated passe à vrai.
Erreur
Lève une erreur si Google renvoie error.
9Respond
Réponse · n8n-nodes-base.respondToWebhook · v1.5
Renvoie le JSON à WF0.
Réponse
firstIncomingItem.
Connexions entre nodes
  • Webhook → Init params
  • Init params → Read EXPORT_SAYRANK
  • Read EXPORT_SAYRANK → Lookup
  • Lookup → Call sub-gbp (accounts)
  • Call sub-gbp (accounts) → Build reviews url
  • Build reviews url → Call sub-gbp (reviews)
  • Call sub-gbp (reviews) → Parse reviews
  • Parse reviews → Respond

WF4 · WF4 - KPI Sayrank Positions

ID n8nsYhzBXw55NH2LywD
RôleCollecte · Interroge l'API SayRank pour connaître la position Google de chaque mot-clé suivi pour le client.
DéclencheurWebhook POST · /webhook/kpi/sayrank-positions
EntréeLe codecrm du client (trouvé par WF0) et son nom.
{ "codecrm": "CL…", "company_name": "MENUISERIE VINCENT PIERRE" }
SortieLe nombre de mots-clés classés, le nombre en position 1, top 3 et top 10, et la liste complète.
{
  "ok": true,
  "positions_total": <nombre>,
  "top1_count": …, "top3_count": …, "top10_count": …,
  "top3_keywords": [{"mot_cle":"…","position":…}],
  "mots_cles_rates": [ … ],
  "all_keywords": [ … ]
}

Nodes (6), dans l'ordre du flux

#NodeCe qu'il fait
1Webhook
Déclencheur · n8n-nodes-base.webhook · v2.1
Reçoit la demande de WF0.
Chemin
POST /webhook/kpi/sayrank-positions.
2Init params
Code JS · n8n-nodes-base.code · v2
Vérifie que le codecrm est fourni.
Piège
Le codecrm SayRank (format CL…) n'est pas l'ID Saycom de la sheet. Appelé seul avec un mauvais code, WF4 répond 200 mais vide.
3Lookup
Code JS · n8n-nodes-base.code · v2
Simple passage de relais : aucune lecture de sheet ici, le codecrm arrive déjà résolu.
Écrit
company_name, codecrm.
4Sayrank positions
Requête HTTP · n8n-nodes-base.httpRequest · v4.2
Demande à SayRank les positions des mots-clés du client.
Credential
Header Auth account
Appel
POST https://sayrank.agence-saycom.fr/api-v1/statistiques/positions, corps multipart avec un champ data = {"codecrm":"…"}.
Piège
La réponse data est une chaîne JSON, à parser.
5Parse positions
Code JS · n8n-nodes-base.code · v2
Trie les mots-clés par position et compte ceux en 1re position, dans le top 3 et dans le top 10.
Logique
Chaque entrée = [id, mot_cle, position]. Les positions au-delà de 100 sont ignorées (valeur « non classé » de SayRank). Mots-clés « ratés » = positions au-delà de 10, 6 au plus.
Erreur
Lève une erreur si SayRank répond un code négatif ou une donnée illisible.
6Respond
Réponse · n8n-nodes-base.respondToWebhook · v1.5
Renvoie le JSON à WF0.
Réponse
firstIncomingItem.
Connexions entre nodes
  • Webhook → Init params
  • Init params → Lookup
  • Lookup → Sayrank positions
  • Sayrank positions → Parse positions
  • Parse positions → Respond

WF6 · WF6 - KPI Report Render (TEST inline HTML)

ID n8nKE6dIswFEO6hdpkc
RôleRendu · Prend toutes les cases calculées par WF0 et les place dans le template HTML du rapport (mint, noir, orange) pour produire la page finale.
DéclencheurWebhook POST · /webhook/kpi/render
EntréeL'objet data produit par Compute derived.
{
  "data": {
    "NOM_CLIENT": "…", "PERIODE": "3 mois",
    "SESSIONS_N": "…", "HAS_REVIEWS": true,
    "CONCURRENTS": [ … ], …
  }
}
SortieLa page HTML complète (text/html).
<!DOCTYPE html>
<html lang="fr"> … </html>

Nodes (3), dans l'ordre du flux

#NodeCe qu'il fait
1Webhook
Déclencheur · n8n-nodes-base.webhook · v2.1
Reçoit les données de WF0.
Chemin
POST /webhook/kpi/render.
2Render mustache
Code JS · n8n-nodes-base.code · v2
Contient le template du rapport et le remplit avec les données : chaque {{VARIABLE}} est remplacée, chaque bloc conditionnel est affiché ou masqué.
Template
Embarqué en base64 dans la constante TPL_B64 (environ 326 000 caractères). Source : template-suivi-client-v4.html, ré-embarqué par deploy_v4.py wf6.
Moteur
mustache (Handlebars plante dans le sandbox n8n).
Écrit
html, plus un token et un filename qui ne servent pas en mode TEST (prévus pour le stockage du rapport).
Piège
Ne pas modifier le base64 à la main : passer par le fichier source et le script.
3Respond
Réponse · n8n-nodes-base.respondToWebhook · v1.5
Renvoie la page HTML à WF0.
Réponse
Corps = $json.html, Content-Type: text/html; charset=utf-8.
Connexions entre nodes
  • Webhook → Render mustache
  • Render mustache → Respond

sub-GA4 · sub-ga4-call

ID n8n5w2DBqpQPmlDOdld
RôleRoutage des comptes · Le standard téléphonique GA4 : il reçoit une question pour GA4 et la pose avec le bon compte Google parmi les 9 de l'agence.
DéclencheurExecute Workflow Trigger · appelé par WF1
EntréeL'email du compte, l'ID de propriété GA4 et les questions préparées par WF1.
{
  "ga4_email": "saycomgmb@gmail.com",
  "ga4_property_id": "<ID GA4>",
  "body": { "requests": [ … ] }
}
SortieLa réponse brute de GA4 (reports[]).
{ "reports": [ {"rows":[…]}, {"rows":[…]}, {"rows":[…]} ] }

Nodes (12), dans l'ordre du flux

#NodeCe qu'il fait
1When called by another wf
Déclencheur · n8n-nodes-base.executeWorkflowTrigger · v1.1
Démarre quand WF1 l'appelle, avec en entrée l'email du compte GA4, la propriété et les questions.
Entrée
inputSource = passthrough : {ga4_email, ga4_property_id, body}.
Accès
callerPolicy = workflowsFromSameOwner.
2Switch ga4_email
Condition · n8n-nodes-base.switch · v3.1
L'aiguilleur : regarde quel compte Google gère ce client et envoie la demande sur la branche de ce compte.
Règles
9 règles, égalité exacte et sensible à la casse sur ga4_email (email complet).
Piège
Pas de sortie par défaut : un email inconnu ne part nulle part, et GA4 revient vide dans le rapport.
3GA4 - saycomanalytics@gmail.com
Requête HTTP · n8n-nodes-base.httpRequest · v4.2
Interroge GA4 avec le compte saycomanalytics@gmail.com.
Credential
Google Analytics account - saycomanalytics@gmail.com
Appel
POST https://analyticsdata.googleapis.com/v1beta/properties/<ga4_property_id>:batchRunReports, corps = $json.body.
4GA4 - recuperationsaycomgmb@gmail.com
Requête HTTP · n8n-nodes-base.httpRequest · v4.2
Interroge GA4 avec le compte recuperationsaycomgmb@gmail.com.
Credential
Google Analytics account - recuperationsaycomgmb@gmail.com
Appel
POST https://analyticsdata.googleapis.com/v1beta/properties/<ga4_property_id>:batchRunReports, corps = $json.body.
5GA4 - s.ly@agence-saycom.fr
Requête HTTP · n8n-nodes-base.httpRequest · v4.2
Interroge GA4 avec le compte s.ly@agence-saycom.fr.
Credential
Google Analytics account - s.ly@agence-saycom.fr
Appel
POST https://analyticsdata.googleapis.com/v1beta/properties/<ga4_property_id>:batchRunReports, corps = $json.body.
6GA4 - saycomgmb@gmail.com
Requête HTTP · n8n-nodes-base.httpRequest · v4.2
Interroge GA4 avec le compte saycomgmb@gmail.com.
Credential
Google Analytics account - saycomgmb@gmail.com
Appel
POST https://analyticsdata.googleapis.com/v1beta/properties/<ga4_property_id>:batchRunReports, corps = $json.body.
7GA4 - saycom.mcpte@gmail.com
Requête HTTP · n8n-nodes-base.httpRequest · v4.2
Interroge GA4 avec ce compte. Attention : ce compte est signalé révoqué côté Google depuis le 16/08/2026.
Credential
Google Analytics account - saycom.mcpte@gmail.com
Appel
POST https://analyticsdata.googleapis.com/v1beta/properties/<ga4_property_id>:batchRunReports, corps = $json.body.
Point ouvert
Révoqué (invalid_grant) au 16/08/2026, à reconnecter côté Saycom.
8GA4 - sevellecyann@gmail.com
Requête HTTP · n8n-nodes-base.httpRequest · v4.2
Interroge GA4 avec le compte sevellecyann@gmail.com.
Credential
Google Analytics account - sevellecyann@gmail.com
Appel
POST https://analyticsdata.googleapis.com/v1beta/properties/<ga4_property_id>:batchRunReports, corps = $json.body.
9GA4 - ssoulabailsaycom@gmail.com
Requête HTTP · n8n-nodes-base.httpRequest · v4.2
Interroge GA4 avec le compte ssoulabailsaycom@gmail.com.
Credential
Google Analytics account - ssoulabailsaycom@gmail.com
Appel
POST https://analyticsdata.googleapis.com/v1beta/properties/<ga4_property_id>:batchRunReports, corps = $json.body.
10GA4 - a.grelier@agence-saycom.fr
Requête HTTP · n8n-nodes-base.httpRequest · v4.2
Interroge GA4 avec le compte a.grelier@agence-saycom.fr.
Credential
Google Analytics account - a.grelier@agence-saycom.fr
Appel
POST https://analyticsdata.googleapis.com/v1beta/properties/<ga4_property_id>:batchRunReports, corps = $json.body.
11GA4 - l.ly@agence-saycom.fr
Requête HTTP · n8n-nodes-base.httpRequest · v4.2
Interroge GA4 avec le compte l.ly@agence-saycom.fr.
Credential
Google Analytics account - l.ly@agence-saycom.fr
Appel
POST https://analyticsdata.googleapis.com/v1beta/properties/<ga4_property_id>:batchRunReports, corps = $json.body.
12Merge
Fusion · n8n-nodes-base.merge · v3
Rassemble les 9 branches : une seule a tourné, c'est sa réponse qui repart vers WF1.
Réglage
numberInputs = 9. À augmenter quand on ajoute un compte.
Connexions entre nodes
  • When called by another wf → Switch ga4_email
  • Switch ga4_email → GA4 - saycomanalytics@gmail.com (sortie 0)
  • GA4 - saycomanalytics@gmail.com → Merge
  • Switch ga4_email → GA4 - recuperationsaycomgmb@gmail.com (sortie 1)
  • GA4 - recuperationsaycomgmb@gmail.com → Merge
  • Switch ga4_email → GA4 - s.ly@agence-saycom.fr (sortie 2)
  • GA4 - s.ly@agence-saycom.fr → Merge
  • Switch ga4_email → GA4 - saycomgmb@gmail.com (sortie 3)
  • GA4 - saycomgmb@gmail.com → Merge
  • Switch ga4_email → GA4 - saycom.mcpte@gmail.com (sortie 4)
  • GA4 - saycom.mcpte@gmail.com → Merge
  • Switch ga4_email → GA4 - sevellecyann@gmail.com (sortie 5)
  • GA4 - sevellecyann@gmail.com → Merge
  • Switch ga4_email → GA4 - ssoulabailsaycom@gmail.com (sortie 6)
  • GA4 - ssoulabailsaycom@gmail.com → Merge
  • Switch ga4_email → GA4 - a.grelier@agence-saycom.fr (sortie 7)
  • GA4 - a.grelier@agence-saycom.fr → Merge
  • Switch ga4_email → GA4 - l.ly@agence-saycom.fr (sortie 8)
  • GA4 - l.ly@agence-saycom.fr → Merge

sub-GBP · sub-gbp-call

ID n8n9ZtvIxsyFt1hY0JT
RôleRoutage des comptes · Le standard téléphonique Google Business : il pose n'importe quelle question à l'API Google Business avec le bon compte parmi les 10 de l'agence.
DéclencheurExecute Workflow Trigger · appelé par WF2, WF3, WF0
EntréeLa partie avant le @ du compte, l'adresse de l'API et la méthode.
{
  "gbp_email_local": "recuperationsaycomgmb",
  "url": "https://mybusiness.googleapis.com/v4/…/reviews?pageSize=50",
  "method": "GET"
}
SortieLa réponse brute de Google, telle quelle.
{ "reviews": [ … ] }  ou  { "multiDailyMetricTimeSeries": [ … ] }

Nodes (13), dans l'ordre du flux

#NodeCe qu'il fait
1When called by another wf
Déclencheur · n8n-nodes-base.executeWorkflowTrigger · v1.1
Démarre quand WF2, WF3 ou WF0 l'appelle, avec une adresse d'API Google déjà prête et le compte à utiliser.
Entrée
inputSource = passthrough : {gbp_email_local, url, method, body?}.
Utilisé par
WF2 (performances), WF3 (comptes puis avis), WF0 (fiche du client) : 4 usages pour un seul sub-workflow.
2Switch gbp_email
Condition · n8n-nodes-base.switch · v3.1
L'aiguilleur : choisit la branche du compte Google qui gère la fiche du client.
Règles
10 règles, égalité exacte sur gbp_email_local, c'est-à-dire la partie avant le @ de email_gbp.
Pourquoi
La sheet stocke l'email complet, les credentials portent un nom court.
Piège
Pas de sortie par défaut : un compte inconnu ne part nulle part.
3GBP - saycom.mcpte
Requête HTTP · n8n-nodes-base.httpRequest · v4.2
Appelle l'API Google Business avec ce compte. Attention : compte signalé révoqué depuis le 16/08/2026.
Credential
Google account - saycom.mcpte
Appel
Méthode et URL reçues en entrée : $json.method (GET par défaut), $json.url.
Point ouvert
Révoqué (invalid_grant) au 16/08/2026, à reconnecter côté Saycom.
4GBP - recuperationsaycomgmb
Requête HTTP · n8n-nodes-base.httpRequest · v4.2
Appelle l'API Google Business avec le compte recuperationsaycomgmb.
Credential
Google account - recuperationsaycomgmb
Appel
Méthode et URL reçues en entrée : $json.method (GET par défaut), $json.url.
5GBP - l.ly
Requête HTTP · n8n-nodes-base.httpRequest · v4.2
Appelle l'API Google Business avec le compte l.ly.
Credential
Google account - l.ly
Appel
Méthode et URL reçues en entrée : $json.method (GET par défaut), $json.url.
6GBP - recuperationgbp
Requête HTTP · n8n-nodes-base.httpRequest · v4.2
Appelle l'API Google Business avec le compte recuperationgbp.
Credential
Google account - recuperationgbp
Appel
Méthode et URL reçues en entrée : $json.method (GET par défaut), $json.url.
7GBP - saycomgmb
Requête HTTP · n8n-nodes-base.httpRequest · v4.2
Appelle l'API Google Business avec le compte saycomgmb.
Credential
Google account - saycomgmb
Appel
Méthode et URL reçues en entrée : $json.method (GET par défaut), $json.url.
8GBP - s.ly
Requête HTTP · n8n-nodes-base.httpRequest · v4.2
Appelle l'API Google Business avec le compte s.ly.
Credential
Google account - s.ly
Appel
Méthode et URL reçues en entrée : $json.method (GET par défaut), $json.url.
9GBP - a.grelier
Requête HTTP · n8n-nodes-base.httpRequest · v4.2
Appelle l'API Google Business avec le compte a.grelier.
Credential
Google account - a.grelier
Appel
Méthode et URL reçues en entrée : $json.method (GET par défaut), $json.url.
10GBP - ssoulabailsaycom
Requête HTTP · n8n-nodes-base.httpRequest · v4.2
Appelle l'API Google Business avec le compte ssoulabailsaycom.
Credential
Google account - ssoulabailsaycom
Appel
Méthode et URL reçues en entrée : $json.method (GET par défaut), $json.url.
11GBP - sevellecyann
Requête HTTP · n8n-nodes-base.httpRequest · v4.2
Appelle l'API Google Business avec le compte sevellecyann.
Credential
Google account - sevellecyann
Appel
Méthode et URL reçues en entrée : $json.method (GET par défaut), $json.url.
12GBP - test
Requête HTTP · n8n-nodes-base.httpRequest · v4.2
Branche du compte nommé « test » : appelle l'API Google Business avec ce credential.
Credential
Google account - test
Appel
Méthode et URL reçues en entrée : $json.method (GET par défaut), $json.url.
13Merge
Fusion · n8n-nodes-base.merge · v3
Rassemble les 10 branches et renvoie la réponse de celle qui a tourné.
Réglage
numberInputs = 10.
Connexions entre nodes
  • When called by another wf → Switch gbp_email
  • Switch gbp_email → GBP - saycom.mcpte (sortie 0)
  • GBP - saycom.mcpte → Merge
  • Switch gbp_email → GBP - recuperationsaycomgmb (sortie 1)
  • GBP - recuperationsaycomgmb → Merge
  • Switch gbp_email → GBP - l.ly (sortie 2)
  • GBP - l.ly → Merge
  • Switch gbp_email → GBP - recuperationgbp (sortie 3)
  • GBP - recuperationgbp → Merge
  • Switch gbp_email → GBP - saycomgmb (sortie 4)
  • GBP - saycomgmb → Merge
  • Switch gbp_email → GBP - s.ly (sortie 5)
  • GBP - s.ly → Merge
  • Switch gbp_email → GBP - a.grelier (sortie 6)
  • GBP - a.grelier → Merge
  • Switch gbp_email → GBP - ssoulabailsaycom (sortie 7)
  • GBP - ssoulabailsaycom → Merge
  • Switch gbp_email → GBP - sevellecyann (sortie 8)
  • GBP - sevellecyann → Merge
  • Switch gbp_email → GBP - test (sortie 9)
  • GBP - test → Merge

3. D'où vient chaque chiffre du rapport

Une ligne par source de données : le workflow qui la lit, et les variables du rapport qu'elle remplit. Les noms en majuscules sont les cases du template.

SourceLue parVariables du rapport alimentées
EXPORT_SAYRANK, Feuille 1
Google Sheets, compte saycomtech@gmail.com
WF0 Lookup unique
OYzS27t6hRwFMiEd
WF1
50mry2S2YpnBvAvn
WF2
Y9c44O0aHTlZ5tFH
WF3
g9KZrahbuv4Jcd4W
NOM_CLIENT NOM_CLIENT_COURT REF_DOSSIER QR_GOOGLE_QUERY
Donne aussi tout le routage : email_ga4, email_gbp, ID GA4, ID GBP, site_ga4, domain, colonne activité.
Liste entreprise Sayrank
onglet de la même sheet
WF0 Read Sayrank sheet
OYzS27t6hRwFMiEd

Sert seulement à retrouver le codecrm quand la colonne CRM de la Feuille 1 est vide.
Google Analytics 4
9 comptes Google
WF1
50mry2S2YpnBvAvn
sub-ga4-call
5w2DBqpQPmlDOdld
USERS_N USERS_N1 DELTA_USERS SESSIONS_N SESSIONS_N1 DELTA_SESSIONS PAGES_PER_SESSION_N AVG_DURATION_N ENGAGEMENT_TIME_N DEVICE_MOBILE_PCT DEVICE_DESKTOP_PCT DEVICE_TABLET_PCT SOURCE_1..4_NAME / _N HAS_GA4
Google Business Profile, performances
10 comptes Google
WF2
Y9c44O0aHTlZ5tFH
sub-gbp-call
9ZtvIxsyFt1hY0JT
APPARITIONS_N APPARITIONS_PAR_MOIS CALLS_N CALLS_PAR_MOIS WEBSITE_CLICKS_N DIRECTIONS_N CONTACTS_TOTAL_N HAS_GBP
Google Business Profile, avis
API v4 reviews
WF3
g9KZrahbuv4Jcd4W
sub-gbp-call
9ZtvIxsyFt1hY0JT
NOTE_GOOGLE NB_AVIS HAS_REVIEWS
Google Business Profile, fiche
API Business Information
WF0 Call GBP location
OYzS27t6hRwFMiEd
sub-gbp-call
9ZtvIxsyFt1hY0JT
VILLE ACTIVITE ZONES_LABEL
La catégorie sert aussi de repli pour la recherche des concurrents.
API SayRank
positions des mots-clés
WF4
sYhzBXw55NH2LywD
NB_KEYWORDS_TOTAL KW_PAGE1_COUNT KW_PAGE1_PCT KW_TOP1_COUNT KW_TOP_1..3_KEYWORD / _RANK KEYWORDS HAS_KEYWORDS
Base Postgres submissions
formulaires Elementor des sites
WF0 Emails count
OYzS27t6hRwFMiEd
MAILS_N CONTACTS_TOTAL_N
SerpApi (Google Maps)
cache 30 jours
WF0 Call SerpApi
OYzS27t6hRwFMiEd
CONCURRENTS CONCURRENTS_QUERY HAS_CONCURRENTS
QualiAvis
pas d'API : colonnes note_moyenne_ql et nombre_note_ql de la sheet
WF0 Lookup unique
OYzS27t6hRwFMiEd
QUALIAVIS_NOTE QUALIAVIS_COUNT HAS_QUALIAVIS
Calculé par WF0
à partir des dates
WF0 Compute derived
OYzS27t6hRwFMiEd
PERIODE PERIODE_NOM TYPE_RAPPORT PERIODE_DEBUT PERIODE_FIN DATE_RAPPORT YEAR_N YEAR_N1 *_POL (flèches et couleurs)

Les variables sont toutes calculées dans le node Compute derived de WF0, à partir des réponses brutes. Les flags HAS_… affichent ou masquent un bloc entier du rapport.

4. Erreurs et dégradation

Deux familles : la demande est fausse (le système refuse avec un message clair), ou une source est en panne (le rapport sort quand même).

1. La demande est refusée

Réponse JSON {error, message, field, details}. Le front doit se baser sur error, qui ne change jamais.

HTTPCode errorCas
400missing_fieldUn champ obligatoire manque (nom, dates).
400invalid_date_formatUne date n'est pas au format AAAA-MM-JJ.
400invalid_date_rangeFin avant début, date future, période de plus de 730 jours, ou N et N-1 qui se chevauchent.
400invalid_period_monthsperiod_months n'est pas 3, 6 ou 12.
404company_not_foundLe client est introuvable dans EXPORT_SAYRANK.
422ga4_not_connectedemail_ga4 ou ID GA4 vide dans la sheet.
422gbp_not_connectedemail_gbp ou ID GBP vide dans la sheet.
422codecrm_not_resolvedPas de codecrm : ni colonne CRM en Feuille 1, ni ligne dans la Liste entreprise Sayrank.
500(erreur n8n)Panne non prévue : sheet illisible après 3 essais, ou WF6 indisponible.

2. Une source tombe : rapport dégradé, pas d'échec

Les 6 appels de collecte et l'appel SerpApi sont réglés sur onError = continueRegularOutput. Une source en panne laisse sa case vide (— ou 0) ou masque son bloc.

GA4 en panneLes chiffres de trafic affichent —, HAS_GA4 passe à faux.
GBP performances en panneApparitions, appels et clics affichent 0.
Avis Google en panneNote à —, 0 avis, le bloc avis est masqué.
SayRank en panne0 mot-clé : la section SEO est masquée.
Fiche GBP en panneVille et activité à —, la recherche concurrents n'a plus de catégorie de repli.
Base e-mails en panne0 e-mail compté.
SerpApi en panne ou quota épuiséLe bloc « Et vos concurrents ? » est masqué.
WF6 (rendu) en panneBloquant : pas de rapport, erreur renvoyée.

5. Maintenance

Ajouter un compte Google

Un nouveau client branché sur un compte Google que n8n ne connaît pas encore.

  1. Créer le credential dans n8n : Google Analytics OAuth2 pour GA4, Google OAuth2 API pour GBP. Garder la convention de nom : Google Analytics account - <email> et Google account - <partie avant @>.
  2. Dans sub-ga4-call : ajouter une règle au Switch ga4_email (égalité exacte, sensible à la casse, sur l'email complet), dupliquer un node GA4 - …, lui donner le nouveau credential, le brancher sur une nouvelle entrée du Merge (augmenter son nombre d'entrées).
  3. Dans sub-gbp-call : même chose sur le Switch gbp_email, mais la règle compare la partie avant le @ (gbp_email_local).
  4. Remplir email_ga4 / email_gbp et les IDs du client dans EXPORT_SAYRANK, puis lancer un rapport test.

Attention : les Switch n'ont pas de sortie par défaut. Un email absent des règles ne lève pas d'erreur, la source revient simplement vide dans le rapport.

Modifier le template ou WF0

  1. Le template rendu est template-suivi-client-v4.html, mais il est régénéré par retokenize_v4.py depuis template-v4-new.html. Une retouche faite à la main sur le fichier rendu est perdue à la régénération : passer par un rep() dans le script.
  2. Déployer avec deploy_v4.py wf6 (ou wf0, form). Le script ré-encode le template en base64 dans Render mustache, fait un PUT sur l'ID live (pas de doublon) et écrit d'abord un backup backup-live-<ID>-<slot>.json. Option --dry-run pour voir sans écrire.
  3. Les identifiants n8n se chargent depuis .env (jamais commité).
  4. Le backup est l'état d'avant le déploiement. Pour auditer ce qui tourne vraiment, relire le workflow live via l'API (GET /api/v1/workflows/<id>).

deploy_v4.py ne couvre que WF0, WF6 et WF-Form. WF1 à WF4 et les deux sub-workflows se modifient dans l'éditeur n8n (ou via redeploy.py).

Règles à ne pas casser

6. Points ouverts

7. Glossaire

GA4
Google Analytics 4 : l'outil de Google qui mesure les visites du site du client.
GBP
Google Business Profile : la fiche de l'entreprise sur Google et Maps (appels, itinéraires, avis).
codecrm
L'identifiant du client dans SayRank (format CL…). À ne pas confondre avec l'ID Saycom de la sheet.
Webhook
Une adresse web qui déclenche un workflow quand on lui envoie une requête. Exemple : /webhook/kpi/report.
Sub-workflow
Un petit workflow réutilisable appelé par d'autres, ici pour choisir le bon compte Google.
Mustache
Le moteur de template : il remplace chaque {{VARIABLE}} du modèle HTML par sa valeur.
Credential
Un accès enregistré dans n8n (compte Google, base, clé API). Les workflows le désignent par son nom.
EXPORT_SAYRANK
La Google Sheet de référence : une ligne par client, avec ses comptes et ses IDs.
N / N-1
La période étudiée (N) et la même période un an plus tôt (N-1), pour comparer.