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
| Method | Purpose |
|---|---|
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'
});
| Option | Default | Description |
|---|---|---|
key | the tag's data-key | Property public key. |
endpoint | origin of the SDK src | Collector base URL. Required when the script tag has no data-key. |
autoPageviews | true | Send marketing.page_viewed during init. |
spa | true | Send a page view on history-based route changes. |
consent | everything unknown | Initial consent snapshot (see consentChanged). May also carry defaultWhenUnknown. |
consent.defaultWhenUnknown | denied | How 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. |
consentProvider | none | A function returning a consent snapshot (or null). Called once during init; its result wins over consent. |
functionalStorage | false (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'. |
campaignRef | the tag's data-campaign-ref | Sent as campaign_ref on every event. |
pageRef | the tag's data-page-ref | Sent as landing_page_ref on every event. |
experimentRef | the tag's data-experiment-ref | Sent as experiment_ref on every event. |
initruns once; later calls are ignored.- Without both a key and an endpoint the SDK stays inactive, silently.
- Queue
initbefore any other call. Queuedpage,track,flushandexperimentcalls placed beforeinitare dropped, and aninitreplayed afterconsentChangedreplaces 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:
| Ref | Effect |
|---|---|
campaign_ref, creative_ref, landing_page_ref, experiment_ref, experiment_variant_ref, order_ref | Join keys for this event only. |
event_id | Your 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
});
| Option | Required | Description |
|---|---|---|
selector or formId | yes, one of them | A CSS selector the form must match, or the form's id. |
consentSelector | yes | A selector, inside the form, for the consent checkbox. Nothing is sent unless it is checked. |
emailField | no | The name of the email input. Default: the first input[type=email] or [name=email]. |
firstNameField | no | The name of the first-name input. Default: [name=firstName]. Cut to 100 characters. |
debug | no | true 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) astext/plainJSON withsendBeacon, elsefetchwithkeepaliveand no credentials. The body carriesemail,firstName,consent: true,formId(the form's id, up to 64 characters) andsource: "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_originsthat contains the page's host (see Domains); anything else answers403. - 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 same202. - Where Kentro Email is not enabled,
formsends nothing. - With a Content-Security-Policy, add
https://t.kentro.emailtoconnect-src.
Automatic page views and single-page apps
initsendsmarketing.session_started(entry_url,referrer_domain,landing) when a new session begins, then onemarketing.page_viewedunlessautoPageviewsisfalse.- With
spa: truethe SDK wrapshistory.pushStateandhistory.replaceStateand listens forpopstate. Whenever the URL differs from the last page view it sends a newmarketing.page_viewed. URL rewrites throughreplaceStatecount too; if your app rewrites the URL often, setspa: falseand callpageyourself. - A session ends after 30 minutes without events. The next event starts a new session id and sends
marketing.session_startedfirst.
Data attributes
| Attribute | Equivalent |
|---|---|
data-key | init({ key }) |
data-campaign-ref | init({ campaignRef }) |
data-page-ref | init({ pageRef }) |
data-experiment-ref | init({ 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
| Name | Where | Contents | Lifetime | Written |
|---|---|---|---|---|
kp_anon | First-party cookie (Path=/, SameSite=Lax, Secure on HTTPS) and a localStorage copy | Random anonymous id (anon_ plus 32 hex characters) | 13 months (390 days) | Only while storage is allowed; deleted on an explicit denial |
kp_sess | localStorage | Random session id and two timestamps | Replaced after 30 idle minutes | Only while storage is allowed |
kp_cmp | sessionStorage | Kentro GO campaign reference taken from utm_id | The browser tab session | Only while storage is allowed |
kp_exp_<experimentRef> | localStorage | Assigned experiment arm | Until an explicit denial or reset clears it | Only 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.globalPrivacyControlasgpc: truein every snapshot. It never changes the purposes your consent manager reported. - At the collector,
consent.gpc: trueor theSec-GPC: 1request header makes marketing consentdeniedfor that event whilesettings.consent.honorGpcistrue(the default). An event with the marketing purpose that only GPC blocked is recordedsuppressedwithGPC_OPT_OUT. - Ad networks (Meta Conversions API, Google Ads) never receive a person whose latest consent carries a GPC signal, whatever
honorGpcsays.
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.

