Documentation

Everything you need to install and configure Korvus.

Data collected

The complete list of the data Korvus collects, before and after cookie consent, with the CNIL classification of each field.

Philosophy

Korvus collects the bare minimum needed to detect your site's revenue leaks. Every piece of data collected serves directly to identify a problem and to calculate its impact in euros.

Korvus is not a behavioral analytics tool: it builds no individual heatmaps or user profiles, and does no fingerprinting or cross-domain tracking. It does not record browsing continuously. On a detected incident, and only if you have turned on the video proof module, it can produce a video proof whose text is masked before it leaves the browser, and which can only be viewed from that incident's case file. The video produced lasts at most 94 seconds. The code enforces this upper bound; a typical proof is much shorter, and there is no minimum length.

This module is optional and turned on site by site. It changes what is collected and what the snippet does on the page: the "Video proof module" section below covers it in full.

The snippet reads structured signals in your site's DOM: action button text, product names and prices, visible error messages, promo codes, payment method labels, out-of-stock statuses. It never reads free page content, data your visitors enter, or personal information displayed in customer accounts.

The snippet observes, the server calculates

Our architectural principle is non-negotiable: no business calculation, no classification and no detection logic runs in your visitor's browser. The Korvus snippet is limited to passive observers (standard browser APIs, read-only DOM access). It sends raw signals to the Korvus server, which makes the decisions. As a result, the impact on your site's speed and responsiveness (TBT, INP) stays undetectable, including in real-world conditions.

In practice, the snippet:

  • Only runs after the page has fully loaded (window load + setTimeout(0))
  • Never writes to the DOM and never forces a reflow (one exception if you have turned on the video proof module: see "Video proof module" below)
  • Switches itself off after 30 minutes of inactivity and resumes on the first interaction

What Korvus does not collect

  • Form content: no reading of emails, passwords, postal addresses, bank card numbers, order numbers or any personal information entered by the user
  • Payment fields: no access to payment iframes (Stripe Elements, Adyen, PayPlug, PayPal), no reading of CVV, card number or 3D Secure code
  • Content of login, account and pro/B2B pages: on these identifying pages, the snippet collects the same anonymized technical signals as everywhere else (hashed selectors, errors, friction points), but never reads their content: no name, no email, no password, no order history, no saved payment methods, no entered data or displayed personal information
  • HTTP request and response bodies: Korvus observes failing network requests to extract technical metadata (URL without parameters, method, status code, duration, response content type), but never reads a request body, a response body or authentication headers
  • Cookies: Korvus reads none of your site's cookies (except korvus_optout, which it sets itself when a visitor objects)
  • localStorage / sessionStorage: no access to your site's local storage, except a technical read of structural cart keys (cart, shopify_cart, woocommerce_cart_hash) to confirm an add-to-cart
  • Continuous browsing recording, individual heatmaps, keystrokes, cross-domain tracking: never collected, under any configuration. An incident video proof is none of these: it is only kept if an incident occurred, it covers the pageview where it happened (and the previous page in the case of an error), the video produced lasts at most 94 seconds, and its text is masked before it leaves the browser.
  • Mouse movements, click coordinates, scrolling: never collected by the base snippet, under any configuration. Friction (clicks with no effect, repeated clicks) is detected locally and sent in aggregated form, with no coordinates or content. The video proof module does collect them when you turn it on: see "Video proof module" below.
  • Browser fingerprint (fingerprinting): no canvas, no audio fingerprinting, no collection of installed fonts, no navigator.hardwareConcurrency, no memory
  • Raw screen resolution: only the bucketed viewport width is collected (see below)
  • IP address: never stored (see the dedicated section)
  • Cross-session, cross-tab, cross-domain tracking: each tab is independent, with no correlation between visits
  • New vs. returning visitor: incompatible with our sessionStorage model, not collected

Video proof module

This module is off by default. It is only switched on if you ask for it, site by site. As long as it is not turned on, none of the following applies to your site, and its code is not even downloaded by your visitors' browsers.

Once turned on, it lets Korvus attach to a detected incident a short video of what happened on screen, so your developer sees the defect instead of having to reproduce it. This capability means collecting more, and the lists below spell out exactly what.

What this module adds to collection:

  • Pointer positions and the coordinates of every click. The base snippet never collects these. Without them, the video would show neither the visitor's gesture nor the element they aimed at, and would no longer prove anything.
  • The page structure and its changes during capture, in order to replay the screen. Text is masked at recording time, before it leaves the browser. Input fields are always masked, and no setting can reveal a value entered by a visitor.

