# How consent is enforced in the tracker

The banner only captures the choice; the thing that enforces it is the `__sov` tracker itself. Here is which tracker behaviour each category opens, why opt-in fails closed, and what gets stamped on every event row.

## The gates are inside the tracker

An external consent tool can only persuade other tags to behave. AdPix is the tag, so it enforces consent at the source: before every page view, every event and every cookie, the tracker asks itself whether that category is allowed right now.

Three things feed that decision:

- **The consent block on the tracker config** — three values only: whether the module is on, the default mode, and the current revision. The block is cached in the browser so even the very first page view is gated correctly, before any network response arrives.
- **The `cc_cookie` cookie** — the visitor's own choice: the list of accepted categories and the revision it was made under.
- **A comparison of the two** — if the cookie's revision is lower than the current one, the choice is stale and is ignored.

If the module is not enabled for the property, none of this exists and the tracker behaves exactly as it did before.

## What each category opens

The overview of the categories, and why there are four, is in [Consent categories](concepts/governance/consent-categories). The table below expands the same thing into actual tracker behaviour.

| Category | What it opens |
| --- | --- |
| Necessary | Always on. The tracker gates nothing on it. |
| Preferences | AdPix ties no behaviour to it. It is there for your own site's cookies. |
| Statistics | Page views, custom events, e-commerce events, identify, form auto-identify, the anonymous-id and session cookies, heatmaps. |
| Marketing | First-touch and last-touch attribution cookies, the cross-domain id handoff via the `_apx` parameter, the fingerprint-enrichment bundle. |

Two things usually come as a surprise:

**Identify sits under statistics, not marketing.** A sign-in or sign-up is a first-party signal the user typed themselves, and it follows the same tier that page views follow. Under marketing, every sign-in from a visitor who accepted statistics only would be silently dropped.

**Preferences is deliberately empty.** Nothing of ours depends on it, yet it is still shown to the visitor, written into the consent record and stamped on events — so that if you set preference cookies of your own, the visitor's answer for them is captured and recorded alongside the rest.

## Opt-in fails closed

"Unresolved" means any state with no valid choice in it: the banner has not appeared yet, the visitor has not chosen, the choice cookie expired, or its revision is stale.

Under opt-out, unresolved means allowed — everything runs until the visitor rejects. Under opt-in, unresolved behaves exactly like a rejection. That is deliberate, and it is enforced at specific points in the tracker:

- **Without statistics consent, no durable id is minted or written.** The tracker writes neither the `__sov_did` cookie nor the `__sov_aid` key in local storage. An id already on the browser is read only; otherwise a temporary id lives in that page's memory, is stored nowhere, and goes when the page closes.
- **Without statistics consent, the heatmap bundle is never loaded.** Capture does not start, rather than starting and being thrown away.
- **Without marketing consent, the attribution cookies are not written** and cross-domain links do not carry the id.

> **These two points used to fail open**
>
> In the module's early versions the durable id and the heatmap ran with no statistics check at all, so a visitor who had rejected statistics still got a durable id written. Both are closed now. If your site's tag is a very old build, update it to the current one.

## A new choice takes effect at once

At the moment the visitor decides, two things happen back to back.

First, the cookies belonging to rejected categories are cleared right there: rejecting statistics drops the anonymous-id cookie and the session cookie and removes the matching key from local storage; rejecting marketing drops the first-touch and last-touch cookies.

Second, the banner fires an `apx:consent` event on the page and the tracker re-evaluates itself immediately. If the new choice opened statistics, that same page view is sent at once; if it opened marketing, the deferred bundles load. No page reload is needed.

And because the gates stay shut, none of the cleared cookies is re-created.

A page view fires at most once per URL, so a visitor who spent a few seconds with the banner before accepting produces one page view, not two.

## Every event carries its own categories

With each event it sends, the tracker also sends the list of categories accepted at that moment, and the collector writes that list into the event row's `consent_flags` field.

- **Accepted** categories are written.
- **Rejected** categories are not written at all; their absence is what "not accepted" means.
- When the module is off, the field stays empty.

The effect is that every row of data carries the consent it was collected under. You never have to reconstruct what the settings were on a given day, or dig through change history; it is written on the row.

## The three endpoints behind this

All of them live on the same collection domain the measurement tag loads from — first-party, with no third-party request.

| Endpoint | What it does |
| --- | --- |
| `GET /consent.js` | The banner bundle. The base tracker loads it only when the module is enabled for the property. Served with a 300-second cache. |
| `GET /consent/config?site=<measurement id>` | The banner's full configuration: categories, theme, copy, mode, revision and the configuration fingerprint. An unknown key, or the module off, returns `{"enabled":false}`. 60-second cache. |
| `POST /consent/record` | Stores the visitor's choice as a record. It is public and hardened like the collection path: rate-limited per IP, idempotent on the consent ID, and the IP is stored only as a hash. |

The three-value consent block the base tracker gates itself on is not fetched separately; it rides on the same config response the tracker already requests, likewise with a 60-second cache. That is why a mode change or a revision bump reaches live visitors within about a minute.

> **Recording a consent never breaks the page**
>
> `POST /consent/record` returns a 200 on every non-fatal error — an invalid body, an unknown key, a database write failure. The visitor's choice was already written in their own browser at the moment they made it and is valid from then on; the record is only your proof of it. If your banner is served through a Tag Gateway, these paths have to be in the proxy allowlist or no record is ever stored.

## When the module is off

Off is the default for every new property. In that state no banner is loaded, no gate is applied and no categories are stamped on events — the tracker is exactly what it was before this module existed.

Turning it on and building the banner is covered in [set up the consent banner](analytics/privacy/consent-cmp-setup). If your numbers dropped after enabling it and you are not sure consent is the cause, see [consent is blocking data](analytics/troubleshooting/consent-is-blocking-data).

## Frequently asked questions

### If a visitor rejects statistics, is an id still written?

No. The tracker writes neither the id cookie nor the local-storage key. If an id is already present on the browser it is only read; otherwise a temporary id is held in that page's memory and disappears when the page is closed.

### Is the page view lost if the visitor accepts after the page loaded?

No. Once the choice is saved, the banner fires an event on the page and the tracker re-evaluates itself on the spot and sends the page view. No reload is needed.

### How do I tell what consent an event was collected under?

Every event row carries a `consent_flags` field listing the categories accepted at that moment. Rejected categories are not written at all; their absence is what "not accepted" means.

### Are heatmaps covered by consent too?

Yes, under statistics. Without statistics consent the heatmap bundle is never even loaded and no capture starts — it is not captured and then discarded.

## Related

- [Consent categories](https://docs.adpix.io/en/concepts/governance/consent-categories/)
- [Set up the consent banner](https://docs.adpix.io/en/analytics/privacy/consent-cmp-setup/)
- [Consent is blocking data](https://docs.adpix.io/en/analytics/troubleshooting/consent-is-blocking-data/)
- [Cookies and headers](https://docs.adpix.io/en/developers/analytics/developers/cookies-and-headers/)

---

[Docs](https://docs.adpix.io/en/analytics/privacy/consent-categories-and-gating/) · AdPix
