Votre CMP recueille le consentement et affiche la bannière. TrustRGPD se place en aval : à chaque décision de l'utilisateur, vous envoyez l'état du consentement à l'API, qui l'archive de manière immuable et horodatée. L'intégration tient en trois étapes, identiques quelle que soit la CMP — seul l'évènement à écouter change.

1. Principe d'architecture

Le flux est toujours le même :

Navigateur                    Votre serveur                  TrustRGPD
─────────                     ─────────────                  ─────────
CMP : consentement modifié
   │  sendConsentProof(state)
   ▼
POST /consent-proof  ───────►  reçoit l'état + résout
(sans clé, first-party)        un user_identifier stable
                                  │  ajoute Authorization: Bearer <clé>
                                  ▼
                              POST https://trustrgpd.newfutur.com/api/v1/log  ───►  201 Created
                                                                                    (preuve archivée)

Avantages : la clé reste sur le serveur, vous maîtrisez l'identifiant utilisateur envoyé, et vous pouvez enrichir ou filtrer l'appel (journalisation, quotas internes). L'appel côté navigateur est asynchrone : aucun impact sur le temps de chargement.

Étape 1 — Créer une clé API

  1. Connectez-vous à votre espace client.
  2. Carte « Vos clés API » → Créer une clé.
  3. Copiez la clé complète (format trgpd_<prefix>_<secret>) : elle n'est affichée qu'une seule fois.
  4. Stockez-la comme un secret d'environnement sur votre serveur (variable TRUSTRGPD_API_KEY), jamais dans un dépôt de code.

Limites à connaître : 120 requêtes/minute par clé, corps de requête ≤ 64 Kio, quota mensuel selon l'offre. Une clé révoquée cesse immédiatement de fonctionner.

Étape 2 — Le point de collecte sur votre serveur

Exposez une route POST /consent-proof sur votre domaine. Elle reçoit l'état du consentement depuis le navigateur, y ajoute la clé, et relaie l'appel à POST /api/v1/log. Exemples dans deux langages — la logique est transposable à n'importe quel back-end.

Node.js (Express)

// routes/consent-proof.js  —  sur VOTRE serveur
app.post("/consent-proof", express.json({ limit: "16kb" }), function (req, res) {
  var b = req.body || {};
  if (!b.user_identifier) { return res.sendStatus(400); }

  fetch("https://trustrgpd.newfutur.com/api/v1/log", {
    method: "POST",
    headers: {
      "Authorization": "Bearer " + process.env.TRUSTRGPD_API_KEY,  // clé côté serveur uniquement
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      user_identifier: b.user_identifier,
      framework:  b.framework  || "custom",
      consent:    b.consent    || undefined,
      tcf_string: b.tcf_string || undefined,
      gpp_string: b.gpp_string || undefined,
      page_url:   b.page_url   || undefined
    })
  })
  .then(function (r) { res.sendStatus(r.status === 201 ? 204 : 502); })
  .catch(function ()  { res.sendStatus(502); });
});

PHP

<?php
// consent-proof.php  —  sur VOTRE serveur
header("Content-Type: application/json");
$in = json_decode(file_get_contents("php://input"), true) ?: [];
if (empty($in["user_identifier"])) { http_response_code(400); exit; }

$payload = json_encode([
  "user_identifier" => $in["user_identifier"],
  "framework"       => $in["framework"]   ?? "custom",
  "consent"         => $in["consent"]     ?? null,
  "tcf_string"      => $in["tcf_string"]  ?? null,
  "gpp_string"      => $in["gpp_string"]  ?? null,
  "page_url"        => $in["page_url"]    ?? null,
]);

$ch = curl_init("https://trustrgpd.newfutur.com/api/v1/log");
curl_setopt_array($ch, [
  CURLOPT_POST           => true,
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER     => [
    "Authorization: Bearer " . getenv("TRUSTRGPD_API_KEY"),  // jamais exposée au navigateur
    "Content-Type: application/json",
  ],
  CURLOPT_POSTFIELDS     => $payload,
]);
curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
http_response_code($code === 201 ? 204 : 502);