What limits this module:

  • Consent required. No capture is recorded if the visitor has not consented, including when the triggering incident is itself exempt from consent. An exempt signal does not make the video it triggers exempt.
  • No continuous recording. A capture is only kept if an incident occurred; it covers the pageview concerned (and the previous one in the case of an error), not the entire journey.
  • A length capped by the code. The delivered video lasts at most 94 seconds, a ceiling applied when the file is produced. There is no minimum length: a proof can be much shorter.
  • Viewable only from the incident's case file, never as a library of sessions to browse.

What this module does on the page, departing from our "zero DOM changes" principle:

  • It adds a <script> tag to the <head> to load its own code from our CDN. It is the only way to do this without requiring unsafe-eval in your Content Security Policy.
  • The recording library creates a hidden iframe to read browser APIs that other scripts on the page have not altered. This iframe is invisible, loads no page, and is removed when recording stops (on WebKit browsers, it stays in place for the whole capture).

These two elements have no visual effect and do not change how your page renders, but they do write to the DOM: the statement "Korvus never changes the DOM" holds for the base snippet, not for a site where video proof is turned on.

CNIL transparency: two classes of data

Korvus is an audience measurement and technical issue detection solution. As such, part of our data is exempt from consent under Article 82 of the French Data Protection Act, according to the criteria set by the CNIL's July 2025 self-assessment tool and the EDPB Guidelines 2/2023 on Article 5(3) of the ePrivacy Directive.

The exempt purposes cover:

  • Measuring technical performance (Core Web Vitals, load times)
  • Detecting navigation problems (JavaScript errors, failed HTTP requests)
  • Optimizing the site's UX

Any data with a marketing purpose (conversion measurement, ad campaign performance, acquisition channels, individual monetary values) requires the visitor's consent.

What still works when visitors refuse cookies

In the "site reactions" category (JS errors, failed HTTP requests, broken resources, error messages, CSP violations, invalid structured data), 6 out of 9 signals are captured with no cookie and no consent. In the "user actions" category, half of the signals are also captured in exempt mode (add-to-cart attempts, add-to-cart confirmations, search structure, visible promo codes).

In practice, for visitors who refuse cookies, Korvus keeps detecting the most critical leaks (checkout errors, broken buttons, out-of-stock products, 404 pages in the funnel), while most tools lose 30 to 40% of their signals.

Visitor identification

Korvus sets no cookie in normal operation.

The session identifier is an anonymous identifier generated locally by the visitor's browser and stored in sessionStorage. In practice:

  • The identifier is created when the tab opens
  • It is destroyed when the tab closes
  • It is not shared between tabs
  • It is not persisted between visits
  • Two sessions from the same visitor cannot be linked

Korvus does no fingerprinting and never cross-references data between sessions.

IP address

The visitor's IP address is used only once, server-side, to determine the country code (e.g. FR, BE, CH) and the preferred language (through the Accept-Language header). It is deleted immediately after this operation:

  • The IP is never stored in a database
  • The IP is never truncated (a truncated IP would still be personal data)
  • The IP is never passed to a third-party service

Data collected without consent

This data is exempt from consent as audience measurement and technical issue detection. It is collected on 100% of your traffic.

Session

Data collected once per visit (one tab = one session):

DataExamplePurpose
Landing page path/produit/air-max-90Identify the entry page, never the full URL, never the URL parameters
Referrer domaingoogle.comIdentify the traffic source (host only, never the full URL)
Entry channelorganic_search / social / email / referral / directClassification derived server-side from the referrer (no additional data captured)
CountryFRSegment problems by geographic area
Preferred languagefr-FRDetect locale-specific bugs (e.g. a checkout broken for fr-BE). Read from the Accept-Language header server-side
Device typemobileDetect problems specific to a device type
Viewport width (bucketed)769 (for 769-1024 px)Identify bugs tied to a responsive breakpoint. 6 buckets only, no exact value
Dark modetrue / falseDetect conversion drops tied to dark mode display (invisible CTAs, unreadable prices)
Operating systemiOS 17Identify incompatibilities (major version only)
BrowserChrome 124Identify incompatibilities (major version only)
Connection type4gPut performance problems in context
Ad blocker detectedtrue / falseTell a truly broken marketing pixel from one blocked by the user. Without this field, we would generate ~30% false positives on desktop
Pixels loaded (list)["meta", "ga4"]List of known pixels that loaded successfully on the page
Pixels blocked (list)["tiktok"]List of known pixels whose resource failed at network level (ad blocker, CSP, CDN unavailable). A technical load measurement, no user tracking
Bot detectedtrue / falseFilter bots out of detections

