Tous les articles

Consent Mode v2 sur Shopify : quelle solution pour la bannière cookie ?

août 20, 2026 · Jeremy

En bref : sur Shopify, un consentement mal branché, c’est du budget publicitaire dépensé à l’aveugle : conversions qui ne remontent pas, données qui fuitent, décisions faussées. Cet article explique pourquoi la plateforme est un cas à part, compare les deux configurations les plus courantes (bannière native et Cookiebot, toutes deux avec GTM), et vous montre où le consentement se casse, checkout compris.

Le consentement est devenu la bête noire de beaucoup de marchands, et pour une bonne raison : sa complexité ne cesse d’augmenter. Entre le RGPD, le Consent Mode v2 de Google, les CMP et les spécificités de chaque plateforme, ce qui devrait n’être qu’une bannière est devenu un système à part entière, où le moindre détail mal réglé fait fuiter des données ou, à l’inverse, casse votre mesure.

Si vous êtes e-commerçant, il y a de grandes chances que vous utilisiez Shopify. Et on n’imagine pas le nombre de boutiques qui rencontrent des problèmes de consentement, en particulier sur le paramétrage.

L’idée de cet article n’est pas de vous réexpliquer le fonctionnement du Consent Mode ou ses impacts (j’ai déjà écrit un article pour introduire le Consent Mode), mais de vous donner un niveau de connaissance suffisant pour en parler avec vos équipes ou avec la personne qui s’occupe de votre tracking.

Ça vous évitera peut-être de dépenser des centaines, voire des milliers d’euros dans le vide.

Un peu de lexique avant de commencer

Quelques notions clés, celles qui reviennent tout au long de l’article.

La Customer Privacy API

C’est le cœur du consentement sur Shopify. Elle stocke l’état de consentement du visiteur (analytics, marketing, préférences) et le met à disposition des pixels. Votre bannière, qu’elle soit native ou pilotée par une CMP, ne fait au fond que renseigner cette API.

Les Customer Events (Web Pixels)

C’est l’environnement isolé où s’exécutent les pixels tiers sur Shopify. Vous y ajoutez des pixels personnalisés (Settings > Customer events) qui s’abonnent aux événements du site et respectent le consentement. C’est le mécanisme recommandé par Shopify à la place des scripts posés en dur, notamment parce qu’il fonctionne aussi sur le checkout.

La Checkout extensibility

C’est la nouvelle technologie avec laquelle Shopify a refondu son tunnel de commande. Avant, on pouvait glisser des scripts « à la main » dans le checkout (les fameux additional scripts). Désormais, ce tunnel est verrouillé et sécurisé : on n’y ajoute plus de code librement, on passe par des mécanismes encadrés comme les Web Pixels. En clair, votre tracking au moment de l’achat, l’étape la plus importante, ne s’installe plus comme avant, et c’est souvent là que les ennuis commencent.

Apps & sales channels

Pour utiliser un outil sur Shopify, vous devez l’installer directement dans la boutique. Vous entendrez souvent parler de « sales channels » (canaux de vente) : c’est là que se branchent des outils comme Google (GA4, Google Ads, Merchant Center) ou Meta. Conséquence : une partie de votre tracking peut vivre dans ces apps, à l’intérieur de Shopify.

Google Tag Manager (GTM)

Un gestionnaire de balises : au lieu de coller vos scripts de suivi un par un dans le site, vous les centralisez et les pilotez depuis une seule interface. C’est le dénominateur commun des deux cas de cet article, et c’est aussi ce qui place une partie de votre tracking en dehors de Shopify.

Pourquoi Shopify est un cas à part

L’accès au code est limité, surtout sur le tunnel de commande. Le checkout est verrouillé : Shopify a basculé sur la checkout extensibility et retire progressivement les anciens scripts de checkout.

Qu’est-ce que ça change ? Les pixels tiers ne s’exécutent pas librement : ils tournent dans un environnement isolé, les Customer Events (Web Pixels), qui respectent le consentement défini par la Customer Privacy API de Shopify.

Autrement dit, la vraie question n’est pas seulement « quelle bannière », mais « comment le consentement circule jusqu’à vos tags Google, y compris au moment de l’achat ».

Je vais donc vous présenter les deux cas où, d’expérience, il y a le plus de problèmes. Ces deux cas ont un dénominateur commun : l’utilisation de Google Tag Manager, et donc une partie de votre tracking gérée en dehors de Shopify.