Étape 3 — Le script navigateur

Hébergez ce fichier sur votre domaine et chargez-le sur toutes les pages. Il expose une fonction globale sendConsentProof(state) que les sections CMP ci-dessous appellent. Il résout un identifiant stable et non nominatif, et évite les envois en double.

/* /assets/consent-proof.js  —  sur VOTRE domaine */
(function (w, d) {
  var ENDPOINT = "/consent-proof";   // la route de l'étape 2
  var last = null;

  function stableUserId() {
    // 1) utilisateur connecté : renvoyez votre identifiant interne (déjà pseudonyme)
    if (w.APP && w.APP.userId) { return "u:" + w.APP.userId; }
    // 2) sinon : identifiant first-party persistant, non nominatif
    var m = d.cookie.match(/(?:^|;\s*)trg_uid=([^;]+)/);
    if (m) { return decodeURIComponent(m[1]); }
    var id = (w.crypto && w.crypto.randomUUID)
      ? w.crypto.randomUUID()
      : "a" + Date.now().toString(16) + Math.random().toString(16).slice(2);
    d.cookie = "trg_uid=" + encodeURIComponent(id) + ";path=/;max-age=31536000;SameSite=Lax";
    return id;
  }

  w.sendConsentProof = function (state) {
    state = state || {};
    var body = JSON.stringify({
      user_identifier: stableUserId(),
      page_url: location.href,
      framework: state.framework || "custom",
      consent: state.consent || undefined,
      tcf_string: state.tcf_string || undefined,
      gpp_string: state.gpp_string || undefined
    });
    if (body === last) { return; }   // rien de neuf depuis le dernier envoi
    last = body;

    if (navigator.sendBeacon) {
      navigator.sendBeacon(ENDPOINT, new Blob([body], { type: "application/json" }));
    } else {
      fetch(ENDPOINT, {
        method: "POST", headers: { "Content-Type": "application/json" },
        body: body, keepalive: true
      }).catch(function () {});
    }
  };
})(window, document);

state.consent est un objet { "signal": "granted" | "denied" }. Vous pouvez aussi (ou à la place) transmettre state.tcf_string (chaîne TCF v2.2) ou state.gpp_string (chaîne GPP). Renseignez framework avec consent_mode_v2, tcf_v2_2, gpp ou custom.

Méthode universelle — Google Consent Mode / dataLayer

La plupart des CMP de cette page peuvent piloter Google Consent Mode v2. Si c'est votre cas, cet observateur unique suffit : il intercepte chaque gtag('consent', 'update', …) et le transmet à TrustRGPD, sans dépendre d'une CMP précise.

(function () {
  var dl = window.dataLayer = window.dataLayer || [];
  var push = dl.push;
  dl.push = function () {
    var r = push.apply(dl, arguments);
    for (var i = 0; i < arguments.length; i++) {
      var a = arguments[i];
      if (a && a[0] === "consent" && (a[1] === "update" || a[1] === "default")) {
        var g = a[2] || {};
        window.sendConsentProof({
          framework: "consent_mode_v2",
          consent: {
            ad_storage:              g.ad_storage,
            ad_user_data:            g.ad_user_data,
            ad_personalization:      g.ad_personalization,
            analytics_storage:       g.analytics_storage,
            functionality_storage:   g.functionality_storage,
            personalization_storage: g.personalization_storage,
            security_storage:        g.security_storage
          }
        });
      }
    }
    return r;
  };
})();

Les clés à undefined sont automatiquement omises de l'envoi ; seuls les signaux réellement définis par la CMP sont archivés.

OneTrust

OneTrust appelle OptanonWrapper() à chaque mise à jour et expose l'évènement OneTrust.OnConsentChanged. Les groupes actifs sont listés dans OnetrustActiveGroups (ids type C0001…C0005).

