Skip to content
Documentation

Install website analytics

One script on your own website. It measures what visitors do before the form, joins it to the requests and the signed quotes, and stores nothing on a visitor's device until they have agreed to statistics.

The snippet

Put this line on every page, ideally in the <head>. The key is in the portal under Website → Install; it is public and identifies nothing but your site.

<script src="https://offerte.yourdomain.com/zv.js"
        data-zv-site="YOUR_SITE_KEY"
        defer></script>

If your form lives on your own subdomain (offerte.yourdomain.com), use THAT address rather than app.zinevu.com. The measurement is then first-party: no blocklist knows the host, Safari does not shorten what it stores, and a visit is joined to its request without a parameter on any link.

  • One line, no configuration. Pages, languages and sections are recognised on their own.
  • Nothing is written to a visitor's device before a statistics decision — so the script also runs while your cookie banner is still unanswered.
  • The script is idempotent: pasted twice, it measures once.
  • It adds nothing to your page and waits for nothing: it measures beside your site, not inside it.
  • Append ?zv_test=1 to an address to silence your own tab while testing.

What it measures

All of this without configuring anything or writing a tag. Google Analytics sees most of it only if somebody builds a tag for it first.

Visits

  • Visits, visitors and page views — three numbers, never one
  • Time per page, scroll depth, and the page somebody left from
  • Channel, source, campaign and advertising click ids
  • Device, browser, the browser's language and the page's language
  • City and country from a locally installed database — no IP address goes to any third party
  • Core loading metrics (LCP, INP, TTFB) per page

Behaviour

  • Modals opening — on many sites that IS the conversion, and it has no URL of its own
  • Cookie banners counted separately, because that is not a conversion
  • Disclosures and FAQs: which question somebody opened
  • Tabs and product switches inside one page
  • Clicks on phone, email (including addresses Cloudflare has rewritten), WhatsApp, downloads and outbound links
  • Your own forms: started, sent or given up halfway — field names only, never what was typed
  • Rage clicks and dead clicks: a button that does nothing
  • On-site searches, copied phone numbers, JavaScript errors
  • Where on the page people click, in a 32×32 grid

Money

  • Which page led to the form, and which visit produced the request
  • Request → quote → signed → revenue, per page, channel and campaign
  • First touch AND last touch, separately — one figure would write off half your advertising unfairly
  • One customer's journey on the lead page: which pages, how long, how many visits

What lands on a visitor's device

This table is what belongs in your own cookie policy. Before a statistics decision there is nothing in it — that is not a caveat, it is the design.

NameKindWhat forWhenRetention
zv_vidCookie (first-party, on your own domain) + localStorageRecognises a returning visitor across days and joins a visit to a request. Holds a random number, no personal data.Only after the visitor has accepted statistics cookies.One year after the last visit. If the visitor withdraws consent, both copies are erased immediately.
zv_vid_tlocalStorageThe time of the last visit, so the retention above is true.Alongside zv_vid.Same.
zv_testsessionStorageSilences this tab while testing. Holds only “1”.Only when somebody types ?zv_test=1 into the address themselves.Until the tab closes.

Without consent the script still measures — but with a key derived on our server from the IP address and browser, renewed every day and never stored. Which means a visitor who returns tomorrow is a new visitor. That is the honest price of storing nothing, and the report says so beside the number.

Consent

The script reads the decision itself, or you hand it over. Three sources, in this order.

  • Zinevu.consent({ statistics: true }) — if you call this from your own cookie banner, that is the source that counts.
  • Complianz: only cmplz_banner-status=dismissed proves a decision was made. Without that flag, a 'deny' in the categories is read as 'not asked yet' rather than as a no.
  • data-consent="assumed" on the script tag — only for a site that has already recorded the decision bindingly elsewhere.
  • The browser's Global Privacy Control always wins and puts everything back to zero.
  • Zinevu.consent({ statistics: false }) erases the number from the cookie AND from localStorage, and stops sending it immediately.
// in your own banner, on Accept
window.Zinevu?.consent({ statistics: true })

// and on Decline or Withdraw
window.Zinevu?.consent({ statistics: false })

Calling consent() only after load is fine: the script waits, and does not read silence as a no.

Your own events

Two ways to mark something we cannot guess.

  • data-zv-track="name" on a button or link: the click appears under that name in the report.
  • Zinevu.track('goal', { … }) for a goal you define yourself.
  • Zinevu.page() if your router swaps a page without the URL changing — normally that is noticed on its own.
  • Zinevu.handoffToken() returns this page's token; put it on a hand-built link to the form as zvh= and the visit joins the request exactly.
<a href="/showroom" data-zv-track="showroom-button-hero">Visit our showroom</a>

<script>
  window.Zinevu?.track("goal", { label: "brochure-downloaded" })
</script>

Into your own tags

Everything measured is also announced on your page. Your existing GA4, Ads or Meta setup can ride along for free.

  • dataLayer receives every event as zinevu_<kind>, for example zinevu_modal_view or zinevu_click_tel.
  • zinevu:insight is dispatched on document, with the event in event.detail.
  • We never take over a name your platform optimises on — no generate_lead, no Purchase. What you do with ours is your decision, in your own tag manager.
  • dataLayer is only used if it already exists; we do not create it.
document.addEventListener("zinevu:insight", (e) => {
  // e.detail = { kind, label, url, page_group, href, at }
  if (e.detail.kind === "click_tel") {
    gtag("event", "phone_click", { label: e.detail.label })
  }
})

If nothing appears

The portal says “nothing received yet”.

Open your site, watch your browser's network tab for a request to /api/zv/e and look for status 204. If it never appears, the script tag did not load or data-zv-site is wrong — the key is 24 characters and is in the portal.

I see visits but no requests, and the revenue column stays empty.

Then the join between visit and form is the problem, not your site. The report says how many click-throughs could be matched, and warns below 60%. Serving the script from your own subdomain is what fixes it.

Every visitor seems to come from one place.

Almost always a proxy or firewall replacing the visitor's IP address. We would rather refuse to measure than show a thousand visitors as one person; get in touch and we will look at it.

Our own office visits are in the numbers.

Put your office IP in the portal under Forms → Ignored addresses. That same list applies to both reports, so you never type it twice.

We use React or Next.js.

Then there is nothing to do: route changes are noticed, and a page announced twice during hydration is counted once.

Zinevu

Ready to measure

The key, the snippet and the status are in the portal under Website. Need help placing it? Send this page to whoever builds your website.