Je ne rentrerai pas dans les détails de l’installation de GTM sur Shopify, il y a assez d’articles là-dessus, et si vous ne voulez pas l’implémenter vous-même, contactez-moi.

Cas 1 : bannière native Shopify avec tracking via GTM

Première configuration, la plus simple en apparence : on utilise la bannière de consentement native de Shopify (dans les réglages Customer privacy) et on ajoute GTM à la boutique.

Ce qui fonctionne : la bannière native pilote la Customer Privacy API, et les Customer Events respectent ce consentement. Pour tout ce qui est propre à Shopify (l’analytics de la boutique, ses KPI natifs), ça marche.

Là où ça coince : GTM, lui, ne reçoit pas automatiquement les signaux de la Customer Privacy API. Il faut explicitement relier les deux pour que le Consent Mode v2 reçoive les bons états (accordé / refusé).

La solution consiste donc à écouter les valeurs de la Customer Privacy API et à les transformer en signaux de consentement compréhensibles par les balises de GTM. Concrètement, on ajoute dans GTM une balise « traductrice » qui lit l’état Shopify et le convertit en Consent Mode. Ça paraît simple, mais il faut faire attention à plusieurs situations.

Par exemple, à chaque page, GTM repart d’un consentement « refusé » par défaut, puis lit l’état réel. Si vos balises se déclenchent avant que cet état soit lu, elles partent trop tôt, et vous obtenez une fuite si le visiteur avait refusé. La parade : faire tourner la traduction du consentement le plus tôt possible, sur un déclenchement dédié avant tout le reste, et, pour un visiteur déjà venu, lire directement le cookie de consentement de Shopify pour poser le bon état immédiatement. On peut aussi régler ce problème d’ordre avec des trigger groups dans GTM, en combinant l’événement et le consent update sur les balises concernées.

Dans tous les cas, vous aurez besoin de deux balises HTML pour écouter et transmettre le consentement, sur le storefront comme sur le checkout.

En conclusion, le cas 1 fonctionne très bien une fois propre, mais il additionne les points sensibles (l’ordre de chargement, les deux environnements, les balises HTML, le referrer). C’est le cas le plus délicat, et celui où une relecture experte évite des semaines de données faussées.

Cas 2 : bannière Cookiebot (via GTM) + Shopify

Deuxième configuration, quand on veut plus de contrôle : une CMP (plateforme de gestion du consentement) comme Cookiebot, mise en place via GTM. L’intérêt : un bandeau plus fin et plus conforme, un blocage automatique des scripts avant consentement, et un mapping propre vers le Consent Mode v2. Pour un marchand qui tient à sa conformité, c’est solide.

Le hic, c’est que Cookiebot vit dans GTM. Il pilote donc très bien vos balises GTM, mais il ne parle pas à Shopify. Or l’analytics natif de Shopify et les apps installées dans la boutique (Klaviyo, par exemple) n’écoutent pas Cookiebot : ils n’écoutent que la Customer Privacy API de Shopify. Sans signal de consentement dans cette API, l’analytics Shopify ne se déclenche pas, et vos apps peuvent ne rien recevoir (ou, à l’inverse, tourner sans consentement).

Le point le plus critique, c’est le checkout : sur le tunnel de commande et la page de confirmation, ni votre thème ni Cookiebot ne sont chargés. Le pixel qui mesure l’achat ne peut lire le consentement que via la Customer Privacy API (le cookie _tracking_consent). Il faut donc un pont qui écrive, dès le storefront, le choix fait dans Cookiebot dans cette API, pour que le checkout en hérite.

La solution : un script à poser le plus haut possible dans layout/theme.liquid, dans le <head>, juste après content_for_header. En résumé, il lit le consentement Cookiebot (avec une lecture rapide du cookie pour un visiteur déjà venu, afin d’éviter un « refusé » temporaire), applique un refus par défaut pour un vrai nouveau visiteur, puis l’écrit dans la Customer Privacy API de Shopify et se met à jour à chaque changement de choix.

Deux prérequis avant de le coller : désactiver la bannière native de Shopify (sinon deux systèmes écrivent dans la même API et se contredisent) et ne pas l’installer en Custom Pixel (contexte sandboxé où Cookiebot n’existe pas). À noter, ce pont gère le consentement côté Shopify et checkout, il ne remplace pas le câblage du Consent Mode dans GTM pour vos tags storefront : les deux se complètent.

