Wire / Cookie Banners / Article
┌── POST 09.06 · Cookie Banners · 7 min read

You Added a Tracker. Does the Consent You Already Have Still Cover It?

Adding a vendor to a self-hosted CMP changes the config but not the consent already stored in your visitors' browsers. Klaro withholds every service until they re-confirm while getConsent() still says yes; CookieConsent notices nothing without a manual revision bump. Both behaviours measured, with the traps in each.

TL;DR

Adding a tracker to a self-hosted CMP is a config change, and the two most common open-source libraries treat it in opposite ways. Klaro detects the new service automatically and, until the visitor re-confirms, withholds every service — while getConsent() still reports the old answer. CookieConsent detects nothing: without a manual revision bump the new category is silently denied for returning visitors forever. Both behaviours were measured on 2026-09-06, not inferred from the docs.

The consent you collected in March was collected for the vendor list you had in March. In August you added a pixel. Nobody tested what that deploy did to the consent already sitting in a few hundred thousand browsers, because nothing visibly breaks — the banner still works and the CMP logs nothing about it. It does break, in one of two directions depending on which library you run, and neither is what most people assume.

What the regulator says about the question

Worth establishing before the code, because it is what the code is trying to implement. The EDPB’s Guidelines 05/2020 on consent (Version 1.1, adopted 4 May 2020) is direct at paragraph 58:

“If a controller processes data based on consent and wishes to process the data for another purpose, too, that controller needs to seek additional consent for this other purpose unless there is another lawful basis, which better reflects the situation.”

Paragraph 110 covers the drift case: “If the processing operations change or evolve considerably then the original consent is no longer valid. If this is the case, then new consent needs to be obtained.”

Note the unit. It is the purpose, not the vendor. Swapping one analytics provider for another inside a category you already described accurately is a different situation from adding advertising to a site that only ever had analytics. What follows is how the libraries behave; whether a given change crosses that line is a question for whoever owns the decision, not for your CMP.

Klaro: automatic, and much blunter than you expect

Klaro has no revision number. It compares the service names in your config against the service names in the stored cookie on every page load, in _checkConsents():

for(const service of this.config.services){
    if (!consents.has(service.name)){
        this.consents[service.name] = this.getDefaultConsent(service)
        complete = false
    }
}
this.confirmed = complete
if (!complete)
    this.changed = true

One unknown service name and confirmed goes false. That flag is then read in applyConsents():

const confirmed = this.confirmed || optOut || dryRun || interactive
const consent = (this.getConsent(service.name) && confirmed) || required

confirmed is one flag for the whole config, not one per service, so an unconfirmed state collapses consent to false for everything that is not required. Measured on Klaro 0.7.21, with ga accepted and stored, then a second service meta added and the page reloaded as a returning visitor:

Probe Before After adding meta
klaro cookie {"ga":true} {"ga":true} — unchanged
getConsent('ga') true true
manager.states.ga true false
ga callback fired true fired false
ga actually executed
Notice dismissed shown again

The safe half of that: Klaro fails closed. Adding a vendor neither grandfathers it in nor keeps the old ones running on stale consent.

The trap: getConsent('ga') returns true while Klaro is refusing to run ga. The stored answer and the effective answer disagree, and the public API reports the stored one. If you have your own tag gated on klaro.getManager().getConsent('...') — a very common pattern for firing something Klaro does not manage itself — your tag will fire during a window in which Klaro is deliberately withholding that exact service. Read manager.states[name], or gate on the service callback, which receives the effective value.

The one configuration where this goes the wrong way

Look again at const confirmed = this.confirmed || optOut || .... Setting optOut: true forces that term true regardless of the missing confirmation. Measured, with optOut: true and the new service carrying default: true:

confirmed:       false          // the visitor has not re-confirmed anything
notice:          visible        // they are being asked, right now
fired:           { ga: 1, meta: 1 }   // both ran anyway, on page load

The new pixel executes for a returning visitor who has never been asked, while the notice requesting that consent is still on screen. That is opt-out mode working as designed — but it means adding a service to an opt-out Klaro config produces a tracker running before consent. We isolated the cause: default: true alone does not do this (0 executions in opt-in mode); optOut: true does.

One more Klaro detail, since it is easy to misread: config.version exists, but it selects the config schema (services versus the older apps key). It is not a consent revision and bumping it will not re-ask anybody.