function onetrustToProof() {
  var g = "," + (window.OnetrustActiveGroups || "") + ",";
  var has = function (id) { return g.indexOf("," + id + ",") !== -1; };
  window.sendConsentProof({
    framework: "consent_mode_v2",
    consent: {
      security_storage:      "granted",                       // C0001 strictement nécessaires
      analytics_storage:     has("C0002") ? "granted" : "denied",  // performance
      functionality_storage: has("C0003") ? "granted" : "denied",  // fonctionnels
      personalization_storage: has("C0003") ? "granted" : "denied",
      ad_storage:            has("C0004") ? "granted" : "denied",   // publicité / ciblage
      ad_user_data:          has("C0004") ? "granted" : "denied",
      ad_personalization:    has("C0004") ? "granted" : "denied"
    }
  });
}

// à chaque changement + au chargement
window.addEventListener("consent.onetrust", onetrustToProof);   // certaines versions
if (window.OneTrust && OneTrust.OnConsentChanged) { OneTrust.OnConsentChanged(onetrustToProof); }
window.OptanonWrapper = function () { onetrustToProof(); };

Cookiebot

Cookiebot déclenche des évènements sur window (CookiebotOnAccept, CookiebotOnDecline, CookiebotOnConsentReady) et expose l'état courant dans Cookiebot.consent.

function cookiebotToProof() {
  var c = (window.Cookiebot && window.Cookiebot.consent) || {};
  var yn = function (b) { return b ? "granted" : "denied"; };
  window.sendConsentProof({
    framework: "consent_mode_v2",
    consent: {
      security_storage:        "granted",
      functionality_storage:   yn(c.preferences),
      personalization_storage: yn(c.preferences),
      analytics_storage:       yn(c.statistics),
      ad_storage:              yn(c.marketing),
      ad_user_data:            yn(c.marketing),
      ad_personalization:      yn(c.marketing)
    }
  });
}

window.addEventListener("CookiebotOnConsentReady", cookiebotToProof);
window.addEventListener("CookiebotOnAccept",       cookiebotToProof);
window.addEventListener("CookiebotOnDecline",      cookiebotToProof);

Usercentrics

Usercentrics (CMP Web) émet l'évènement ucEvent puis consent_status, et fournit l'état des services via UC_UI.getServicesBaseInfo(). Repérez vos services par leur nom (Google Analytics, Google Ads…) et lisez consent.status.

function usercentricsToProof() {
  if (!window.UC_UI || !UC_UI.getServicesBaseInfo) { return; }
  var svc = UC_UI.getServicesBaseInfo();               // [{ name, consent: { status } }, ...]
  var on = function (needle) {
    return svc.some(function (s) {
      return s.name && s.name.toLowerCase().indexOf(needle) !== -1 && s.consent && s.consent.status;
    }) ? "granted" : "denied";
  };
  window.sendConsentProof({
    framework: "consent_mode_v2",
    consent: {
      security_storage:    "granted",
      analytics_storage:   on("analytics"),
      ad_storage:          on("ads"),
      ad_user_data:        on("ads"),
      ad_personalization:  on("ads")
    }
  });
}

window.addEventListener("ucEvent", function (e) {
  if (e.detail && (e.detail.event === "consent_status" || e.detail.action === "onUpdateServices")) {
    usercentricsToProof();
  }
});
// nouvelle API : window.__ucCmp && __ucCmp.getConsentDetails().then(...)

Didomi

Didomi expose une file didomiOnReady et l'évènement consent.changed. Récupérez la chaîne TCF avec Didomi.getUserConsentString() et/ou l'état par finalité avec getUserConsentStatusForAll().

window.didomiOnReady = window.didomiOnReady || [];
window.didomiOnReady.push(function (Didomi) {

  function didomiToProof() {
    var s = Didomi.getUserConsentStatusForAll();      // { purposes: { enabled: [...], disabled: [...] } }
    var on = function (id) { return s.purposes.enabled.indexOf(id) !== -1 ? "granted" : "denied"; };
    window.sendConsentProof({
      framework: "tcf_v2_2",
      tcf_string: Didomi.getUserConsentString() || undefined,
      consent: {
        security_storage:   "granted",
        analytics_storage:  on("analytics"),           // ids selon votre config Didomi
        ad_storage:         on("advertising"),
        ad_user_data:       on("advertising"),
        ad_personalization: on("advertising")
      }
    });
  }

  Didomi.on("consent.changed", didomiToProof);
  didomiToProof();                                     // état initial
});

