Browser SDK

Reference for the Kentro Pixel browser SDK v1.3.0: methods, options, storage, transport, consent integration and signup forms.

The browser SDK is a single dependency-free ES2017 file that sends page views and events from your site to the Kentro Pixel collector. Every method is called through one global function, kentroPixel('method', ...arguments), and calls made before the file loads are queued and replayed.

In this page

  • Loading the SDK
  • Methods at a glance
  • init, page, track, identify, consentChanged, flush, reset, experiment
  • Signup forms (form)
  • Automatic page views and single-page apps
  • Data attributes
  • Storage and lifetimes
  • Transport and retries
  • Global Privacy Control and consent integration
  • No-JavaScript fallback

Loading the SDK

The SDK is served from https://pixel.kentro.live/sdk/v1/kentro-pixel.js with Cache-Control: public, max-age=3600, an ETag, Access-Control-Allow-Origin: * and Cross-Origin-Resource-Policy: cross-origin. It is served unminified so you can read it (about 27 KB). Once loaded, kentroPixel.version is 1.3.0. Install it with the snippet from the Quickstart.

Methods at a glance

MethodPurpose
init(options)Start the SDK. Runs once; the snippet calls it for you when the tag has data-key.
page(properties?)Send an extra marketing.page_viewed.
track(name, properties?, refs?)Send any builtin or custom event.
identify(token)Bind a signed-in visitor with an identity token your server minted.
consentChanged(snapshot)Report the visitor's consent and every later change.
flush()Send the queue now.
reset()Forget ids, token, campaign and experiment assignments.
experiment(ref, arms)Get a sticky experiment arm and record the exposure.
form(options)Capture consented newsletter signups for Kentro Email.

init