À coller dans layout/theme.liquid, dans le <head>, juste après content_for_header :

<script>
(function () {
  "use strict";

  // Passer a true pour tracer les ecritures en console pendant la recette.
  var DEBUG = false;

  var apiReady = false;
  var lastWritten = null;

  function log() {
    if (DEBUG) console.log.apply(console, ["[consent]"].concat([].slice.call(arguments)));
  }

  /* ------------------------------------------------------------------
     1) Source de verite prioritaire : l'objet Cookiebot, quand il est la.
     ------------------------------------------------------------------ */
  function fromCookiebotObject() {
    var cb = window.Cookiebot;
    if (!cb || !cb.hasResponse || !cb.consent) return null;
    var c = cb.consent;
    return {
      analytics: c.statistics === true,
      marketing: c.marketing === true,
      preferences: c.preferences === true
    };
  }

  /* ------------------------------------------------------------------
     2) Fallback synchrone : le cookie first-party CookieConsent.
     Lisible des la premiere ligne, sans attendre la chaine
     app embed -> GTM -> balise Cookiebot -> script Cookiebot.
     C'est ce qui evite d'ecrire un refus temporaire a un visiteur
     recurrent qui avait accepte (et donc la regeneration de _shopify_y).
     Le format n'est pas du JSON valide et n'est pas contractuel cote
     Cookiebot : parsing tolerant, et abandon si la forme est inattendue.
     ------------------------------------------------------------------ */
  function fromCookie() {
    var m = document.cookie.match(/(?:^|;\s*)CookieConsent=([^;]*)/);
    if (!m) return null;

    var raw;
    try { raw = decodeURIComponent(m[1]); } catch (e) { raw = m[1]; }

    // Garde-fou : si on ne reconnait pas la structure, on ne devine pas.
    if (!/necessary\s*:/.test(raw)) return null;

    function flag(key) {
      return new RegExp(key + "\\s*:\\s*true").test(raw);
    }

    return {
      analytics: flag("statistics"),
      marketing: flag("marketing"),
      preferences: flag("preferences")
    };
  }

  /* ------------------------------------------------------------------
     3) Resolution : objet Cookiebot > cookie > refus par defaut.
     Le refus par defaut ne concerne qu'un visiteur sans cookie
     CookieConsent, donc un vrai nouveau visiteur : rien a casser.
     ------------------------------------------------------------------ */
  function resolveConsent() {
    return fromCookiebotObject()
        || fromCookie()
        || { analytics: false, marketing: false, preferences: false };
  }

  function toPayload(c) {
    return {
      analytics: c.analytics,
      marketing: c.marketing,
      preferences: c.preferences,
      // Aligne sur marketing. C'est une decision juridique (CCPA/CPRA),
      // pas technique : a valider avec qui gere la conformite.
      sale_of_data: c.marketing
    };
  }

  /* ------------------------------------------------------------------
     4) Anti-churn : ne pas reecrire un etat identique.
     currentVisitorConsent() renvoie des chaines "yes" / "no" / "".
     ------------------------------------------------------------------ */
  function alreadyMatches(payload) {
    var api = window.Shopify && window.Shopify.customerPrivacy;
    if (!api || typeof api.currentVisitorConsent !== "function") return false;

    var current;
    try { current = api.currentVisitorConsent(); } catch (e) { return false; }
    if (!current) return false;

    var keys = ["analytics", "marketing", "preferences", "sale_of_data"];
    for (var i = 0; i < keys.length; i++) {
      var expected = payload[keys[i]] ? "yes" : "no";
      if (String(current[keys[i]] || "") !== expected) return false;
    }
    return true;
  }

  /* ------------------------------------------------------------------
     5) Ecriture
     ------------------------------------------------------------------ */
  function applyConsent() {
    // Si l'API Shopify n'est pas prete, on ne fait rien : le callback de
    // loadFeatures rappellera applyConsent() de toute facon.
    if (!apiReady) { log("API pas prete, differe"); return; }

    var api = window.Shopify && window.Shopify.customerPrivacy;
    if (!api || typeof api.setTrackingConsent !== "function") return;

    var payload = toPayload(resolveConsent());
    var signature = JSON.stringify(payload);

    if (signature === lastWritten) { log("inchange"); return; }
    if (alreadyMatches(payload)) { lastWritten = signature; log("deja a jour"); return; }

    try {
      api.setTrackingConsent(payload, function (result) {
        if (result && result.error) {
          console.warn("[consent] Shopify a refuse la mise a jour :", result.error);
          return;
        }
        lastWritten = signature;
        log("applique", payload);
      });
    } catch (e) {
      console.warn("[consent] setTrackingConsent a echoue :", e);
    }
  }

  /* ------------------------------------------------------------------
     6) Ecouteurs Cookiebot, poses AVANT loadFeatures.
     OnConsentReady : restitution ou premier choix.
     OnAccept / OnDecline : changement via la banniere reouverte.
     ------------------------------------------------------------------ */
  var events = ["CookiebotOnConsentReady", "CookiebotOnAccept", "CookiebotOnDecline"];
  for (var i = 0; i < events.length; i++) {
    window.addEventListener(events[i], applyConsent);
  }

  /* ------------------------------------------------------------------
     7) Chargement de l'API Shopify.
     window.Shopify est defini par le script injecte par
     content_for_header. Plutot que d'exiger un emplacement precis, on
     attend son apparition : le snippet fonctionne alors quel que soit
     son emplacement dans le theme (head ou fin de body).
     ------------------------------------------------------------------ */
  function whenShopifyReady(callback) {
    if (window.Shopify && typeof window.Shopify.loadFeatures === "function") {
      callback();
      return;
    }
    var started = Date.now();
    var timer = setInterval(function () {
      if (window.Shopify && typeof window.Shopify.loadFeatures === "function") {
        clearInterval(timer);
        callback();
      } else if (Date.now() - started > 10000) {
        clearInterval(timer);
        console.warn("[consent] window.Shopify.loadFeatures introuvable apres 10s : boutique headless, ou snippet charge hors du theme ?");
      }
    }, 50);
  }

  whenShopifyReady(function () {
    window.Shopify.loadFeatures(
      [{ name: "consent-tracking-api", version: "0.1" }],
      function (error) {
        if (error) {
          console.warn("[consent] consent-tracking-api non chargee :", error);
          return;
        }
        apiReady = true;
        applyConsent(); // ecrit l'etat reel des maintenant (cookie ou objet)
      }
    );
  });
})();
</script>