Axeptio

Axeptio fournit la file _axcb et les évènements cookies:complete / consent:updated. Le callback reçoit un objet de choix indexé par vendor.

window._axcb = window._axcb || [];
window._axcb.push(function (axeptio) {

  function axeptioToProof(choices) {
    choices = choices || {};
    var yn = function (v) { return v ? "granted" : "denied"; };
    window.sendConsentProof({
      framework: "consent_mode_v2",
      consent: {
        security_storage:   "granted",
        analytics_storage:  yn(choices.google_analytics || choices.matomo),
        ad_storage:         yn(choices.google_ads || choices.facebook_pixel),
        ad_user_data:       yn(choices.google_ads || choices.facebook_pixel),
        ad_personalization: yn(choices.google_ads || choices.facebook_pixel)
      }
    });
  }

  axeptio.on("cookies:complete", axeptioToProof);
  axeptio.on("consent:updated",  axeptioToProof);
});

CookieHub

CookieHub se configure avec des callbacks (onStatusChange, onAllow, onRevoke, onInitialise) et expose cookiehub.hasConsented(category) pour les catégories analytics, preferences, marketing.

function cookiehubToProof() {
  var ok = function (cat) {
    return window.cookiehub && cookiehub.hasConsented(cat) ? "granted" : "denied";
  };
  window.sendConsentProof({
    framework: "consent_mode_v2",
    consent: {
      security_storage:        "granted",
      functionality_storage:   ok("preferences"),
      personalization_storage: ok("preferences"),
      analytics_storage:       ok("analytics"),
      ad_storage:              ok("marketing"),
      ad_user_data:            ok("marketing"),
      ad_personalization:      ok("marketing")
    }
  });
}

var cpm = window.cpm || {};
cpm.onInitialise  = cookiehubToProof;
cpm.onStatusChange = cookiehubToProof;
cpm.onAllow  = cookiehubToProof;
cpm.onRevoke = cookiehubToProof;
window.cookiehub && window.cookiehub.load(cpm);

Enzuzo

Enzuzo alimente Google Consent Mode et le dataLayer : la méthode universelle ci-dessus fonctionne telle quelle. Si vous préférez l'évènement natif, Enzuzo diffuse un CustomEvent à chaque mise à jour du bandeau.

function enzuzoToProof(prefs) {
  prefs = prefs || (window.__ENZUZO_PREFS__ || {});
  var yn = function (v) { return v ? "granted" : "denied"; };
  window.sendConsentProof({
    framework: "consent_mode_v2",
    consent: {
      security_storage:        "granted",
      functionality_storage:   yn(prefs.functional || prefs.preferences),
      analytics_storage:       yn(prefs.analytics),
      ad_storage:              yn(prefs.marketing || prefs.advertising),
      ad_user_data:            yn(prefs.marketing || prefs.advertising),
      ad_personalization:      yn(prefs.marketing || prefs.advertising)
    }
  });
}

// évènement natif (le nom exact peut varier selon la version Enzuzo)
window.addEventListener("enzuzo-consent-updated", function (e) { enzuzoToProof(e.detail); });

Piwik PRO

Le Consent Manager de Piwik PRO expose une API asynchrone : ppms.cm.api('subscribeConsentUpdated', …) pour être notifié et getComplianceSettings pour lire l'état (statut 1 = accordé, -1 = refusé).