Pageviews

Data collected on every page change:

DataExamplePurpose
Page path/produit/air-max-90Locate the problem. URL parameters are kept separate (see consent section)
Page typepdp (product page)Segment by journey step
HTTP status200, 404, 500Detect error pages receiving traffic (a massive leak on a paid campaign pointing to a deleted PDP)
Error pagetrue / falseFlag derived from the HTTP status and DOM signatures (title "404", "erreur", "not found")
Navigation typenavigate / reload / back_forward / prerenderDetect users reloading a broken payment page (a very strong bug signal)
Actual visible time12400 msTime the page was actually in the foreground (active tab), not the time it sat open in the background
Maximum scroll depth85 %Detect pages whose main content (buy button, form) can no longer be reached after a deployment
User interactiontrue / false + time_to_first_interaction_msTell a page that was "seen but dead" (broken JS, no click possible) from a page used normally
Product identifier type (PDP)dataLayer / jsonld / dom / slugSource of the detected product identifier (catalog SKU)
Product identifier (PDP)REF-12345SKU or external identifier of the displayed product. Catalog data, zero personal data
Product name (PDP)Air Max 90 — Taille 42Human-readable name shown in alerts and the dashboard. Used for messages such as "Out of stock on Air Max 90 with 42 sessions/h"
Product availability (PDP)true / false / nullStock status observed through JSON-LD, the add-to-cart button or visible text
Time to First Byte320 msDetect server slowdowns
First Contentful Paint1200 msMeasure initial display speed
Largest Contentful Paint2500 msMeasure perceived load speed
LCP element{ tag: "img", selector: "section.hero > img.banner", url, area_px: 870400 }Pinpoint the element slowing down the load and keep its visual area in px² (a single element per page, the one that caused the LCP measurement)
Interaction to Next Paint180 msMeasure responsiveness to interactions
INP element{ selector: "button.add-to-cart", event_type: "click", processing_ms, delay_ms }Identify the slow interaction (a single element per page, the one that caused the INP measurement)
Cumulative Layout Shift0.12Detect visual shifts
Largest CLS shift{ selector: "div.ad-banner", shift_value: 0.18, had_recent_input: false }Identify the element responsible for the largest visual shift (a single element per page)
Long tasksCount + total_blocking_time_msMeasure the time a script blocks the browser (complements network time)
Bfcache restoredtrue / falseDetect when a third-party script breaks the browser cache (a massive degradation of back navigations)
Slow resourcesTop 10 resources taking more than 100 ms to load, as an array { url, type, duration_ms, transfer_kb, blocking }Identify the scripts, images, stylesheets or fonts slowing down the page
Loaded scripts fingerprintSHA hash of the URLsDetect front-end deployments and correlate them with new errors
Error counters on the pagejs_error_count: 2, request_error_count: 1, resource_error_count: 0, ux_error_count: 0Aggregated summary calculated server-side from the detailed error events

The page types detected are: home, plp (product listing), pdp (product page), cart, checkout (generic), checkout_info, checkout_shipping, checkout_payment, order_confirmation, login, account, search, other.

login, account and pro/B2B pages: field-level masking, not page-level blocking

On these identifying pages, Korvus collects the same anonymized technical signals as on the rest of the site (hashed selectors, errors, friction points). This is the market standard (Contentsquare, Datadog RUM, Sentry): the right safeguard is masking at field level, not blocking at page level. The CNIL itself explicitly names technical error detection and the identification of friction points as legitimate purposes.

The safeguards applied on these pages are structural:

  1. No content reading (no-body-read): never the page body text, never an entered value (.value), never the body of an HTTP request or response, never an authentication header.
  2. Hashed, hardened selectors: a clicked element is identified by a technical hash; id identifiers carrying a first or last name (bonjour-jeandupont, jean.dupont) are rejected before anything is sent.
  3. Field-level anonymization: the rare text strings that are sent are stripped of any personal data (email, phone number, IBAN, card number).

Korvus therefore never reads the content, entered values or displayed personal information of these pages: it only captures anonymized structural technical signals from them.

Events: site reactions

Korvus captures 6 types of technical signals emitted by your site, with no consent required:

EventWhat is capturedPurpose
js_errorException name (TypeError, ReferenceError...), message, source file, line and column numbers, stack trace truncated to 500 characters, first-party vs. third-party script flag, time since the last interactionDetect bugs that block the journey. Stack traces never contain user data
request_errorURL host and path (never the URL parameters, stripped in the browser), HTTP method, status code, duration, response content type, third-party origin flag, type (fetch / xhr)Detect broken APIs. Query params are always stripped before sending to prevent any token or email leak
resource_errorTag type (img, script, iframe...), host and path, third-party flag, iframe flag, time before failureDetect missing resources, broken payment iframes (Stripe, Adyen, PayPal), unavailable critical widgets
ux_errorText of the visible error message (truncated to 200 characters), CSS selector of the element, inside-a-form flag, actual visibility flagDetect error messages shown to the visitor ("Une erreur est survenue", "Paiement refusé", "Stock indisponible")
csp_violationViolated directive, host and path of the blocked resource, effective directive, mode (enforce / report)Tell a resource blocked by your Content Security Policy from a broken one. Enables precise diagnostics such as "your CSP is blocking the Meta pixel, add connect.facebook.net to script-src"
structured_data_checkPresence of Schema.org Product, presence of Schema.org Offer, missing fields, errors, product identifier and name (if present in the JSON-LD), availabilityCheck product page SEO compliance and detect inconsistencies between the schema sent to Google Merchant and the price actually displayed

All of this data is pure technical measurement. None of it concerns the individual visitor's behavior.

Events: user actions (exempt mode)

Half of the monetizable user actions are captured without consent, as raw technical facts, with no monetary value. Values (amounts, discounts) are captured separately, with consent.

EventWhat is capturedPurpose
add_to_cart_attemptClick detected on an add-to-cart button, product identifier, success flagDetect broken buttons that no longer respond
add_to_cart_succeededAdd-to-cart confirmation, detected by passive DOM observation (cart counter incremented, success notification, cart drawer opening, URL change, mutation of a structural cart key in localStorage), latency between the click and the confirmationMeasure the real add-to-cart success rate on 100% of traffic. This is the signal that reveals silent cart API bugs
promo_applied (DOM source)Promo code visible on the pageDetect promo code overuse (shared on a forum or a cashback extension). The discount amount is captured separately, with consent
payment_method_selectedRaw signals describing the chosen payment method: displayed label, technical input value, icon path if present, detection cascade typeMeasure the real payment mix and detect a payment method that disappears from the DOM after a PSP integration bug. Normalization into a category (card, paypal, apple_pay, alma...) is done server-side. No reading of bank card fields or opaque PSP iframes
payment_attemptedClick detected on a "Pay" / "Place order" button or equivalents in 15 European languages, text of the clicked button (truncated to 100 characters), detection cascade typeA pure technical fact, "payment button clicked", with no monetary value. Needed to detect clicks that fail silently (the most expensive case at checkout)
promo_code_rejectedCode entered, rejection signal (error message, CSS class, code not applied)Detect a promo code broken in the back office, or a brute-force attempt on codes
shipping_method_selectedRaw signals describing the chosen shipping method (label, input value, cascade type)Detect a broken carrier or a missing option that forces users to abandon
3ds_initiated / 3ds_completed3D Secure context: a challenge iframe appearing, disappearing, navigation to an order confirmation. No reading of the iframe content, no reading of OTP codes, no reading of cardsMeasure 3D Secure challenge success rates by issuer and detect a bank whose flow is broken
variant_selectedDimension chosen on a product page (size, color, format), selected valueDetect a variant that is systematically unavailable after selection (stock / UI sync bug)

A deliberate limit

Some modern payment systems render their fields in a fully opaque cross-origin iframe (Stripe Payment Element, Adyen Drop-in, Mollie Components). In these cases, Korvus only captures the signal "the user interacted with the payment block", with no detail on the exact method. This is a deliberate choice: no technique for inspecting these iframes would be compatible with the GDPR or with our pure-observation principle.

Data collected with consent

This data requires the visitor's consent (cookies accepted). If consent is not granted, it is simply not collected, and Korvus neither estimates it nor falls back on another source.

Session (additional)