C’est le genre de brique où un détail casse tout sans prévenir : un ordre de chargement, un visiteur récurrent qui bascule à tort en refus, une double écriture dans l’API. Ce script gère ces cas, mais si vous avez un doute sur votre installation ou votre tracking sur Shopify, c’est précisément ce que je vérifie et corrige.

Ma reco : quelle configuration selon votre boutique

Pas de réponse universelle. Voici les deux configurations mises en regard, sur les critères qui comptent vraiment.

CritèreBannière native + GTMCookiebot (CMP) + GTM
Bandeau & conformitéStandard, correctFin, personnalisable, blocage avant consentement
Lien natif avec Shopify (analytics, apps)Direct : la bannière pilote la Customer Privacy APIIndirect : nécessite un pont vers la Customer Privacy API
Consent Mode dans GTMÀ câbler soi-même (storefront + checkout)Mapping propre et direct
Complexité de mise en placeÉlevée (ordre, 2 environnements, HTML, referrer)Moyenne (le pont à poser)
CoûtInclusAbonnement d’une CMP
Quand la privilégierTracking mixte : une partie via les apps ShopifyTout le tracking passe par GTM

Le point contre-intuitif : la bannière native, souvent perçue comme « la solution simple », est en réalité le montage le plus délicat dès qu’il faut alimenter GTM proprement, à cause des points sensibles vus au cas 1.

Ma conclusion

Sur Shopify, il n’y a pas de bonne ou de mauvaise bannière, parce que ce n’est pas elle qui compte : ce qui compte, c’est que le consentement arrive jusqu’à vos tags et à vos apps, checkout inclus. Aucune option n’est plus simple qu’une autre dans l’absolu, tout dépend de votre situation. Le vrai critère de choix, c’est la place de GTM dans votre tracking : si tout passe par Tag Manager, privilégiez une bannière externe ; dans le cas d’un tracking mixte, la bannière native peut être la meilleure option.

Le consentement Shopify vous donne des sueurs froides ?

Contactez-moi pour un audit gratuit