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
- Connectez-vous à votre espace client.
- Carte « Vos clés API » → Créer une clé.
- Copiez la clé complète (format
trgpd_<prefix>_<secret>) : elle n'est affichée qu'une seule fois. - 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.
sendConsentProof().
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 / Essentiels | security_storage (toujours granted) |
| Fonctionnels / Préférences | functionality_storage, personalization_storage |
| Statistiques / Mesure d'audience / Performance | analytics_storage |
| Marketing / Publicité / Ciblage | ad_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-proofdoit être appelée à chaque fois et relayer un201de 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_identifierpour 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/keepalivepour survivre à une navigation. Côté serveur, prévoyez une file d'attente ou un ré-essai court si l'API renvoie429(débit) ou5xx. - 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_identifierde référence doit venir de votre session quand l'utilisateur est connecté).