kentroPixel('init', {
  key: 'pk_live_Xq3M9b2LkV7tR1sNw8YhC4Ze', // default: the script tag's data-key
  endpoint: 'https://pixel.kentro.live',    // default: the origin of the script src
  autoPageviews: true,
  spa: true,
  consent: { analytics: 'unknown', marketing: 'unknown', functional: 'unknown', defaultWhenUnknown: 'denied' },
  campaignRef: 'cmp_spring_launch',
  pageRef: 'page_pricing',
  experimentRef: 'exp_pricing_page'
});
OptionDefaultDescription
keythe tag's data-keyProperty public key.
endpointorigin of the SDK srcCollector base URL. Required when the script tag has no data-key.
autoPageviewstrueSend marketing.page_viewed during init.
spatrueSend a page view on history-based route changes.
consenteverything unknownInitial consent snapshot (see consentChanged). May also carry defaultWhenUnknown.
consent.defaultWhenUnknowndeniedHow the SDK treats unknown analytics consent for its own browser storage (granted or denied). It does not change the collector's decision, which follows the property's settings.consent.
consentProvidernoneA function returning a consent snapshot (or null). Called once during init; its result wins over consent.
functionalStoragefalse (true with the tag's data-functional-storage="true")Declares that your site keeps the SDK's ids as functional storage: they are then stored without analytics consent, until the snapshot says functional: 'denied'.
campaignRefthe tag's data-campaign-refSent as campaign_ref on every event.
pageRefthe tag's data-page-refSent as landing_page_ref on every event.
experimentRefthe tag's data-experiment-refSent as experiment_ref on every event.
  • init runs once; later calls are ignored.
  • Without both a key and an endpoint the SDK stays inactive, silently.
  • Queue init before any other call. Queued page, track, flush and experiment calls placed before init are dropped, and an init replayed after consentChanged replaces that consent with its own.

page

kentroPixel('page', { page_type: 'pricing', page_name: 'Pricing' });

Sends marketing.page_viewed, filling page_path and title from the current page when you leave them out. The event accepts only page_type, page_name, page_path and title; other properties are rejected with SCHEMA_VIOLATION.

track

kentroPixel('track', 'commerce.added_to_cart', {
  product_id: 'sku-123',
  quantity: 1,
  unit_price: { amount_minor: 1999, currency: 'USD' }
}, { campaign_ref: 'cmp_spring_launch' });

track(name, properties?, refs?) sends any builtin or custom event. The optional refs object may set:

RefEffect
campaign_ref, creative_ref, landing_page_ref, experiment_ref, experiment_variant_ref, order_refJoin keys for this event only.
event_idYour own event id: 8 to 128 letters, digits, _, ., : or -. Use it when your server sends the same event, so the collector counts the pair once (see Server API). Any other value is ignored and the SDK generates a random id.

Person references are set with identify, never through refs.

identify

kentroPixel('identify', identityToken); // minted by your server

Binds the visitor to a person reference without trusting the browser: pass the identity token your server minted for the signed-in user (a string, or { token }). The SDK sends it as identity_token on every later event, and the collector verifies it before it sets subject_ref or commerce_subject_ref. A value that is not token-shaped (two base64url parts joined by a dot, at most 1,024 characters) is ignored. The token is kept in memory only: call identify on every page load after login, and kentroPixel('identify', null) on sign-out (reset clears it too). See Identity tokens.

consentChanged

kentroPixel('consentChanged', {
  analytics: 'granted',                  // 'granted' or 'denied'; anything else counts as unknown
  marketing: 'denied',
  functional: 'granted',
  policy_version: '2026-10',             // the version of your consent text
  source: 'cmp',                         // where the choice was made
  recorded_at: '2026-10-09T17:04:58Z',   // default: now
  region: 'US-CA'
});

Every event carries the snapshot in force when the event was created. gpc is filled in by the SDK from navigator.globalPrivacyControl; the purposes stay exactly as you report them. Storage follows the snapshot: when storage becomes allowed the SDK persists its ids; on an explicit denial (analytics: 'denied', or functional: 'denied' when you declared functional storage) it deletes all of them and switches to new in-memory ids, and a later grant persists a new anonymous id, never the old one. Call consentChanged for every change, including withdrawal.

flush

kentroPixel('flush');

Sends the queue now. Without it the SDK sends every 2 seconds, or as soon as 10 events are waiting, with up to 100 events per request.

reset

kentroPixel('reset');

Deletes everything the SDK stored (kp_anon, kp_sess, kp_cmp and kp_exp_*), forgets the identity token, the campaign reference and the experiment assignments, and continues with a new anonymous id and session id. Use it on logout or when a visitor asks to start over.

experiment

function applyPricingExperiment() {
  var arm = window.kentroPixel('experiment', 'exp_pricing_page', ['control', 'annual_first']);
  if (arm === 'annual_first') document.body.classList.add('annual-first');
}
if (window.kentroPixel && window.kentroPixel.version) applyPricingExperiment();
else document.querySelector('script[src*="kentro-pixel"]').addEventListener('load', applyPricingExperiment);

experiment(experimentRef, arms) returns the visitor's arm and sends marketing.experiment_exposed (with experiment_ref, experiment_variant_ref and properties.arm) once per session per experiment. The arm is derived from the anonymous id and is sticky: it is stored in kp_exp_<experimentRef> when storage is allowed and kept in memory for the page otherwise. The return value is only available once the SDK has loaded (the queue stub returns nothing), and it is null when the SDK is not initialized, so treat null as your control.

A Kentro GO experiment is judged on one primary metric, one of the five conversion events: marketing.lead_captured, marketing.signup_completed, marketing.demo_booked, commerce.order_placed or commerce.order_paid. Its results count only conversions of that type, recorded in a session that saw the experiment, after the exposure.

Signup forms (form)

form connects an existing newsletter or signup form on your site to Kentro Email. On each submit of the matched form, and only when its consent checkbox is checked, the SDK posts the email address (and first name, when present) to Kentro Email's signup capture. It never blocks or delays the form's own submit.

kentroPixel('form', {
  selector: '#newsletter-form',          // or formId: 'newsletter-form'
  consentSelector: 'input[name="marketing_consent"]',
  emailField: 'email',                   // optional
  firstNameField: 'first_name'           // optional
});
OptionRequiredDescription
selector or formIdyes, one of themA CSS selector the form must match, or the form's id.
consentSelectoryesA selector, inside the form, for the consent checkbox. Nothing is sent unless it is checked.
emailFieldnoThe name of the email input. Default: the first input[type=email] or [name=email].
firstNameFieldnoThe name of the first-name input. Default: [name=firstName]. Cut to 100 characters.
debugnotrue logs a console warning when the options are incomplete.
  • The SDK must be initialized with a key. The post goes to https://t.kentro.email/t/s/<public key> (staging: https://t-stg.kentro.email) as text/plain JSON with sendBeacon, else fetch with keepalive and no credentials. The body carries email, firstName, consent: true, formId (the form's id, up to 64 characters) and source: "sdk_form".
  • Email addresses longer than 254 characters or without a valid shape are not sent.
  • The capture fails closed on origins: the property needs a non-empty allowed_origins that contains the page's host (see Domains); anything else answers 403.
  • An accepted signup answers 202. Kentro Email records the contact and the consent; each property accepts about 60 signups a minute per collector instance, and extra signups are dropped silently with the same 202.
  • Where Kentro Email is not enabled, form sends nothing.
  • With a Content-Security-Policy, add https://t.kentro.email to connect-src.

Automatic page views and single-page apps

  • init sends marketing.session_started (entry_url, referrer_domain, landing) when a new session begins, then one marketing.page_viewed unless autoPageviews is false.
  • With spa: true the SDK wraps history.pushState and history.replaceState and listens for popstate. Whenever the URL differs from the last page view it sends a new marketing.page_viewed. URL rewrites through replaceState count too; if your app rewrites the URL often, set spa: false and call page yourself.
  • A session ends after 30 minutes without events. The next event starts a new session id and sends marketing.session_started first.

Data attributes

AttributeEquivalent
data-keyinit({ key })
data-campaign-refinit({ campaignRef })
data-page-refinit({ pageRef })
data-experiment-refinit({ experimentRef })
data-functional-storage="true"init({ functionalStorage: true })

Campaign references are resolved in this order: refs.campaign_ref on the call, then campaignRef or data-campaign-ref, then a utm_id starting with cmp_ on the page URL (Kentro GO campaign links). A cmp_ reference from the URL is reused for the rest of the session.

Storage and lifetimes

NameWhereContentsLifetimeWritten
kp_anonFirst-party cookie (Path=/, SameSite=Lax, Secure on HTTPS) and a localStorage copyRandom anonymous id (anon_ plus 32 hex characters)13 months (390 days)Only while storage is allowed; deleted on an explicit denial
kp_sesslocalStorageRandom session id and two timestampsReplaced after 30 idle minutesOnly while storage is allowed
kp_cmpsessionStorageKentro GO campaign reference taken from utm_idThe browser tab sessionOnly while storage is allowed
kp_exp_<experimentRef>localStorageAssigned experiment armUntil an explicit denial or reset clears itOnly while storage is allowed

Storage is allowed when the snapshot's analytics is granted (or unknown with defaultWhenUnknown: 'granted' in the init options), or when you declared functional storage and functional is not denied. Without it the ids live in memory only, so every page load gets a new anonymous id. Moving from allowed to unknown stops writing without deleting anything; only an explicit denial (or reset) deletes.

Transport and retries

Events go to POST /v1/events with fetch (keepalive, Content-Type: text/plain, so no CORS preflight; the key travels in the body). When the page is hidden or unloaded, the SDK hands the remaining queue to navigator.sendBeacon. Responses 429, 5xx and network errors are retried up to three times with exponential backoff (at most 30 seconds); other 4xx answers are not retried. The in-memory queue holds at most 200 events; when it is full, the oldest are dropped.

Global Privacy Control

  • In the browser, the SDK reports navigator.globalPrivacyControl as gpc: true in every snapshot. It never changes the purposes your consent manager reported.
  • At the collector, consent.gpc: true or the Sec-GPC: 1 request header makes marketing consent denied for that event while settings.consent.honorGpc is true (the default). An event with the marketing purpose that only GPC blocked is recorded suppressed with GPC_OPT_OUT.
  • Ad networks (Meta Conversions API, Google Ads) never receive a person whose latest consent carries a GPC signal, whatever honorGpc says.

Consent integration

The SDK does not read your consent manager on its own. Translate your consent manager's categories into Kentro purposes, pass the current state at init, and report every change with consentChanged. In this example window.MyCmp and the mycmp:change event stand for your consent manager's API:

<script>
  window.kentroPixel=window.kentroPixel||function(){(window.kentroPixel.q=window.kentroPixel.q||[]).push(arguments)};

  function kentroConsentFrom(choices) {
    return {
      analytics: choices.statistics ? 'granted' : 'denied',
      marketing: choices.advertising ? 'granted' : 'denied',
      functional: 'granted',
      policy_version: choices.policyVersion,
      source: 'cmp',
      region: choices.region
    };
  }

  kentroPixel('init', {
    // Called once during init. Return null while the visitor has not chosen yet.
    consentProvider: function () {
      var choices = window.MyCmp && window.MyCmp.getChoices();
      return choices ? kentroConsentFrom(choices) : null;
    }
  });

  // Every later change, including withdrawal.
  window.addEventListener('mycmp:change', function (event) {
    kentroPixel('consentChanged', kentroConsentFrom(event.detail));
  });
</script>
<script async src="https://pixel.kentro.live/sdk/v1/kentro-pixel.js" data-key="pk_live_Xq3M9b2LkV7tR1sNw8YhC4Ze"></script>
document.getElementById('consent-accept').addEventListener('click', function () {
  kentroPixel('consentChanged', { analytics: 'granted', marketing: 'granted', functional: 'granted', source: 'banner', policy_version: '2026-10' });
});
document.getElementById('consent-reject').addEventListener('click', function () {
  kentroPixel('consentChanged', { analytics: 'denied', marketing: 'denied', functional: 'granted', source: 'banner', policy_version: '2026-10' });
});

No-JavaScript fallback

Where JavaScript is not available, send one envelope (or { "events": [...] }) as base64url-encoded JSON in the e parameter of the GIF endpoint:

<img src="https://pixel.kentro.live/v1/events/pixel.gif?key=pk_live_Xq3M9b2LkV7tR1sNw8YhC4Ze&e=eyJldmVudF9uYW1lIjoibWFya2V0aW5nLnBhZ2Vfdmlld2VkIn0" width="1" height="1" alt="">

The answer is always a 200 transparent GIF. The X-Kentro-Pixel-Status response header is accepted when the request was processed and the error code otherwise; per-event results are not returned, so use the event debugger. The e value can be at most 16 KB. The same key, origin, consent and validation rules apply.