function piwikProToProof() {
  window.ppms.cm.api("getComplianceSettings", function (s) {
    var c = (s && s.consents) || {};
    var st = function (k) { return c[k] && c[k].status === 1 ? "granted" : "denied"; };
    window.sendConsentProof({
      framework: "consent_mode_v2",
      consent: {
        security_storage:   "granted",
        analytics_storage:  st("analytics"),
        ad_storage:         st("remarketing"),
        ad_user_data:       st("conversion_tracking"),
        ad_personalization: st("ab_testing_and_personalization")
      }
    });
  });
}

window._paq = window._paq || [];
window.ppms = window.ppms || { cm: { api: function () { (ppms.cm.pending = ppms.cm.pending || []).push(arguments); } } };
window.ppms.cm.api("subscribeConsentUpdated", piwikProToProof);
window.ppms.cm.api("getUserId", piwikProToProof);   // déclenche aussi une première lecture

Commanders Act (TrustCommander)

TrustCommander expose l'objet tC.privacy : getOptin() renvoie l'état par catégorie (ids numériques configurés dans la plateforme) et un évènement est diffusé à chaque changement.

function commandersActToProof() {
  if (!window.tC || !tC.privacy || !tC.privacy.getOptin) { return; }
  var o = tC.privacy.getOptin() || {};
  var cat = o.categories || {};
  var yn = function (id) { return cat[id] ? "granted" : "denied"; };
  window.sendConsentProof({
    framework: "consent_mode_v2",
    consent: {
      security_storage:   "granted",
      analytics_storage:  yn("2"),    // remplacez par vos ids de catégories Commanders Act
      ad_storage:         yn("3"),
      ad_user_data:       yn("3"),
      ad_personalization: yn("3")
    }
  });
}

window.addEventListener("tc_privacy_change", commandersActToProof);
document.addEventListener("consent.onLoad", commandersActToProof);

Correspondance catégories → signaux

TrustRGPD accepte n'importe quel nom de signal (jusqu'à 32), mais rester aligné sur Google Consent Mode v2 facilite l'audit. Correspondance habituelle :

Catégorie CMP (intitulé courant)Signal(aux) TrustRGPD
Strictement nécessaires / Essentielssecurity_storage (toujours granted)
Fonctionnels / Préférencesfunctionality_storage, personalization_storage
Statistiques / Mesure d'audience / Performanceanalytics_storage
Marketing / Publicité / Ciblagead_storage, ad_user_data, ad_personalization

Champ framework : consent_mode_v2 pour un objet de signaux, tcf_v2_2 si vous transmettez une tcf_string, gpp pour une gpp_string, custom pour un schéma maison.

Vérifier & fiabiliser

  • Test rapide : acceptez puis refusez la bannière ; votre route /consent-proof doit être appelée à chaque fois et relayer un 201 de l'API.
  • Contrôle côté DPO : dans l'espace client, onglet Audit RGPD, recherchez l'identifiant de test : l'historique horodaté doit apparaître. Même résultat via GET /api/v1/audit/<user_identifier>.
  • Quand archiver : au recueil initial du consentement et à chaque modification. Inutile de rejournaliser à chaque page vue — cela consommerait votre quota sans valeur probante supplémentaire (le script fourni ignore déjà les envois identiques consécutifs).
  • Identifiant stable : réutilisez le même user_identifier pour une même personne (id interne pseudonyme, ou cookie first-party durable). TrustRGPD le hache (HMAC-SHA256) avant stockage : l'identifiant en clair n'est jamais conservé, mais un identifiant instable casse la continuité de l'historique.
  • Résilience : l'appel navigateur utilise sendBeacon / keepalive pour survivre à une navigation. Côté serveur, prévoyez une file d'attente ou un ré-essai court si l'API renvoie 429 (débit) ou 5xx.
  • Sécurité : limitez le débit de /consent-proof, ignorez les corps > 16 Kio, et ne faites confiance à aucune donnée du navigateur au-delà de l'état de consentement (l'user_identifier de référence doit venir de votre session quand l'utilisateur est connecté).

Référence complète de l'API Créer un compte & une clé

Une CMP absente de cette liste ?

Si elle expose un évènement « consentement modifié » et l'état courant, l'intégration prend cinq minutes. Écrivez-nous, on vous aide.