Consent Management
Datafly.js includes built-in consent management that integrates with popular Consent Management Platforms (CMPs). Consent state is read from the page automatically and used to gate both event collection (client-side) and event delivery (server-side).
How consent works in Datafly
Consent is enforced at two levels:
- Client-side (Datafly.js) — reads consent state from the CMP and includes it with every event. Client-side tag injection is gated by consent category.
- Server-side (Signals) — re-checks consent before delivering events to each vendor. Even if an event reaches the server, it will not be forwarded to a vendor unless the required consent category is granted.
This dual enforcement ensures that vendor data delivery respects user consent regardless of client-side manipulation.
User grants consent
│
▼
┌──────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Datafly.js │────>│ Signal endpoint │────>│ Vendor delivery │
│ reads CMP │ │ stores consent │ │ re-checks │
│ includes in │ │ with event │ │ consent before │
│ event │ │ │ │ delivery │
└──────────────┘ └──────────────────┘ └─────────────────┘Consent categories
Datafly carries two views of consent on every event:
categories— keyed by your CMP’s codes verbatim (Cookiebot’sstatistics, OneTrust’sC0002, custom codes). Used for CMP-specific blueprints and audit.canonical— keyed by Signal’s CMP-agnostic vocabulary. Vendor blueprints (GA4, Meta, Google Ads, TikTok …) reference this so they stay portable.
The canonical vocabulary:
| Canonical | Description | Example vendors |
|---|---|---|
essential | Strictly necessary — always granted | First-party identity cookie |
functional | Preferences and UI personalisation | A/B testing tools, chat widgets |
analytics | Performance and aggregate measurement | Google Analytics 4, BigQuery |
marketing | Advertising and conversion APIs | Meta CAPI, Google Ads, TikTok, Pinterest |
personalization | Cross-property profiling | Recommender services |
The customer maps their CMP’s category codes onto this vocabulary in Settings → Consent. See Canonical Categories for the full guide.
The first-party identity cookie (_dfid) is always set so events within a visit can be correlated, but when the pipeline has a consent provider its lifetime follows consent. It is a session cookie until a category that allows persistence is granted, and then becomes a persistent cookie with the pipeline’s cookie lifetime (400 days unless set shorter). Choose those categories in the Collector tab under Persist identifiers when consented to (essential always counts). If none are chosen, persistence follows your active consent categories, or analytics or marketing consent. The cookie carries only an anonymous identifier.
Supported CMPs
Datafly.js automatically detects and reads consent from the following CMPs. No additional configuration is required — just install your CMP as usual and Datafly.js will find it.
OneTrust
Datafly.js reads the OptanonConsent cookie and parses the consent groups:
| OneTrust group | Datafly category |
|---|---|
C0001 (Strictly Necessary) | Always allowed |
C0002 (Performance) | analytics |
C0003 (Functional) | functional |
C0004 (Targeting) | marketing |
OptanonConsent cookie value:
groups=C0001%3A1%2CC0002%3A1%2CC0003%3A0%2CC0004%3A0
Parsed as:
C0001: 1 (Necessary - always on)
C0002: 1 (Analytics - granted)
C0003: 0 (Functional - denied)
C0004: 0 (Marketing - denied)Cookiebot
Datafly.js reads the CookieConsent cookie and maps the Cookiebot fields:
| Cookiebot field | Datafly category |
|---|---|
necessary | Always allowed |
statistics | analytics |
preferences | functional |
marketing | marketing |
Custom / Datafly consent cookie
If you manage consent yourself or use a CMP not listed above, Datafly.js reads a _df_consent cookie with a simple key-value format:
_df_consent=analytics=1;marketing=0;functional=1Values of 1 or true indicate consent granted. Values of 0 or false indicate consent denied.
Configuration
Default consent state
Configure the default consent state (before the user makes a choice) in your source configuration:
{
"consent": {
"mode": "onetrust",
"default_state": {
"analytics": false,
"marketing": false,
"functional": false
}
}
}The mode option tells Datafly.js which CMP to look for first. Supported values:
| Mode | CMP | Cookie read |
|---|---|---|
"onetrust" | OneTrust | OptanonConsent |
"cookiebot" | Cookiebot | CookieConsent |
"custom" | Your own CMP | _df_consent |
"auto" (default) | Auto-detect | Checks all in order |
Opt-in vs opt-out regions
For regions where consent is not required (e.g., United States under current law), you can set defaults to true:
{
"consent": {
"mode": "auto",
"default_state": {
"analytics": true,
"marketing": true,
"functional": true
}
}
}Consent defaults should match your legal obligations. Setting defaults to true means events will be collected and delivered before the user interacts with a consent banner. Consult your legal team for the correct default state for each region you operate in.
Custom consent integration
If your CMP is not auto-detected, set the _df_consent cookie from your consent handler:
// Example: integrating with a custom consent banner
consentBanner.onAccept((categories) => {
const parts = [];
if (categories.analytics) parts.push('analytics=1');
if (categories.marketing) parts.push('marketing=1');
if (categories.functional) parts.push('functional=1');
document.cookie = `_df_consent=${parts.join(';')};path=/;max-age=31536000`;
});
consentBanner.onReject(() => {
document.cookie = '_df_consent=analytics=0;marketing=0;functional=0;path=/;max-age=31536000';
});Reading consent state
You can read the current consent state from Datafly.js at any time:
const consent = datafly.getConsent();
console.log(consent);
// { analytics: true, marketing: false, functional: true }This is useful for conditionally showing UI elements or gating your own application features based on consent.
Client-side tag injection and consent
If you use Datafly’s client-side tag injection (for tags that must run in the browser), tags are consent-gated by category. A tag will only be injected if the user has granted consent for its category:
datafly.injectTag({
id: 'hotjar',
src: 'https://static.hotjar.com/c/hotjar-123456.js',
consentCategory: 'analytics',
timing: 'post-render',
});
// This tag will only load if consent.analytics === trueConsent is re-checked at the time of injection. If a user revokes consent, subsequently scheduled tags will not be injected.
Calls Datafly.js makes to other companies
A few features make Datafly.js call a third party directly from the browser. Each call waits for the visitor’s consent to its own consent category, checked at the moment it would fire:
| Call | Consent category |
|---|---|
GA4 session beacon (session_start to /g/collect), and creating or reading the GA client ID | The GA4 row’s Consent category in Identity → ID Syncs. If it is left at the default, the GA4 integration’s category (when all your published GA4 integrations share one), else analytics. |
| ID syncs, both the init-time syncs and the syncs the collection server asks for | Each vendor’s Consent category in Identity → ID Syncs; marketing by default. |
| Floodlight match pixel and vendor match pixels (Meta, Pinterest, LinkedIn, …) | The vendor’s Consent category in Identity → ID Syncs; marketing by default. |
| Cross-domain identity hub | The consent that lets the _dfid cookie persist. |
A category is one of your banner’s category codes, or a canonical name (analytics, marketing, …) when none of your codes has that name. Signal applies the visitor’s stored choice for it, or, before they have chosen, the Before the visitor chooses setting for that category (see Consent). This is the same decision the server makes for delivery.
These settings take effect without a new tag. Datafly.js asks the collection server for your pipeline’s current consent rules once a session, and again every 15 minutes, and holds these calls until the first answer arrives, usually a fraction of a second after the first event. Your events, including engagement heartbeats, session_start and first_visit, go only to your collection endpoint, and the server checks each destination’s own consent category when it delivers them.
UK first-party analytics. If your banner keeps a first-party analytics category on after “Reject all” (the UK DUAA exemption), key your GA4 integration to it, and set it to Allowed before the visitor chooses if your policy lets it run until they object. Classify it as analytics, not essential: essential should mean a category the visitor cannot switch off.
With no consent provider configured on the pipeline, Datafly.js has no consent signal to read and does not hold these calls back. If you manage consent outside Signal, load the tag only after the visitor consents.
When consent changes
Call datafly.refreshConsent() from your CMP’s change callback (for example OneTrust OptanonWrapper or Cookiebot CookiebotOnAccept). Datafly.js also notices a new choice your page has saved without that call, before the next event it sends.
- Consent granted: the calls it allows start straight away. The GA4 beacon still fires at most once per session.
- Consent withdrawn: they stop, and the stored GA client ID is deleted, so events no longer carry it. A new ID is created if analytics consent is granted again.
Consent in the event payload
The current consent state is sent with every event, allowing the server to make delivery decisions:
{
"type": "track",
"event": "Product Viewed",
"context": {
"consent": {
"analytics": true,
"marketing": false,
"functional": true
}
}
}The server uses this to determine which vendors can receive the event. For example, if marketing is false, the event will not be delivered to Meta, TikTok, or other marketing vendors, even though it was collected.
With a consent provider configured, Datafly.js also sends chosen: the categories your banner has actually stored for the visitor. It is empty before they make a choice. The server applies your per-category Before the visitor chooses settings to every category not in chosen (see Consent), so you can change those settings without a new tag.
Detection order
When mode is "auto" (the default), Datafly.js checks for CMP cookies in this order:
OptanonConsent(OneTrust)CookieConsent(Cookiebot)_df_consent(custom)- Default state (all
falseunless overridden)
The first match wins. If you have multiple CMPs on the page (not recommended), the first detected CMP takes precedence.