JavaScript SDK (Client) Integration
The HashEntry CMP SDK is a dependency-free script that shows the cookie banner, blocks the scripts you tag until the visitor allows them, and records each decision on the HashEntry server. It can also record document approvals (terms, contracts, policies) — see Document Approval & Signature.
This page describes what the SDK does today. Where a behaviour depends on a dashboard setting or on your own markup, it says so.
Quick Start
Section titled “Quick Start”<head> <script src="https://tool.hashentry.com/cmp.js" data-site="YOUR_SITE_API_KEY" async ></script></head>The SDK loads the configuration bound to your API key (banner design, texts, categories, documents, behaviour settings) from the server and decides whether a banner is needed for this visitor.
Two things must be in place before a cookie decision can be recorded on the server:
- The site is verified in the dashboard. Requests for an unverified site are rejected with
403 SITE_NOT_VERIFIED. - A cookie policy document is attached to the site. See the next section.
A cookie policy document is required
Section titled “A cookie policy document is required”A cookie consent is recorded only if it references the exact active version of a document of type cookie_policy that is attached to the site. The accepted policy version is part of the evidence, so the SDK does not relax this rule.
- If no published cookie policy is attached, the SDK does not show the banner (nor the widget or the “Do Not Sell” link) and keeps every tagged non-essential script blocked, in every region: a choice that cannot be recorded as evidence is not collected. The
consent:erroranderrorevents fire withcode: "COOKIE_POLICY_MISSING"and the error is written to the browser console.CMP.accept()/CMP.reject()resolve withsuccess: false,error.code = "COOKIE_POLICY_MISSING"andpending_sync: false; nothing is stored, applied or queued. Once a policy is attached, the banner appears on the next page load and the visitor decides against that version. A consent that was recorded while a policy was attached stays valid. - A decision is never held back and sent later with a policy version the visitor has not seen. If an earlier SDK version left such a decision in the local queue, it is dropped (
consent:errorwithfinal: true,reprompt: true) and the visitor is asked again. - The only exception is
localmode withoutdata-site: nothing is sent to the server, so the banner is shown and decisions stay in the browser (local_only: true). - If the consent references a version that is not the active one, the server answers
409 CONFIG_OUTDATED. See Re-consent and changed document versions.
What you must do: create a cookie policy document in the dashboard, publish a version in every language your banner uses, and attach it to the site.
Blocking scripts before consent
Section titled “Blocking scripts before consent”Only scripts that you tag are blocked. The SDK does not scan the page for trackers and does not intercept scripts you leave untouched: a normal <script> tag runs immediately, with or without consent.
To hold a script back, change its type to text/plain and name its category:
<!-- Inline script: runs only after "analytics" is allowed --><script type="text/plain" data-cookie-category="analytics"> /* analytics snippet */</script>
<!-- External script: use data-src instead of src --><script type="text/plain" data-cookie-category="marketing" data-src="https://connect.facebook.net/en_US/fbevents.js"></script>- Category names must match the category keys of your banner (
analytics,marketing,preferences, or your own keys). Scripts taggednecessaryare activated immediately, before the configuration is loaded. - Tagged scripts that are added to the page later (SPA navigation, tag injection) are handled as well.
- A script that has already run cannot be stopped. When a visitor revokes a category whose scripts ran on the current page, the SDK deletes the known cookies of that category and, by default, reloads the page (see
reload_on_revoke). - When a category is revoked, the SDK deletes the cookies / storage entries listed for that category in your banner configuration, plus a small built-in list of well-known names. Cookies it does not know about are not deleted, and third-party cookies on other domains cannot be deleted from your page.
Script tag attributes
Section titled “Script tag attributes”| Attribute | Default | Description |
|---|---|---|
data-site | — | Site API key. Required in remote mode; also needed in the other modes for decisions to be recorded on the server. |
data-mode | local-fallback | Configuration source: remote, local, local-fallback. See Operating modes. |
data-language | auto | Banner language. auto uses <html lang>, then the browser language. |
data-region | auto | Forces a region or framework (DE, US, gdpr, ccpa, none, …) instead of detection. This is the compliance region (which rules the banner follows); it does not choose where data is stored or which API host is called. |
data-config | — | Inline JSON configuration (for local / local-fallback). |
data-debug | false | true prints debug logs. Errors and warnings are always printed to the console. |
data-headless | false | true renders no UI at all (banner, widget, “Do Not Sell” link). See Headless mode. |
data-consent-mode | dashboard setting | true / false forces Google Consent Mode v2 on or off. See Google Consent Mode v2. |
data-reload-on-revoke | dashboard setting | true / false overrides the reload_on_revoke behaviour flag. |
data-modules | — | Modules to load up front, e.g. documents, preferences. See Bundles and on-demand modules. |
data-module-base | script’s own folder | Base URL the module files are loaded from. Only needed when they are not served next to cmp.js. |
data-api-url | HashEntry API | API base URL for a first-party proxy or custom domain. If your account’s data region has its own API host, the configuration response announces it (api_base_url) and the SDK uses it for all later calls; the embed code generated in the panel then already contains the matching data-api-url. A value you set here always takes precedence. |
JavaScript API (window.CMP)
Section titled “JavaScript API (window.CMP)”window.CMP exists as soon as the script has run. Methods that record something return a promise with a result object; they do not throw.
CMP.ready(() => { /* configuration loaded, consent state evaluated */ });
// BannerCMP.show(); // open the banner (e.g. from a footer link)CMP.hide();
// Decisions — each returns Promise<ConsentResult>CMP.accept(); // allow all categoriesCMP.accept({ marketing: false }); // allow all except marketingCMP.reject(); // only necessaryCMP.withdraw(); // withdraw the stored consent
// StateCMP.getConsent(); // ConsentState or nullCMP.hasConsent('analytics'); // boolean ("necessary" is always true)
// LanguageCMP.setLanguage('de');CMP.getLanguage();CMP.setLocale('de', { ccpa: { optOut: 'Widersprechen' } }); // texts for one language
// Metadata: added to every consent and approval recorded afterwardsCMP.setMetadata({ order_id: 'ORD-54321' });CMP.setMetadata('customer_id', 'CUST-999');CMP.getMetadata();
// Region override, theme, API URL, local configurationCMP.setRegion('DE');CMP.setTheme({ primaryColor: '#0F766E' });CMP.setApiUrl('/hashentry-api');CMP.getApiUrl();CMP.setConfig({ banner_config: { /* … */ } });
// EventsCMP.on('consent:given', (payload) => {});CMP.off('consent:given', handler);
// Documents (loaded on demand — see "Document Approval & Signature")CMP.showDocumentApproval('terms-of-service', { language: 'en' });CMP.approveDocument('terms-of-service', { /* … */ });CMP.withdraw()
Section titled “CMP.withdraw()”Withdrawing turns all non-essential categories off, deletes the known cookies of those categories, sets Google Consent Mode to denied, sends a decision of withdrawn to the server (if a consent had been stored), removes the stored consent from the browser and — in opt-in regions — shows the banner again. If scripts of a revoked category already ran on the page, the page is reloaded unless reload_on_revoke is off.
CMP.setLanguage(lang)
Section titled “CMP.setLanguage(lang)”Changes the language of the open banner and reloads the configuration for that language, because document versions are language-specific: later consents then reference the cookie policy version of the new language. If the banner has no translation for the language, it falls back to the banner’s default language.
CMP.setMetadata(...)
Section titled “CMP.setMetadata(...)”Values are merged and sent as metadata with every later cookie decision and document approval. The server accepts at most 30 keys and about 16 KB of metadata per record.
Calling the API before the script has loaded
Section titled “Calling the API before the script has loaded”<script> window.__CMP_QUEUE = window.__CMP_QUEUE || []; window.__CMP_QUEUE.push(['setLanguage', 'de']); window.__CMP_QUEUE.push(['setMetadata', { order_id: 'ORD-12345' }]); window.__CMP_QUEUE.push(['on', 'consent:given', (p) => console.log(p)]);</script>Queued calls run in order when the SDK starts. A language set this way is not overridden by automatic detection.
Result objects
Section titled “Result objects”ConsentResult (accept, reject, withdraw)
Section titled “ConsentResult (accept, reject, withdraw)”{ success: true, consent_token: 'ct_…', proof_hash: '…', // 64 hex characters created_at: '2026-10-10T10:00:00+00:00', idempotency_key: '…' // one UUID per user action, reused on every retry}success is true only when the server has accepted the record. Otherwise:
{ success: false, consent_token: '', proof_hash: '', created_at: '…', error: { code: 'NETWORK_ERROR', status: 0, message: '…' }, pending_sync: true, // kept in the local queue, will be retried idempotency_key: '…'}error.codeis the server’s machine code or one of the SDK codes below;error.statusis the HTTP status (0when no response was received).pending_sync: truemeans the decision is applied in the browser and waits in a local queue (localStorage). It is retried on the next page load, when the tab becomes visible again and when the browser comes back online, always with the sameidempotency_key, for up to 7 days or 12 attempts.local_only: trueappears when there is nothing to send (for examplelocalmode withoutdata-site).
The visitor’s choice is applied in the browser immediately in every case; the result tells you whether the server has a record of it. The exception is COOKIE_POLICY_MISSING: the decision is neither stored nor applied.
DocumentApprovalResult
Section titled “DocumentApprovalResult”{ success: true, decision: 'accepted', consent_token: 'ct_…', proof_hash: '…', slug: 'terms-of-service', version: '2.0.0' }{ success: false, slug: 'terms-of-service', version: '2.0.0', error: { code: 'DOCUMENT_VERSION_CHANGED', status: 409, message: '…' } }Document approvals are never queued for later: if success is not true, the approval has not been recorded.
Events
Section titled “Events”| Event | Payload | When |
|---|---|---|
ready | {} | Start-up finished (also when the configuration could not be loaded). |
banner:shown | { type } or { headless: true, reason, config } | The banner was shown, or — in headless mode — should be shown by you. |
banner:hidden | { reason } or { headless: true } | The banner was closed. |
consent:given | { decision, categories, source } | A decision other than “reject all” was stored locally. |
consent:rejected | { decision, categories, source } | ”Reject all” was stored locally. |
consent:changed | { oldCategories, newCategories } | A decision replaced an earlier one. |
consent:withdrawn | { categories, cleanedCookies } | CMP.withdraw() was called. |
consent:synced | { consent_token, idempotency_key } | The server accepted a record (immediately or from the queue). |
consent:error | { code, status, message, idempotency_key, pending_sync, final?, reprompt? } | A record could not be delivered. final: true means it was dropped. |
document:approved | { slug, version, consentId } | A document approval was recorded. |
document:rejected | { slug, version } | The visitor declined a document. |
document:error | { slug, version, code, status, message } | A document could not be loaded or the approval was not recorded. |
scripts:activated | { category, count } | Tagged scripts of a category were activated. |
config:updated | { content_hash } | A newer configuration replaced the cached one. |
language:changed | { from, to } | The language changed. |
error | { scope, code, status, message, … } | Any of the errors above; scope is config, consent, module or document. |
consent:given / consent:rejected fire when the decision is stored in the browser. Use consent:synced (or the promise result) when you need to know that the server has it.
Error codes
Section titled “Error codes”Codes produced by the SDK itself:
| Code | Meaning |
|---|---|
NOT_READY | A decision method was called before the configuration was loaded. Call it inside CMP.ready(). |
COOKIE_POLICY_MISSING | No published cookie policy document is attached to the site. The banner is not shown, non-essential scripts stay blocked, and the decision is neither stored nor queued. |
NETWORK_ERROR, TIMEOUT | No response from the API (timeout: 5 s per attempt). |
HTTP_<status> | The server answered with an error that carries no machine code. |
DOCUMENT_VERSION_CHANGED | The referenced document version is no longer the active one; the record was dropped. |
PENDING_EXPIRED | A queued decision could not be delivered within 7 days and was dropped. |
MODULE_LOAD_FAILED | A module file (documents or preferences) could not be loaded. Also reported through the error event with scope: "module". |
APPROVAL_TOKEN_REQUIRED | The document requires a one-time approval token and none was passed (also returned by the server). |
CONFIG_UNAVAILABLE | The configuration could not be loaded (reported through the error event). |
UNEXPECTED_ERROR | Anything else. |
Codes returned by the server are passed through unchanged. The most relevant ones are listed on the REST API page — for example CONFIG_OUTDATED, SITE_NOT_VERIFIED, ORIGIN_NOT_ALLOWED, RATE_LIMIT_EXCEEDED, CONSENT_LIMIT_EXCEEDED, APPROVAL_TOKEN_INVALID.
Temporary server responses. 503 (API_KEY_LOOKUP_UNAVAILABLE, ADMISSION_UNAVAILABLE, QUEUE_OVERLOADED) and 429 RATE_LIMIT_EXCEEDED are retried automatically with the same idempotency_key; the SDK waits for the time given in the Retry-After header. If that time is longer than 5 seconds, the SDK does not wait inside the page: the decision stays in the local queue (pending_sync: true) and is sent later. After 429 TOO_MANY_INVALID_API_KEYS the SDK sends no further requests during that page view and queued decisions are kept; check the data-site value. None of these responses affects your page: the error event fires and CMP.ready() callbacks still run.
Section titled “Temporary server responses. 503 (API_KEY_LOOKUP_UNAVAILABLE, ADMISSION_UNAVAILABLE, QUEUE_OVERLOADED) and 429 RATE_LIMIT_EXCEEDED are retried automatically with the same idempotency_key; the SDK waits for the time given in the Retry-After header. If that time is longer than 5 seconds, the SDK does not wait inside the page: the decision stays in the local queue (pending_sync: true) and is sent later. After 429 TOO_MANY_INVALID_API_KEYS the SDK sends no further requests during that page view and queued decisions are kept; check the data-site value. None of these responses affects your page: the error event fires and CMP.ready() callbacks still run.”Re-consent and changed document versions
Section titled “Re-consent and changed document versions”When the banner is shown again. The stored consent remembers the content hash of the banner configuration it was given for. The banner is shown again when:
- the consent has expired (the validity period is set in the dashboard),
- the category / cookie structure of the banner changed, or
- a new version of an attached document was published — unless the banner’s
reconsent_on_doc_changesetting is turned off, in which case only structure changes trigger re-consent.
While re-consent is pending in an opt-in region, the visitor’s previous choice stays in effect until they decide again.
Pending decisions and a changed document version. If a decision could not be delivered (offline, page closed, server error) and, when it is retried, the document version it references is no longer the active one, the SDK:
- asks the server for the current configuration, bypassing the cache;
- if the version is unchanged (the 409 was caused by a cold server cache), sends the same record once more;
- if the version changed, drops the record, removes the locally stored consent and shows the banner again (
consent:errorwithcode: "DOCUMENT_VERSION_CHANGED",final: true,reprompt: true).
A pending record is never re-sent with a different document version: the visitor did not see that version.
Regions, regulation profiles and behaviour flags
Section titled “Regions, regulation profiles and behaviour flags”The visitor’s region is resolved in this order: CMP.setRegion() → data-region → the server’s GeoIP result in the configuration response (detected_region) → an estimate from the browser time zone → the banner’s fixed regulation mode → the strictest rules (GDPR). The SDK never calls a third-party geolocation service.
| Region | Framework | Model | Behaviour |
|---|---|---|---|
| EU / EEA, Switzerland | gdpr | opt-in | Banner; tagged scripts blocked until allowed. |
| Türkiye | kvkk | opt-in | Same as GDPR. |
| United Kingdom | uk_gdpr | opt-in | Same as GDPR (see uk_analytics_exemption). |
| Brazil | lgpd | opt-in | Same as GDPR. |
| South Africa, Canada, China, South Korea | popia, pipeda, pipl, pipa | opt-in | GDPR banner rules are applied. |
| USA | ccpa — only if enabled, otherwise gdpr | opt-out / opt-in | See ccpa_opt_out_enabled. |
| Unknown | gdpr | opt-in | Strictest rules. |
none (explicit) | none | — | No banner; all scripts run. |
In every opt-in profile the banner always has a “Reject” button with the same visual weight as “Accept”, and no optional category is pre-selected — regardless of what the banner design says.
Three behaviour flags (banner → behaviour settings in the dashboard, banner_config.behavior in a local configuration) change this:
| Flag | Default | Effect |
|---|---|---|
uk_analytics_exemption | off | UK visitors only: scripts tagged analytics may run before a decision. Every other category still needs consent, and once the visitor has decided, their decision applies. |
ccpa_opt_out_enabled | off | Visitors detected in the USA get the opt-out model: scripts run by default, a “Do Not Sell or Share My Personal Information” link is shown, and a Global Privacy Control signal is applied once as an opt-out (consent_type: do_not_sell_opt_out). When off, US visitors get the opt-in banner. |
reload_on_revoke | on | Reload the page when a visitor revokes a category whose scripts already ran on this page. Turn it off only if your tags react to consent changes themselves. |
Google Consent Mode v2
Section titled “Google Consent Mode v2”When Consent Mode is on, the SDK creates window.dataLayer and a gtag function if they do not exist and pushes:
gtag('consent', 'default', { …all denied…, wait_for_update: 500 })during start-up,gtag('consent', 'update', { … })after every decision and on each page load with a stored consent.
| Signal | Granted when this category is allowed |
|---|---|
ad_storage, ad_user_data, ad_personalization | marketing |
analytics_storage | analytics |
functionality_storage, personalization_storage | preferences |
security_storage | always |
The SDK also pushes the data layer events hashentry_consent_default and hashentry_consent_update.
Setup:
- Turn on Google Consent Mode for the banner in the dashboard, or add
data-consent-mode="true"to the script tag. - Place the HashEntry script tag above your Google tag / GTM snippet.
- If the
defaultcommand must precede Google tags on a visitor’s very first page view, usedata-consent-mode="true"and loadcmp.jswithoutasync. The dashboard setting arrives with the configuration, which on a first visit is loaded asynchronously; on later visits it is read from the cached configuration during start-up.
Consent Mode only sends signals. Tags that are not controlled by Consent Mode still need to be tagged for blocking as described above.
Headless mode
Section titled “Headless mode”With data-headless="true" the SDK renders nothing. It still loads the configuration, blocks tagged scripts, updates Consent Mode and records decisions.
CMP.on('banner:shown', ({ headless, reason, config }) => { if (headless) openMyBanner(config); // reason: no_consent | expired | reconsent | withdrawn | api | opt_out});
myAcceptButton.onclick = async () => { const result = await CMP.accept({ analytics: true, marketing: false }); if (!result.success && !result.pending_sync) showError(result.error);};Operating modes
Section titled “Operating modes”| Mode | Configuration source |
|---|---|
remote | Server only. data-site is required. |
local | The page only (CMP.setConfig, data-config or <script type="application/json" id="cmp-config">). |
local-fallback (default) | Server; if it cannot be reached, the page configuration. |
The mode only selects where the configuration comes from. Decisions are sent to the server in every mode as long as data-site is set. In local mode without data-site, decisions are kept in the browser only (local_only: true).
A local configuration uses the same shape as the server response:
<script type="application/json" id="cmp-config">{ "banner_config": { "layout": "bottom_bar", "theme": { "primary_color": "#2563EB", "font_family": "system", "theme_mode": "auto" }, "behavior": { "show_widget": true, "show_reject_button": true, "show_settings_button": true }, "categories": [ { "key": "necessary", "required": true, "default": true }, { "key": "analytics", "required": false, "default": false, "cookies": [{ "name": "_ga", "provider": "Google", "type": "cookie", "duration": "2 years" }] } ], "languages": { "default": "en", "translations": { "en": { "title": "Cookie preferences", "description": "We use cookies to measure how the site is used.", "accept_button_text": "Accept all", "reject_button_text": "Reject all", "settings_button_text": "Manage preferences", "save_selected_text": "Save selection", "modal_title": "Cookie settings", "always_active_text": "Always active", "categories": { "necessary": { "label": "Necessary", "description": "Required for the site to work." }, "analytics": { "label": "Analytics", "description": "Visitor statistics." } } } } } }}</script><script src="https://tool.hashentry.com/cmp.js" data-site="YOUR_SITE_API_KEY" data-mode="local" async></script>With data-site set, the SDK asks the server once at start-up for the cookie policy document, because a consent must reference its version. If none is attached, the banner is not shown (COOKIE_POLICY_MISSING). To show the banner even when the server cannot be reached, include documents (the cookie policy and its version) in the page configuration.
Languages and texts
Section titled “Languages and texts”All banner texts (title, description, buttons, category names) come from the banner configuration — they are not part of the script.
The SDK ships a small built-in dictionary, in English and Turkish only, for texts that belong to its own UI: the “Do Not Sell” dialog, the document approval buttons and error line, the cookie table headings and the close button. In any other language these texts fall back to English unless you provide them:
CMP.setLocale('de', { document: { approve: 'Ich stimme zu', reject: 'Ablehnen', saving: 'Wird gespeichert…', error: 'Ihre Zustimmung konnte nicht gespeichert werden. Bitte versuchen Sie es erneut.' }, ccpa: { link: '…', description: '…', cancel: 'Abbrechen', optOut: 'Widersprechen' }, cookies: { name: 'Name', provider: 'Anbieter', type: 'Typ', duration: 'Dauer', purpose: 'Zweck', category: 'Kategorie', count: '{n} Cookies', empty: 'Keine Cookies registriert.' }});A configuration can carry the same keys per language under languages.translations.<lang>.sdk_texts.
Bundles and on-demand modules
Section titled “Bundles and on-demand modules”| File | Content | Loaded |
|---|---|---|
cmp.js | Core and cookie consent | By your script tag. |
cmp-documents.<hash>.js | Document approval (showDocumentApproval, approveDocument) | By cmp.js, the first time one of these methods is called. |
cmp-preferences.<hash>.js | Preference centre, cookie details table, “Do Not Sell” confirmation dialog | By cmp.js, the first time the visitor opens one of them. |
You never add a second script tag. A site that only uses the cookie banner never downloads the document module.
- The module file is requested from the same location as
cmp.js(derived from the script’ssrc). - To avoid the short delay on the first call, load it up front:
data-modules="documents". - Calls made before the module has arrived simply wait for it. If it cannot be loaded, the method resolves with
success: falseanderror.code = "MODULE_LOAD_FAILED"; the next call tries again. - If the
preferencesmodule cannot be loaded, the banner stays on screen with its accept and reject buttons, theerrorevent fires withscope: "module"andcode: "MODULE_LOAD_FAILED", and the next click tries again. To open the preference centre without any delay, load it up front:data-modules="preferences". - The file name contains a content hash that is embedded in
cmp.jsat build time, socmp.jsalways asks for the module built with it.
Content Security Policy. The module is loaded with a <script> element from the same origin as cmp.js; the SDK uses neither eval nor inline scripts. For scripts, the SDK origin is all you need:
script-src https://tool.hashentry.com;connect-src https://system.hashentry.com;- The SDK styles its UI with
<style>elements inside its own shadow root. If your policy restrictsstyle-src, either allow'unsafe-inline'for styles or use a nonce: put the nonce on thecmp.jstag (<script nonce="…" …>) and the SDK copies it to the style elements and to the module script tag it creates. - With
script-src 'strict-dynamic'and a nonce on thecmp.jstag, the module script is allowed without listing the origin. - The document approval dialog renders the document in a sandboxed
iframewithout asrc; scripts inside the document do not run.
Subresource Integrity. https://tool.hashentry.com/cmp.js is updated in place when a new SDK version is released, so an integrity attribute on that URL would break on the next release. If you need SRI, host a reviewed copy of cmp.js together with the cmp-documents.*.js / cmp-preferences.*.js files of the same build on your own origin and set integrity on your own tag.
Self-hosting. Copy cmp.js, cmp-documents.<hash>.js, cmp-preferences.<hash>.js, cmp-preferences.js and cmp-documents.js from the same build into one folder. If the module files live elsewhere, set data-module-base.
First-party proxy
Section titled “First-party proxy”To send API requests through your own domain, set data-api-url (or CMP.setApiUrl) to a path or URL on your side and forward it to the HashEntry API without changing the path below the prefix:
<script src="https://tool.hashentry.com/cmp.js" data-site="YOUR_SITE_API_KEY" data-api-url="/hashentry-api" async></script># /hashentry-api/tool/v1/consents → https://system.hashentry.com/tool/v1/consentslocation /hashentry-api/ { proxy_pass https://system.hashentry.com/; proxy_set_header Host system.hashentry.com; proxy_ssl_server_name on; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;}The request signature covers the path /tool/v1/..., so the upstream request must arrive with exactly that path. The recorded IP address is the one HashEntry sees; whether the visitor’s address is taken from X-Forwarded-For depends on the proxy being trusted on the HashEntry side — ask support before relying on it.
What the SDK stores in the browser
Section titled “What the SDK stores in the browser”| Name | Where | Purpose |
|---|---|---|
_he_consent | first-party cookie | The visitor’s decision (categories, timestamps, server token). |
he_consent_token | localStorage | Copy of the same decision. |
he_config_cache | localStorage | Cached configuration (up to 7 days, revalidated in the background on every load). |
he_pending_consents | localStorage | Decisions not yet accepted by the server (removed once delivered; dropped after 7 days). |
he_gpc_applied | localStorage | Marks that a GPC opt-out was applied once. |
he_sdk_reload | sessionStorage | Guards the SDK self-update against reload loops. |
The SDK does not load fonts or any other resource from third parties.