CookieConsent v3: nothing is automatic

The other library takes the opposite approach. Measured on vanilla-cookieconsent 3.1.0, baseline accepted with categories necessary and analytics, then an ads category added to the config with revision left alone:

bannerVisible:                  false
validConsent():                 true
acceptedCategory('analytics'):  true
acceptedCategory('ads'):        false

No prompt, no warning. The new category is denied — the right default — but denied permanently and silently for everyone who already has a cookie. You ship an ads category, watch its consent rate sit near zero among returning visitors, and have nothing in the interface telling you why.

The fix is the revision field, and it is entirely manual. Bumping it from 1 to 2 produced:

bannerVisible:                  true
validConsent():                 false
acceptedCategory('analytics'):  false
managed script executions:      0
cc_cookie:                      still revision 1, still ["necessary","analytics"]

Three details that are not obvious from that:

  • Every category reads false during the re-consent window, including necessary. acceptedCategory() returns against an empty array while consent is invalid. If you gate anything on acceptedCategory('necessary') — some people gate a language cookie or a session restore that way — it stops working until the visitor clicks.
  • The old cookie is not cleared. It keeps the previous revision and categories until the user re-consents, so it is still readable if you need to know what they had agreed to before.
  • Previous choices are not pre-ticked. The analytics toggle came back unticked in the preferences modal. The visitor starts from scratch, which is defensible, but it does mean a revision bump costs you real consent rate rather than a confirmation click.

Two ways the revision message silently does nothing

CookieConsent can explain why someone is being asked again, via a {{revisionMessage}} placeholder in the consent modal description. Two ways it fails quietly, both measured.

Wrong nesting. revisionMessage at the root of the config is ignored — the placeholder is replaced with an empty string, with no error. It belongs inside the translation:

language: {
  default: 'en',
  translations: {
    en: {
      consentModal: {
        title: '...',
        description: 'We use cookies. {{revisionMessage}}',
        revisionMessage: '<br>Our vendor list changed, so please review your choices.'
      }
    }
  }
}

No numeric revision. The library only enables revision handling when you pass a number, and the placeholder substitution is gated on that. Use {{revisionMessage}} in a description without setting revision and the literal string {{revisionMessage}} is rendered to visitors. We measured exactly that.

The good news for anyone who has never set the field: the cookie stores revision: 0 by default, so your first ever bump — from nothing to revision: 1 — does invalidate correctly.

What this means for your deploy process

The libraries disagree, so make the check explicit rather than trusting the tool:

  1. Treat the vendor list as versioned state. Automatic but all-or-nothing on Klaro; a number only you can increment on CookieConsent. Either way, put the change in the pull request description rather than discovering it in production.
  2. Test as a returning visitor, not a fresh one. Every behaviour above is invisible in a clean profile, which has no stored consent to invalidate. Load the site, accept, deploy the config change, then reload with that cookie still present. That single step catches all of it.
  3. Gate your own tags on the effective value. On Klaro, states[name] or the service callback, not getConsent(). On CookieConsent, acceptedCategory() already reflects validity — but remember it goes false across the board during re-consent.
  4. Check the opt-out path specifically if you run Klaro with optOut: true, because that is the one configuration where adding a service produces a tracker firing before anyone agreed to it.

None of this is exotic. It is the ordinary consequence of storing consent in a cookie whose shape is defined by a config file that changes independently of it — the same class of problem as keeping a defensible record of what a visitor agreed to. If you are wiring either library up from scratch, the Consent Mode side is covered for Klaro and for CookieConsent v3.

Method

Measured 2026-09-06 against vanilla-cookieconsent 3.1.0 (4 February 2025, MIT; 5,657 stars, last push 23 July 2026) and klaro 0.7.21 (26 March 2024, BSD-3-Clause; 1,507 stars, last push 27 March 2025), driven in Chromium from a local harness recording library state, managed-script execution and the raw consent cookie on each load. Source quotations come from the unminified npm tarballs. Version numbers move — re-check them before relying on any of this.

The failure mode in all of this is a tracker whose consent state on real returning visitors is not what your config says it should be. Scan your site and see what actually fires with CookieInspector.

C
About the author
Consent Mode HQ
Editorial team at Consent Mode HQ
Read more by author ↗