This page is for whoever owns your site's code. Send them this link. It covers the tracking snippet, consent mode, running several domains, per-order revenue, identified traffic, and visitor properties.
The snippet below is the shape, with a placeholder where your site key goes, so don't paste it as-is. Evarist hands you the real one when you add your site at app.evarist.ai/start, and it stays available in the app under Settings.
<script>
window.evarist=window.evarist||function(){(window.evarist.q=window.evarist.q||[]).push(arguments)};
(function(){var s=document.createElement("script");s.src="https://app.evarist.ai/script.js";s.setAttribute("data-evarist","YOUR-SITE-KEY");(document.head||document.documentElement).appendChild(s);})();
</script>
Put it in the <head> of every page: directly in your templates, or as a custom-HTML tag in your tag manager (Google Tag Manager, etc.) fired on all pages. The snippet is deliberately self-injecting so tag managers can't strip its site key; paste it whole, don't convert it to a bare external script.
It weighs about 13 KB compressed, loads asynchronously after your page, never blocks rendering, and fails silently if it can't load, so your site is unaffected either way.
Separately from this snippet, Evarist also fetches your pages server-side, to build the site model and to verify on-page tags. If your WAF or CDN needs an allowlist rule for that crawler, see the EvaristBot page.
By default the snippet sets its first-party analytics cookies as soon as it loads. If your compliance posture requires no cookies before consent, switch on consent mode: Evarist still counts every visitor from their first page, but keeps its identifiers in per-tab session storage, zero cookies, until you signal that the visitor consented. Add one line inside your snippet, before the loader:
<script>
window.evarist=window.evarist||function(){(window.evarist.q=window.evarist.q||[]).push(arguments)};
evarist('consent','pending'); // ← the one added line
/* …the rest of your Evarist snippet, unchanged… */
</script>
Then, from your cookie banner's callbacks, signal the visitor's choice:
evarist('consent', { analytics: true }); // consented — switch to cookies, keep the same visit
evarist('consent', { analytics: false }); // declined or withdrawn — remove Evarist's cookies
Consent is remembered: once granted, later pages start in cookie mode on their own, and evarist('consent','pending') deliberately leaves the _evarist cookie of an earlier grant in place. "Zero cookies" describes a visitor who hasn't consented yet, not every page load. On Shopify Online Store storefronts, skip the recipes below: with consent mode switched on, Evarist reads Shopify's own Customer Privacy signal and reacts to the store's banner automatically. A headless storefront carries that signal only if it loads Shopify's customer-privacy runtime; otherwise use the recipes like any other site.
Recipes for popular consent tools (category names vary per setup, so map your analytics category, and check your tool's current docs):
// Cookiebot
window.addEventListener('CookiebotOnAccept', function () {
if (Cookiebot.consent.statistics) evarist('consent', { analytics: true });
});
window.addEventListener('CookiebotOnDecline', function () {
evarist('consent', { analytics: false });
});
// OneTrust (C0002 is the default "Performance" group — use your own group id)
function OptanonWrapper() {
var on = (',' + (window.OnetrustActiveGroups || '') + ',').indexOf(',C0002,') !== -1;
evarist('consent', { analytics: on });
}
// Axeptio (use the vendor/step names from your project)
window._axcb = window._axcb || [];
window._axcb.push(function (axeptio) {
axeptio.on('cookies:complete', function (choices) {
evarist('consent', { analytics: !!choices.analytics });
});
});
// Any other CMP: call the two lines above from its accept / decline callbacks.
Trade-off to know: until a visitor consents, their identity is per-tab and per-visit: pageviews and sessions stay accurate, but a returning non-consented visitor counts as new. That's the conservative behavior by design.
Run the same brand on several domains, your .com and .fr, or a separate checkout domain? Install the same snippet on every domain, then add the other domain in the app, under Settings, Domains. From then on a visitor who crosses between them is recognised as one person, and their journey reads as one journey.
The same panel lists a site's domains and removes one at any time.
When the revenue amount varies per order and your checkout is your own code, fire the conversion yourself:
evarist('goal', 'purchase', { value: 49.90 });
The value is in major units (49.90 = €49.90). This fires the same conversion event as a no-code goal and respects the same per-session dedup.
Two input rules: the goal name is lowercased, then must be 1 to 29 characters of letters, digits, _ or -, with no spaces (an invalid name is dropped with a console warning). And value must be a number, not a string: { value: 49.90 }, never { value: "49.90" }, because a string is ignored and the conversion fires without revenue.
For fixed-value outcomes (a signup worth €49), skip the code and ask Evarist to create the goal with a value instead: Track what matters.
Evarist can see that someone opened a product page. Whether it can tell you which product depends on how your store is built.
On Shopify, the id is already collected. The native app's web pixel sends the product id with each product view, so the data is there with nothing to wire. Asking about individual products on top of it is still being finished, so if that is what you are after, tell us and we will let you know the moment it lands. See Evarist on Shopify.
On your own storefront code, not yet. The tracker's track call takes an event name and nothing else, so there is no supported way to attach a product id to a view today. If that is what you need, tell us: it is on our list, and knowing it is blocking you moves it up.
Optionally, Evarist can connect the email a visitor submits on one of your forms to their on-site behaviour, so "who asked for a demo this week, and what did they look at first?" becomes answerable. It's off by default, per site, and double-gated: it captures nothing until you switch it on and the visitor has consented.
Turn it on: ask us to switch it on for your site. It needs Evarist's own tracker on the page.
The consent gate. Captured emails are only sent after an explicit consent signal reaches the tracker. If you use consent mode, the evarist('consent', { analytics: true }) call you already wire is that signal; if you don't, wire that same one call from your cookie banner's accept path. On Shopify, switch on consent mode and the store's banner drives it automatically.
What gets captured. Emails submitted in Wisepops popups and embeds are picked up automatically. For any other form, call the tracker from your own submit handler:
evarist('identify', { email: 'visitor@example.com' });
From then on, ask about identified visitors in the app, or in your assistant. Capture can be switched off the same way you switched it on, any time. Because the tracker reads its config from an edge cache, Evarist warns you in the rare case the cache flush fails (pages already loaded could keep capturing for up to an hour).
Attach a fact you know about a visitor, such as is_employee, plan or est_important, so you can ask Evarist to segment or exclude by it. It's declared-only: the tracker only ever sends a property name you declared first, up to 20 per site.
Declare it: ask your assistant ("declare a property called is_employee"), or use the Properties panel in the app under Settings. Pick a type (boolean, string, or number) and where the value comes from: your own code, or source data_layer (a GTM dataLayer path).
Set it from your code. Call the tracker with the declared name and a matching value:
evarist('set', { is_employee: true });
evarist('set', { plan: 'gold', seats: 12 });
An undeclared name never leaves the browser: no error, it is silently dropped. A value that doesn't match the declared type (a string where a number was declared, say) is dropped the same way. String values are capped at 256 characters.
Or map it from your dataLayer. Declare the property with source data_layer and a dot-path (e.g. user.vip), and the tracker reads it directly, no extra call needed:
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({ user: { vip: true } });
// declared with source "data_layer", path "user.vip" — the tracker picks it up
// from the existing dataLayer on load, and from every later push, automatically
Consent-gated, exactly like identified traffic: nothing ships before evarist('consent', { analytics: true }). A value set before consent is queued, not dropped: it sends on the first event after the visitor consents.
Property names that look like personal data (email, phone, …) are rejected at declare time, because properties are for segmentation, not identity; see identified traffic to capture an email instead.