DataExamplePurpose
UTM sourcefacebookIdentify the ad channel
UTM mediumcpcIdentify the campaign type
UTM campaignsoldes-ete-2026Identify the specific campaign
Paid traffic detectedtrue / falseSegment problems by traffic type
Meta click identifier (fbclid)Present: yes / noDetect Meta Ads traffic (the value is not stored)
Google click identifier (gclid)Present: yes / noDetect Google Ads traffic (the value is not stored)
TikTok click identifier (ttclid)Present: yes / noDetect TikTok Ads traffic (the value is not stored)
Landing URL parameters{ "utm_source": "google", "color": "red" }All entry URL parameters. Not collected without consent
Full OS versioniOS 17.4.1Fine-grained bug analysis by version
Full browser versionChrome 124.0.6367.91Fine-grained bug analysis by version

Pageviews (additional)

DataExamplePurpose
Page URL parameters{ "variant": "42", "utm_campaign": "soldes" }Fine-grained analysis of pages with parameters. Not collected without consent
Observed product price (PDP)49.90 EURDetect price inconsistencies between pages and catastrophic errors (a product displayed at €0.99 instead of €99). Financial data, consent required

Marketing and dataLayer events

EventWhat is capturedPurpose
tag_firedNormalized tag (meta_pixel, google_ads, google_analytics, tiktok_pixel, pinterest_tag, linkedin_insight, bing_ads, criteo, other), event type (PageView, Purchase, AddToCart...), status (fired / failed / blocked), sanitized pixel identifier (never a session token)Check that each marketing pixel fires correctly and at the right granularity
datalayer_validationEvent name, validity, missing fields, null fields, item count, presence of a valueCheck the integrity of e-commerce dataLayer pushes before they feed your analytics tools
datalayer_unknownName of an unknown dataLayer event with an e-commerce structure (5 per session at most)Onboarding tool to automatically detect custom dataLayers
purchaseTransaction amount, currency. Source: a dedicated GTM tag you install yourself on the confirmation page (see HTML installation). The snippet never reads request bodies or page content. If you also have a GA4 enhanced ecommerce dataLayer push (event: 'purchase'), Korvus observes it too, to cross-check your marketing tracking, but euro figures rely exclusively on the dedicated tag, which is what lets us alert you if your GA4 breaksCalculate the real financial impact of the leaks detected
add_to_cartProduct identifier, name, amount, currencyMeasure the real value of the conversion rate
promo_applied (dataLayer source)Promo code, discount amount, discount typeMeasure the financial impact of promotions

Why the exempt / consent split?

Every monetizable user action is captured in two layers:

  1. The raw fact, in exempt mode: on 100% of traffic, it feeds the structural detection of leaks (failure rate, abandonment, outages)
  2. The monetary value, with consent: on around 70% of consented traffic, it feeds the euro figures

As a result, the most critical leaks are detected across your entire audience, and the financial impact is honestly extrapolated from a statistically representative sample. You never miss a leak signal because a visitor refused cookies.

Your site's reference data

On top of traffic data, Korvus maintains a technical reference of your site server-side, populated automatically:

  • List of expected pixels (expected_pixels): initially built by an automated onboarding crawl (a technical analysis of your home page and a few product pages), then updated continuously. It is used to tell a marketing pixel that was never installed on your site from one that is genuinely broken. This list is inferred from traffic data already collected and triggers no additional collection.
  • Passive product catalog: built from visited PDPs (identifier, name, availability, history of price and stock changes). No sync with your backend is needed, and there is no access to your database.

Retention period

All data is kept for 13 months at most, in line with CNIL recommendations. Deletion is automatic (monthly partitioning, with partitions older than 13 months dropped).

Right to object

Even for consent-exempt data, your visitors can refuse Korvus collection. As the site publisher, you must add a link to your privacy policy that sets the korvus_optout=1 cookie.

When this cookie is present, the Korvus snippet does not initialize at all: no data is collected, no network request is sent, no observer is attached.

Data hosting

All data is hosted in France (Scaleway, Paris region) and is never transferred outside the European Union. Korvus acts as a processor within the meaning of Article 28 of the GDPR. Each customer's data is strictly isolated and is never pooled or reused for our own purposes.

Last revised: August 9, 2026 (description of the video proof module: pointer and click coordinates, capped length, DOM writes). This page reflects version V2 of the Korvus snippet. Transaction amounts are now captured through a dedicated GTM tag you install yourself on the order confirmation page (installation guide). CNIL classification based on the CNIL self-assessment tool (July 2025) and the EDPB Guidelines 2/2023 on the application of Article 5(3) of the ePrivacy Directive.