MousePlayer
Funkcijos Integracijos Naudojimo atvejai Kainos Vadovai
English Deutsch Polski Français Español Italiano Português Nederlands Čeština Svenska Dansk Norsk Suomi Română Magyar Slovenčina Ελληνικά Български Hrvatski Slovenščina Lietuvių Latviešu Eesti Українська Íslenska Gaeilge Malti Русский Türkçe 日本語 简体中文 한국어 Bahasa Indonesia Tiếng Việt ไทย हिन्दी العربية עברית Català Српски Shqip Македонски Bosanski
Prisijungti Išbandyti nemokamai!
MousePlayer
Prisijungti Išbandyti nemokamai!
Ištekliai
Įmonė DPA (Data Processing Agreement) Etika ir duomenų privatumas Pagalba Abuse Prevention & Fraud Fighting Technical and Organizational Measures (TOM) Sub-processors Privacy Policy Imprint Access and Erasure: How to Identify Your Data Terms of Service Right of withdrawal EU AI Act: How MousePlayer Uses Artificial Intelligence EU Data Act: Switching, Portability and Access to Your Data Complaints Contract changelog Funkcijos Naudojimo atvejai Kainos Kiek kainuoja paspaudimas be atsako? Everything your legal team will ask for Integracijos Google BigQuery jungtis GTM ir GA4 įvykių integracijos tiltas Intercom, LiveChat ir Zendesk valdikliai Sentry, Bugsnag ir Datadog RUM integracijos tiltas Zapier, Make.com ir gaunami Webhook PostHog, Mixpanel & Amplitude integracijos tiltas Konversijų API Kliento gyvavimo ciklas (serveryje) Pagalbos tarnyba ir užklausos HubSpot, Pipedrive ir Salesforce Užduočių sekimo sistemos (Jira, Linear, Asana, Trello, GitHub) Google Sheets ir SharePoint MCP serveris (AI agentai) A/B bandymai ir funkcijų vėliavėlės Žymų tvarkytuvės ir CDP El. pašto rinkodara (naršyklėje) Objektų saugykla (S3, R2, GCS, Azure) Apklausos ir kliento balsas Google Analytics 4 (importas) Vadovai Kaip įdiegti? GTM integracija ir CAPI Viešoji API ir API raktai JavaScript API Kaip tai veikia: technologija, raiškos ir ribos MousePlayer ir sutikimas Privacy Policy Text for Your Website

Kaip galime padėti?

Aprašykite problemą arba pageidaujamą funkciją. Atsakome darbo dienomis darbo valandomis (CET).

Apsaugota reCAPTCHA v3.

Spartieji klavišai

Spartieji klavišai neveikia, kai rašote lauke arba atidarytas dialogo langas.

  1. Pradžia
  2. Įmonė
  3. Record of Processing Activities (Art. 30 GDPR)

Record of Processing Activities (Art. 30 GDPR)

MousePlayer — LW IT Solutions Company Lukas Wójcik. Internal accountability document, version 2, 16 October 2026 (version 1 as a draft of 10 August 2026). Every new statement in this version was checked against code, schema and database; figures carry a measurement date.

1. How to read this record

This is an internal accountability document under Art. 30 GDPR. It is not a privacy notice, it is not written to inform data subjects, even though it is published, and it is not written to look good. Its only job is to let a supervisory authority — or the next person who has to answer a question — see what is actually processed, where it sits, and how long it stays.

Three rules governed the drafting. First: a statement is made only where code, schema or a measurement supports it. Where an entry is missing, a placeholder marks the gap rather than a plausible-sounding sentence. An honest gap survives an audit; a well-phrased guess does not. Second: where the code documents its own reasoning, that reasoning is quoted with file and line, so a later reader can verify it instead of trusting this document. Third: retention is stated twice — once for the tables the daily purge covers, once for the tables it does not. A record that claims a deletion period which holds for half the tables is worse than one that names the gap, because the gap is exactly what gets checked.

MEASUREMENT BASIS. All measurements refer to the DEVELOPMENT instance (www.mouseplayer.com, APP_ENV=dev); they do not describe production. Row counts, table sizes and configuration values without a date of their own come from the measurement session of 10 August 2026 and describe that state; newer figures carry their date ("as of 16 Oct 2026"). File and line references without a date likewise reflect the state of 10 August 2026: line numbers move with every change to the code; the files and functions named are authoritative. Production figures: to be completed: the same figures measured on the production instance, with the date of measurement

OMITTED ON PURPOSE. The underlying analysis recorded four full public IP addresses as evidence that the legacy stock is unmasked. They are not reproduced here. A record of processing activities circulates internally and to auditors; it must not itself become a second copy of the data it describes. The finding is stated; the values are not.

2. The entity, and why this record has two halves

FieldEntry
Legal entityLW IT Solutions Company Lukas Wójcik
Addressal. Tadeusza Kościuszki 80/82 lok. 301, 90-437 Lodz, Poland
Tax identificationNIP: PL7252266190
Contactoffice@mouseplayer.com (no telephone number published)
ProductMousePlayer — session replay, heatmaps, behavioural analytics
Representative under Art. 27Not applicable. The entity is established in the Union (Poland).
Data protection officerNone designated. See section 9 for the assessment under Art. 37.

MousePlayer holds two different positions towards two different groups of people, and mixing them produces a record that is worthless for both. For the data of VISITORS to customer websites, MousePlayer is a processor and the customer is the controller — Art. 30(2) applies. For the data of MousePlayer's own CUSTOMERS (accounts, billing, support, portal access logs), MousePlayer is the controller — Art. 30(1) applies.

This is not a drafting convenience. The distinction is already implemented in the code and carries a different technical consequence for each half: class/Server.class.php:416 states "REGEL B ... Hier sind wir AUFTRAGSVERARBEITER", and lines 421-425 separate "Regel A (volle Adresse) fuer DIREKTE Zugriffe auf die eigene Anwendung" from Rule B (truncated) for visitor data. The IP address of a visitor is truncated at class/Server.class.php:432-435; the IP address of a customer user is stored in full. The system therefore already behaves as if this record existed.

  • Part A (section 3) — Art. 30(2): twenty-two processing activities (A.1 to A.22) carried out on behalf of controllers.
  • Part B (section 5) — Art. 30(1): eight processing activities (B.1 to B.8) carried out under MousePlayer's own responsibility.
  • Sections 6 to 8 are common to both halves: retention, automated decision-making, and the general description of technical and organisational measures.
  • Section 10 lists the gaps that are proven to exist. A record is a working instrument, not a certificate.

3. Part A — Art. 30(2): activities carried out on behalf of controllers

A.0 Parties

FieldEntry
ProcessorLW IT Solutions Company Lukas Wójcik, al. Tadeusza Kościuszki 80/82 lok. 301, 90-437 Lodz, Poland. office@mouseplayer.com
ControllersThe customers who operate the websites on which the MousePlayer tag (www/mp.js) is installed. Each controller is identified in the system by projects.channelid; contact details are held in users and workspaces, membership and role in workspace_users. Measured on the development instance: 7 projects, 4 customer accounts, roles 4 owner / 1 admin / 1 viewer.
Authoritative controller listHeld in the system, not duplicated here. The list is generated, not transcribed: in the admin area under “Records of Processing (Art. 30)” a button produces it on demand as CSV — per workspace the owner with name, e-mail, company, address, VAT ID, plan and the domains concerned. Responsible for producing it: Lukas Wójcik, owner; it is drawn at every review of this record, that is annually and on every change to the processing. Every retrieval appears in the change log (export_controllers). A list transcribed here would be a second place for the same truth and would go stale with every new customer without anyone noticing. (decision of the controller, 17 Aug 2026)
Processor's representativeNot applicable (established in Poland).
Data protection officerNone designated — see section 9.
Technical and organisational measuresSection 8 applies to every activity in Part A and is not repeated per activity.

A.1 Session replay — capture of behavioural events

FieldEntry
Origin of the eventsTwo paths. (1) The visitor's browser via www/mp.js, where the mpEinw state machine enforces consent technically. mp.js transmits over HTTPS to nginx, where TLS terminates: normally over the WebSocket at /mp_wss/; if the WebSocket never connects, since 11 Oct 2026 through an HTTP tunnel at /mp_wst, to which the same checks of the WebSocket server apply (consent, bot detection, origin, quota); large page snapshots since 7 Oct 2026 as an HTTP upload at /mp_dom with signed, time-limited access (held in Redis for up to 120 seconds, then processed like an ordinary snapshot). The measuring point /mp_wsfail only counts failed WebSocket connections per project, reason, browser family and device type, with no visitor ID; the IP address is only hashed there and kept for at most one hour for de-duplication. (2) Server-side via www/api/capi_ingest.php from a system operated by the customer; this path requires a signature (HMAC-SHA256 over timestamp and body, five-minute window) and an explicit statement per transmission that consent exists — without it the request is rejected with 403. MousePlayer cannot verify consent on that path by technical means; the customer as controller warrants it (DPA section 2). The path is switchable via site_settings.capi_ingest_aktiv; it was disabled until 18 August 2026 and has been enabled since. In addition there is a preview of a native app SDK (Android) with its own intake path (www/api/native_ingest.php, tables mp_native_apps and mp_native_sessions); it exists only on the development instance and is switched off (native_preview_enabled = 0; as of 16 Oct 2026 no stored session).
PurposeRecording the behaviour of visitors on customer websites — mouse movement, clicks, scrolling, taps, tab changes — so that the customer can replay and analyse the session.
Categories of data subjectsVisitors to the websites of MousePlayer customers.
Categories of personal dataMovement coordinates, time offset, scroll position, document height; event type; viewport and screen dimensions; session and pageview identifiers. Measured event distribution: move 100934, mutation 66153, scroll 28214, tab_focus 6185, resize 2590, screen_info 2034, pageview 2031, click 1863, input 599, input_mode 425, k 407, tap 401. In visitor_sessions additionally: referrer, UTM parameters, an ad network's click ID with its type, landing path, traffic channel, in-app browser flag, browser identifier (user agent), bot flag with reason, session quality and, since 26 Sep 2026, a friction score with reasons (friction_score, friction_gruende). The event distribution above was measured on 10 Aug 2026.
Where storedmouse_events (event_id, pageview_id, event_type, time_offset_ms, x, y, scroll_y, scroll_x, doc_height, target_selector) — 225892 rows, 322.6 MB, the largest table in the schema. session_pageviews (pageview_id, session_id, tab_id, url, viewport_width, viewport_height, start_time, start_time_ms, end_time, page_title). visitor_sessions (session_id, channelid, screen_width, screen_height, start_time, end_time, duration_seconds, is_active, device_type). mouse_events_analytik (id, erfasst_am, pageview_id, channelid, event_type, time_offset_ms, x, y, target_selector) — 11812 rows: a slim copy of the CLICK and context events, from which the click layer of the heat map reads. It is named separately because its retention differs from that of mouse_events — those move to the archive after 7 days, the copy stays and therefore carries the click map across the whole history. From it a daily aggregate is built every night, mp_hm_klick_tag with mp_hm_ansicht_tag (since 7 Oct 2026): per day, address, device type, screen class, target and 8 px cell only a count, with no session, visitor ID or page view. The click map uses it for days whose sessions no longer exist. Deleting individual sessions or persons does not touch it, because no row can be attributed to a person. Since October 2026 additionally mp_aufnahme_quittung: per incoming message an identifier and a status; for a message that cannot be processed, also its raw content and the reason. The rows are deleted after 7 days (srv/db_worker.php). Row counts in this entry without a date: as of 10 Aug 2026.
Legal basis (determined by the controller)Consent. Reasoned in ai/migrations/018_consent.sql: recording is behavioural observation (Recital 24 GDPR) and access to terminal equipment (Par. 25 TDDDG); it requires consent, and legitimate interest does not carry it. All 7 projects are set to projects.consent_mode='required'. Server-side enforcement at class/Server.class.php:977-1004; kill switch site_settings.consent_gate_active=1.
Categories of recipients(a) The respective customer — workspace members with role owner, admin or viewer. (b) Any holder of a share link, WITHOUT login: /s/<40-hex-token> maps to player.php?share=..., table session_shares. (c) Optionally the customer through REST API v1 with their own API key, only on a paid tier; as of 16 Oct 2026 no API key exists. (d) Optionally the customer's AI agents through the MCP server (/mcp): read access with a read key of the REST API, to be enabled separately per project (projects.mcp_active, off by default); available are projects, key figures, session lists with replay link, individual sessions with page views and events, pages, frustration signals and goals — not the page snapshot. As of 16 Oct 2026 enabled for no project. (e) The customer as a file: CSV export of the session list and of survey answers from their interface, capped (csv_export_max_zeilen); every export is recorded in the change log (csv_export).
Third-country transferNone in the recording path. No outbound call was found; geo resolution runs against local MaxMind files. See section 4.
RetentionTier-dependent, enforced by the daily purge in srv/db_worker.php (section 6.1) through the deletion chain mp_sitzungen_loeschen(): free 7 days, small 30, business 90, enterprise according to the agreed tier (default 365). Sessions recognised as automated (bot) have their own, shorter periods: their recording (page snapshot and events) is cleared once retention_bot_recording_days has passed (period in days: 1), and the session row with everything attached to it is deleted once retention_bot_session_days has passed (period in days: 14); both are configurable in the admin area, and 0 switches the separate period off, so that the tier period applies. After 7 days events move from mouse_events into compressed archive files per session (srv/cron_archiv.php); they share the period of their session, are deleted with it, and since 6 Oct 2026 form part of the daily backup (section 4). If a customer deletes a project, a workspace or their account, or a deletion by IP address or visitor is triggered, since 7 Oct 2026 the data is archived first — hidden, no further recording, restorable — and finally deleted after at most 30 days (section 6.2, mp_loesch_archiv).

A.2 Capture of page content — DOM snapshot and DOM mutations

FieldEntry
PurposeRecording the page content so the session can be replayed as the visitor saw it.
Categories of data subjectsVisitors to customer websites; and indirectly every person whose data the customer's own page renders — for example an order confirmation showing a postal address.
Categories of personal dataThe complete HTML of the visited page including text, image URLs and metadata, as rendered by the browser, without scripts and with the masking described in A.3. In addition, serialised HTML fragments of every later DOM change, stored in the target_selector field of mutation events. Since 30 Sep 2026 additionally: the content of open shadow roots of web components together with their stylesheets (closed shadow roots are not recorded); canvas elements as a still image in the snapshot and, when they change, continuously as an image (at most once per one-second tick, capped per page view; not in masked areas and not for canvas elements containing cross-origin images); same-origin iframes with their content and, since 2 Oct 2026, the interaction within them (pointer, clicks, input, scrolling); cross-origin iframes only as a placeholder without content.
Where storedsession_pageviews.payload_blob (longtext), stored compressed since 14 Aug 2026 (prefix MPZ1:, gzip, Base64); as of 16 Oct 2026 all populated rows use this format. session_pageviews.has_recording is an ordinary column, not a generated one: the worker sets it when writing the snapshot and resets it when clearing it; likewise hat_interaktion, hat_scroll and scroll_max_y. mouse_events.target_selector where event_type='mutation', including the content of shadow roots, canvas and iframes; after 7 days in the archive files (A.1). session_pageviews.html_path exists but, as of 16 Oct 2026, is set on no row.
Legal basis (determined by the controller)Consent, as A.1.
Categories of recipientsThe customer, through www/player.php, www/ajax_get_events.php and www/player_theatre.php. Any holder of a share link. NOT through the REST API — www/api/v1_session_detail.php:25 records the deliberate exclusion of the replay payload.
Third-country transferNone in the recording path.
RetentionAs A.1: with the session through the daily purge and mp_sitzungen_loeschen(); for sessions recognised as bots the snapshot is cleared after the period from A.1 (retention_bot_recording_days, period in days: 1). See section 6.
Note on scope of maskingMasking applies only INSIDE regions marked data-mp-mask or .mp-masked (www/mp.js:3934-3970). All other page HTML is stored unchanged. Whatever the customer's page itself renders is therefore inside the recording, and no masking setting reaches it.

A.2 is the activity with the widest and least predictable data range in the whole product. The masking controls what the VISITOR types; they do not control what the customer's PAGE displays. If a customer's site renders health, financial or other special-category information on screen, that content enters the snapshot. Handling: Permitted but not recommended. Responsibility lies with the Customer as controller; MousePlayer provides the recording and does not judge what the page displays. Logged-in areas are expressly advised against where health, banking or similar data appear on screen: no field masking helps there, because it acts on input fields and not on what the page outputs. (decision of the controller, 17 Aug 2026) To be carried as an onboarding rule and as a note in the installation guide.

A.3 Capture of form input

FieldEntry
PurposeRecording form input, character by character, so that the interaction with a form can be replayed.
Categories of data subjectsVisitors to customer websites.
Categories of personal dataInput values of input, textarea and select elements — fully masked, partly masked or in clear text depending on the masking level. The state of checkboxes and radio buttons (true/false), which is NEVER masked (www/mp.js:4810-4811). The field category derived by classifyFieldName() (www/mp.js:4657-4674): email, password, phone, payment, address, name, search, coupon, quantity, other.
Where storedmouse_events.target_selector where event_type='input', format {"t":"<data-mp-id>","v":"<value>"} — 599 rows.
Legal basis (determined by the controller)Consent, as A.1.
Categories of recipientsThe customer (player); any holder of a share link.
Third-country transferNone in the recording path.
RetentionNo period of its own; as A.1, with the session (tier period, for bots the periods from A.1), after 7 days in the archive files.
Safeguards in forceHard floor for passwords and payment data regardless of level (www/mp.js:4831-4837). Full masking at level 0 (www/mp.js:4818-4819). All 7 projects are set to projects.smart_masking=0, which is the STRICTEST setting despite the label.
Legacy findingOn 10 Aug 2026, 173 rows in mouse_events contained UNMASKED clear-text input from 2026-07-21 to 2026-07-27, including personal names; rows from that day on are masked. As of 16 Oct 2026 there is no unmasked input left in mouse_events (measured with the same query). The affected rows were deleted on 17 Aug 2026 (impact assessment, R2), i.e. before the archive files were introduced on 18 Aug 2026; they therefore cannot be in the archive files, nor in the backup that has included them since 6 Oct 2026. The encrypted copies of that time on Google Drive were deleted on 6 Sep 2026; no remaining backup from before 17 Aug 2026 is known, but its absence has not been verified in detail.

A.4 Recognition of the visitor across sessions

FieldEntry
PurposeRecognising a visitor across sessions and pageviews.
Categories of data subjectsVisitors to customer websites.
Categories of personal dataPseudonymous visitor identifier, generated and held in the visitor's localStorage under 'mp_visitor_id' (www/mp.js:5056-5059). A clear identifier set by the customer — login ID or customer number — through MousePlayer.setUserId() (www/mp.js:4909-4913, localStorage 'mp_user_id'), and automatically from the dataLayer path 'uid' (www/mp.js:3585-3637). Nickname and browser name per visitor, held ACROSS projects (UNIQUE on visitor_id).
Where storedvisitor_sessions.visitor_id (varchar 64), custom_user_id (varchar 128, own index idx_custom_user_id), nickname_key. visitor_nicknames (visitor_id, nickname_key, browser_name, city, created_at) — 329 rows; the city column is empty in all 329. consent_events.visitor_id.
Legal basis (determined by the controller)Consent, as A.1.
Categories of recipientsThe customer. Through custom_user_id, merging with the customer's own customer records is an explicitly intended use.
Third-country transferNone in the recording path.
RetentionWith the visitor's last session. mp_sitzungen_loeschen() (srv/db_worker.php) checks after every deletion whether any session is left for that visitor and removes the nickname otherwise; the deletion path in the player does the same. The daily tier purge calls the same function, so an orphan can no longer arise. There is no separate period and none is needed: the entry never outlives the sessions it names.

A.5 Network data and coarse location

FieldEntry
PurposeDetermining network and coarse location of the visitor, and determining the region for the consent check.
Categories of data subjectsVisitors to customer websites.
Categories of personal dataIP address (truncated in the recording path, see below), two-character country code, city (determined from the truncated address, not from the full one), ASN number and provider name, region code.
Where storedvisitor_sessions.ip_address (varchar 45), country, city, asn_number, asn_org, region_code. ip_lifetime_stats (ip_address, first_seen, total_conns, total_msgs, total_traffic, total_uptime_sec) — 446 rows.
Legal basis (determined by the controller)Consent for the recording. The country determination is deliberately performed server-side so that no third party is contacted before consent exists (class/Server.class.php:941-944).
Categories of recipientsNo external recipient. Geo resolution runs against LOCAL MaxMind files under cfg/maxmind/ (GeoLite2-City and GeoLite2-ASN; read in class/IP2CityLite.class.php and class/Server.class.php), which srv/geoip_update.sh regularly downloads from MaxMind; no visitor data is transmitted in the process. The resolved values are stored in visitor_sessions and are visible to the customer like the rest of the session record.
Third-country transferNone. No outbound call in the recording path.
TruncationThe WebSocket server (class/Server.class.php, rule B) replaces the last IPv4 octet with 'xxx' and the last IPv6 group with ':xxxx', immediately after determining the address of the connection (behind nginx from X-Forwarded-For, trusted only from the local peer), and therefore before any consumer. The full address exists only transiently in the WebSocket server's memory, in Redis counters that expire after a few seconds and — for addresses that trigger an automatic block — for the duration of the block (24 h) in Redis (mp_autoban:); the Redis instance writes its data to disk for persistence (AOF). As of 16 Oct 2026 no row in visitor_sessions or ip_lifetime_stats holds an untruncated address.
Retention and legacy finding365 days from first_seen for ip_lifetime_stats, enforced by the daily run in srv/db_worker.php; the value lives in site_settings under retention_ip_stats_days and is configurable in the admin area. visitor_sessions falls under the tier purge. The legacy finding is closed: measured on 17 Aug 2026, 267 of 267 addresses in ip_lifetime_stats were truncated and 881 of 901 in visitor_sessions. The remaining 20 carry 0.0.0.0 and come from the form-analysis test data (s_mptest_form_) — they are not visitor addresses.

A.6 Consent records

FieldEntry
PurposeDemonstrating consent and every change to it (Art. 7(1) GDPR).
Categories of data subjectsVisitors to customer websites.
Categories of personal dataState (GRANTED, GRANTED_LATE, withdrawn, region_exempt), source, timestamp; CMP name and version; SHA-256 of the TC string — deliberately not the string itself; GPC signal; region code; version of the project configuration.
Where storedconsent_events (id, session_id, channelid, visitor_id, consent_state, consent_source, cmp_name, cmp_version, tcf_hash, gpc_signal, region_code, consent_config_version, occurred_at, created_at) — 120 rows. visitor_sessions.consent_state, consent_source, consent_ts, cmp_name, cmp_version, tcf_hash, gpc_signal, region_code, consent_config_version, recording_offset_ms.
Legal basisDemonstration obligation, reasoned in ai/migrations/018_consent.sql: the burden of proving valid consent lies with the controller, and without a log there is nothing to show in a dispute.
Categories of recipientsThe customer.
Third-country transferNone.
Retention730 days, counted from the withdrawal. Both the period and the anchor live in site_settings (consent_evidence_days, consent_evidence_anchor) and are configurable in the admin area; enforced by the daily run in srv/db_worker.php. On session deletion the evidence is still not deleted but depersonalised: that a decision was made must remain provable (Art. 5(2)), who made it need not. Only records no session still refers to are deleted — while the recording exists its evidence must exist, or the recording would stand there without permission.
Data quality671 of 706 sessions carry consent_source='testdata' with consent_ts IS NULL, set by ai/devdata/001_altsitzungen_markieren.sql. These are NOT genuine consent evidence. Genuine records: 27 (api), 5 (gcm), 2 GRANTED_LATE (api), 1 withdrawn (gcm).
Deletion on withdrawalOptional and switched off everywhere: projects.consent_delete_on_withdraw=0 for all 7 projects, evaluated at srv/db_worker.php:1067-1070. A withdrawal therefore stops further recording but deletes nothing.

A.7 Custom events, frustration signals and JavaScript errors

FieldEntry
PurposeRecording customer-defined events, frustration signals and JavaScript errors occurring on the customer's page.
Categories of data subjectsVisitors to customer websites.
Categories of personal dataEvent name — measured: dead_click 355, gtm.scrollDepth 290, rage_click 52, text_copied 26, text_selected 26, error_detected 22, consent_status 19, consented_ready 17, consented_update 8, mouse_thrashing 5, add_to_cart 3. Parameter values — target 407, tag 386, desc 274, count 57, length 52, snippet 22, message 22, type 19, filename 9, lineno 9, masked_area 4, price 1, currency 1, item_id 1. Two of these carry more than their name suggests: 'snippet' on text_copied and text_selected is the page text the visitor selected or copied; 'filename' holds complete URLs and was found to contain a moz-extension:// address, which is a device-specific browser extension identifier.
Where storedsession_custom_events (event_id, session_id, pageview_id, event_name, created_at) — 823 rows. session_custom_event_params (param_id, event_id, param_key, param_string_value, param_int_value, param_float_value) — 1264 rows.
Legal basis (determined by the controller)Consent, as A.1.
Categories of recipientsThe customer. Optionally by webhook to a URL freely chosen by the customer — see section 4.
Third-country transferThrough the webhook the destination is undetermined and therefore potentially a third country. Currently inactive: projects.webhook_active=0 and webhook_url NULL for all 7 projects.
RetentionWith the session through the daily purge and mp_sitzungen_loeschen() (tier period, for bots the periods from A.1); session_custom_event_params hangs off session_custom_events by FOREIGN KEY ON DELETE CASCADE. Console, network and Web Vitals events in mouse_events as A.1; mp_cwv is deleted with its page view (cleaned up hourly).
Console messages and network requests (since 30.09.2026)What the page writes to the browser console (the level is set by the operator, default: errors and warnings) and requests made by the page that fail or take longer than a configured threshold: method, address, status code and duration. Never headers or the body of a request or response, never the content of logged objects, only their kind. Texts pass a filter before they are sent (e-mail addresses, IP addresses, long digit sequences, access tokens); addresses lose the values of parameters with sensitive names. Requests to MousePlayer itself are not captured. Stored like a mouse event in mouse_events (event_type con or net), with the same retention and deletion chain as the recording (A.1); shown in the player next to the timeline.
Loading and response times (Web Vitals, since 02.10.2026)How fast the page loads and reacts for the visitor, measured in the browser per page view: Largest Contentful Paint, Interaction to Next Paint, Cumulative Layout Shift, First Contentful Paint and Time to First Byte. In addition a short description of the largest visible element and of the element of the slowest interaction, as for a click (kind, identifier, visible text; in masked areas only the kind). No inputs, no request contents. Stored like a mouse event in mouse_events (event_type cwv) and per page view in mp_cwv, with the same retention and deletion chain as the recording (A.1); shown in the Web Vitals tab and in the player header.

A.8 Derived features and machine-generated session timelines

FieldEntry
PurposeDeriving per-session features and machine-generated text protocols — scoring of conspicuousness and attention.
Categories of data subjectsVisitors to customer websites.
Categories of personal dataFeature vector: duration, page count, scroll depth / median / reversals, pauses, path efficiency, peak speed, rage and dead clicks, taps, zoom, source and campaign, dwell time per element role (role_dwell_json), tab behaviour. Assessments: engagement_score (0-255), session_quality, is_bot. A running-text timeline of the session — sample: 't=0.0 landing / src=(direct) kanal=direct attr=direct mode=unknown motion=unavailable viewport=1699x935 / t=2.0 session ende'.
Where storedai_session_features — 4043 rows (one row per session AND builder_version, so a session appears once per version). ai_timelines (timeline_id, session_id, channelid, builder_version, input_mode, line_count, char_count, pageviews, duration_s, text as mediumtext, built_at) — 3203 rows, 5.5 MB. mp_session_ziel (channelid, session_id, goal_id, tag, ziel_fp, gebaut_am) — 628 rows: which session reached which goal, computed once instead of on every request. ziel_fp is the fingerprint of the goal rule; where the controller changes it the mapping is rebuilt rather than quietly reused. mp_hm_tag (channelid, tag, url_md5, url, titel, ansichten, engagiert) and mp_hm_tag_stand (channelid, tag, berechnet_am), since 24 Sep 2026 (migration 148): for each project, day and page address the number of page views and of engaged page views, from which the list of page heatmaps is served. Counts and addresses only — no visitor or session identifier; the number of visitors per address is still counted from the sessions themselves.
Legal basisNot documented in the code. Derived from the recording and therefore covered indirectly by the consent obtained for it. Instruction: it arises with the Customer's registration and is carried out as soon as they install the script on their site and visitors are recorded (statement of the controller, 17 Aug 2026). Deriving features, timelines and value tiers is part of the commissioned service. Two parts are distinguished. The value tiers are set by the Customer on their own project; every change is logged (log_audit, save_wertregeln). The summaries written by the language model ran until 17 Aug 2026 solely off a global setting in the admin area — the Customer had no influence over it, so they did not rest on their instruction. Since migration 041 projects.ki_texte_aktiv decides in addition, switched on by default; the Customer can turn it off at any time in the settings tab, and every change appears as ki_texte_changed in the change log. The switch is not the instruction but the means of limiting it: switched on by default means the Customer did not switch it on, only failed to switch it off.
Categories of recipientsThe customer (cfg/project_ai_overview.php).
Third-country transferNone. The generated sentences are phrased by the language model on our own hardware in Łódź (section 4); nothing goes to an external AI service.
RetentionWith the session. ai_session_features and ai_timelines have been part of mp_sitzungen_loeschen() (srv/db_worker.php) since 12 Aug 2026 and are therefore removed both on explicit deletion and by the daily tier purge. This cell previously said the opposite; that was the state before that change. The per-visitor, per-day scoring derived from them (ai_visitor_value) and the generated texts (ai_insights) carry a period of their own in addition — see section 6.2.

A.9 Collaboration on the replay — comments and share links

FieldEntry
PurposeCommenting on a replay at a time marker, and sharing a replay by link.
Categories of data subjectsCustomer users (the authors) and, indirectly, the recorded visitors.
Categories of personal dataComment text (up to 2000 characters), time offset, author. Share token (40 hex characters), creator, expiry, revocation, access count, last access.
Where storedsession_comments (comment_id, channelid, session_id, user_id, time_offset_ms, body, created_at, updated_at) — 12 rows. session_shares (share_id, channelid, session_id, token, created_by, expires_at, revoked_at, last_seen, view_count, created_at) — 9 rows.
Legal basisNot documented in the code. Instruction: the Customer expressly permits sharing, including outside its own organisation, and bears responsibility for it (decision of the controller, 17 Aug 2026). The instruction is documented by the act itself: the link is created by an authorised member of the Customer workspace, and its creation is logged (log_audit, action create_share; revocation as revoke_share). MousePlayer provides the function; the choice of recipients and the lawfulness of the disclosure rest with the Customer. Technically bounded: every link expires — 7 days by default, 90 at most — and can be revoked at any time.
Categories of recipientsAny holder of a share link, WITHOUT login (www/.htaccess:77). Invented, expired and revoked tokens all return the same response, so tokens cannot be probed for information.
Third-country transferUndetermined by design — a link can be forwarded anywhere. This is a property of the feature, not an incident.
RetentionWith the session: session_comments and session_shares have been part of mp_sitzungen_loeschen() since 12 Aug 2026 and are deleted with the session. Independently of that, share links carry a maximum lifetime of their own: session_shares.expires_at is NOT NULL, mp_share_erstellen() (cfg/lib.php) defaults to 7 days and caps at 90. The application cannot create an unlimited link. Once a link has expired or been revoked, the daily purge deletes its row 90 days later; the accesses in share_views go with it via the foreign key (B.6).

A.10 Live view — watching a session as it happens

FieldEntry
PurposeObservation of a running session in real time: live list of visitors present, follow mode with a mirrored page snapshot, transmitted pointer position and, since 16 Aug 2026, a reconstructed hover state (:hover).
Categories of data subjectsVisitors to Customer websites (observed), Customer users (observing).
Categories of personal dataThe same categories as A.1 and A.2 — pointer and scroll positions, clicks, input events, the page snapshot and its mutations — but at the moment they arise. Plus the session identifier, so the observer follows the right session.
Where storedNot stored. The stream runs through the WebSocket server (class/Server.class.php) and is passed on in-process to the connected observers. The access token is held in the database table live_tokens (identifier, project, user ID, where applicable the followed session, time of the last confirmation). Since 17 Sep 2026 the player of a running session also attaches to this stream with its own access (identifier with the prefix pl_, bound to the followed session) in order to display its events without delay. The recording of the same session is created independently via A.1 and A.2.
Legal basisInstruction: the observation is initiated by an authorised member of the Customer workspace; access runs through a time-limited token in live_tokens bound to their user id. The end user is not separately notified at the moment of observation (decision of the controller, 17 Aug 2026). Notification takes place in advance: through the consent that precedes any recording, and through the privacy policy, which since 17 Aug 2026 expressly states that a running session can be followed in real time. Until then it named the live view only as a feature in a list — so the decision rested on a sentence that did not yet exist.
Categories of recipientsThe Customer, limited to authorised members of the workspace.
Third-country transferNone.
RetentionThe stream itself is not stored. The WebSocket server removes a row in live_tokens after the view ends, with a short grace period for a reload; an access that is no longer confirmed loses its effect after a short time and is cleared later.

This entry was absent from the record until 17 Aug 2026. It is to be kept separate from A.1 and A.2 even though the same data flows: there it is replayed later, here a human watches at the moment the visitor acts. That simultaneity is exactly the feature Art. 35(3) fastens on in defining systematic monitoring, and the DPIA rests its own necessity on it.

A.11 Integration delivery log

FieldEntry
PurposeEvidence and troubleshooting for the transmissions listed under 4.1. Without it the controller cannot tell whether a bridge is working — a failure would be silent from their side.
Categories of data subjectsVisitors to customer websites, in so far as the controller has set a customer ID for them. Without an ID set, the entry contains no personal reference.
Categories of personal dataTimestamp, type of bridge, an identifier (the customer ID the controller supplied via setUserId, the advertising network, or the event name), success or failure, duration in milliseconds, attempt number, and the recipient's response or error text. That text may contain a record ID from the third-party system. No behavioural data, no page content, no IP address.
Where storedRedis, key mp_webhook_log:<channelid>. The last 25 entries per project; older ones drop out via lTrim. Not in the database and therefore not in the daily backup.
Legal basis (determined by the controller)As A.1. The entry only comes into being if the controller has switched a bridge on — it is the record of their own instruction being carried out.
Categories of recipientsThe controller, via the project settings (Integrations tab). No onward disclosure.
Third-country transferNone. The entry does not leave our own server.
Retention30 days. Set as a Redis expiry when written; additionally capped at 25 entries per project. It expires by itself and needs no erasure run. The figure lives in site_settings.retention_intlog_days and is adjustable in the admin area.

A.12 Behaviour profiles — automated grouping of visits

FieldEntry
PurposeAutomated grouping of recorded VISITS by their course, so that the controller does not have to work through one session after another but sees groups — and can replay one typical visit per group. Alongside this, recurring sequences of pages and events within the computation window.
Categories of data subjectsVisitors to the customer websites. What is grouped are VISITS, not people: the same person with two visits can end up in two groups, and the interface is worded that way. A profile per PERSON is not computed — it would require the customer identifier supplied through setUserId, and 0 of 4226 sessions carry one (measured 27.08.2026).
Categories of personal dataNo new categories. The feature vector from A.8, narrowed to the seven characteristics with a fill rate above 90 %: duration, page count, attention score, pauses, rage clicks, dead clicks, tab switches. Derived from these: which group a session belongs to, and its distance from the group centre. Per group additionally the session ID of the most typical and of the least typical visit, so that both can be replayed. For the sequences: the pattern chain of paths and event names, and up to three session IDs as examples. No page content, no IP address, no form input.
Where storedmp_profile — one row per channel and group (size, share, centre, key figures, computed name, self-assigned name, representative, outlier); 15 rows. mp_profil_sitzung — session to group plus distance; 4226 rows. mp_sequenzmuster — chain, support, lift, up to three examples; 115 rows. All three are DERIVATIONS: they may be lost and recomputed at any time and hold no raw datum that does not also exist elsewhere. Since 6 Sep 2026 mp_profil_sitzung additionally holds the person key (person_key, migration 112): a copy of the key from A.4 (customer ID, otherwise visitor ID), which the profile run updates daily; the list of returning visitors is calculated from it. Row counts in this entry: as of 27 Aug 2026.
Legal basisAs A.8. Derived from the recording and therefore carried indirectly by the consent obtained for it. Instruction: part of the commissioned service; it arises with the customer's registration and is carried out as soon as visitors are recorded. Two limits are worth naming. The grouping is COMPUTED, not generated: which group a visit belongs to is decided by k-means over the seven characteristics in ai/profile.py, and the group names come from the relative position of the centre, not from a text. Where a language model does write a sentence about a group, it is handed the GROUP figures only — percentages and averages — never a session or visitor ID, and the same guard as in A.8 rejects any number that is not among those figures. There is, however, no per-project switch: profile_aktiv is a global operator setting, and the per-project selection of AI sections (projects.ki_module) does not cover this tab. Recorded as a gap in section 10.
Categories of recipientsThe customer, through the Behaviour Profiles tab (cfg/project_profiles.php). No transfer to third parties: group membership enters none of the bridges under 4.1. Checked by searching for profil_key across cfg/, srv/, www/ and class/ — it occurs only on the reading path and in the player, in neither www/mp.js nor class/Server.class.php, so it never reaches the visitor's browser either.
Third-country transfersNone. The computation runs on the same machine (ai/profile.py, started by srv/ai_worker.php). The optional sentence per group is phrased by the language model on one of our own machines with a graphics card in the same local network (see section 4), which fetches only the group figures, over HTTPS. Nothing goes to an external AI service.
RetentionWith the session: mp_profil_sitzung is part of mp_sitzungen_loeschen(). Beyond that 180 days, anchored on the START OF THE SESSION and not on the time of computation — the daily run refreshes that on every pass, so a period hung on it would never expire and nobody would notice. Rows whose session no longer exists are removed without any period at all, because every period scales over a row that is gone. The figure sits in site_settings.retention_profile_days, is adjustable in the administration area, and is raised to at least the computation window so that the deletion run does not remove what the profile run wrote the same day. mp_profile falls as soon as no assignment is left for it, in the same nightly run — which is why that statement stands after the two above. mp_sequenzmuster falls after 180 days without a recomputation, and at once when its channel has no session left at all. There the anchor IS the time of computation, and correctly so: unlike an assignment, a pattern describes the CURRENT window and is rewritten in full per channel on every run, so a fresh timestamp really does mean „describes today's data“.

A.13 Custom name for a visitor — assigned by the controller

FieldEntry
PurposeThe controller gives an individual visitor a name of their own in order to recognise them in lists and recordings. The name replaces the generated nickname from A.4 everywhere it would otherwise appear. The occasion is the support case: „this is the customer who called“.
Categories of data subjectsVisitors to the customer websites — specifically, individual ones picked by the controller. Unlike A.1 to A.12, this processing does not arise on its own when somebody opens the page; it requires a deliberate entry. Without that entry there is no row: on 28.08.2026, 0 of 3830 visitors carry a name of their own.
Categories of personal dataFREE TEXT of at most 64 characters, plus the time and the user account of the assignment. The reference is the visitor identifier from A.4. The content is neither prescribed nor checked — it may be a contract number, a nickname, or a clear name together with a department. This sets the processing apart from every other one in Part A: there the data come from the visitor or are computed from their behaviour, here the controller types them in. Noted as a gap in section 10.
Where storedvisitor_nicknames.custom_label, label_set_at, label_set_by (migration 093) — the same row that already carries the generated nickname, one per visitor, 3830 rows. Deliberately not a table of its own: the name replaces exactly the nickname stored there, hangs on the same key and therefore falls with it without further action. A separate table would be a fifth place in the deletion chain, and one that can be forgotten.
Legal basisInstruction of the controller (Art. 28(3)(a)), given by the entry itself — no job and no default setting creates a name. Whether there is a legal basis for the content entered, MousePlayer cannot judge: the controller chooses the text and knows the context it is taken from. The hint in the input field therefore states the purpose („only your team sees this name“) and not merely the field.
Categories of recipientsThe customer, in their own lists and in the player. No disclosure to third parties: the field enters none of the bridges from 4.1 — verified by a search for custom_label across cfg/, srv/, www/ and class/; it appears neither in www/mp.js nor in class/Server.class.php and therefore never reaches the visitor's browser either. The share link (B.6) deliberately does NOT show it, but the generated nickname instead: passing on a link that needs no login shares a recording, not the identity behind it. The same consideration as in www/selbstauskunft.php.
Third-country transfersNone. The field leaves the database only into the interface of the logged-in customer.
RetentionWith the visitor's last session, like the row it sits in: mp_sitzungen_loeschen() removes that row as soon as no session exists for this visitor identifier any more — on both paths, the daily deletion run and the account deletion. No calendar period of its own, and that is intentional: a period on the name would mean deleting it while the visitor is still there and still being recorded. The controller can remove it at any time; an empty field deletes it, and the generated nickname applies again.

A.14 Import from Google Analytics 4 — figures and sessions from the controller's own account

FieldEntry
PurposeThe controller has data fetched into MousePlayer from their own Google Analytics 4 account. Two paths, each switched on separately: the Data API returns daily totals only and answers the coverage question — GA4 counts 3,000 sessions for a day, MousePlayer recorded 40, and without that reference figure the difference can neither be explained nor even noticed. The BigQuery import reads the property's raw export and stores individual GA4 sessions with their events, so that it becomes visible what happened during a visit MousePlayer could not record.
Categories of data subjectsVisitors to the customer websites — the same people as in A.1, only observed by a second measurement system. What sets this apart from A.1 to A.13 is the origin: this data was not collected here. It already sits in the controller's Google account and MousePlayer fetches it. The Data API path moreover concerns no individual person at all; it returns daily totals only.
Categories of personal dataFor the Data API path: none — four numbers per day (sessions, users, page views, engaged sessions) with no personal reference whatsoever. For the BigQuery path, per GA4 session: the pseudonymous identifier user_pseudo_id, that is the client ID from the _ga cookie; start, end and duration; the number of events and page views; the landing path; device category and country; plus source, medium and campaign. In addition, per event name, a counter, the last timestamp and, where GA4 reports one, a summed value. No IP address, no page snapshot, no mouse movement — GA4 holds none of these and none are generated. An imported session can therefore never be replayed.
Where storedga4_daily, ga4_sessions and ga4_events (migration 097) — three SEPARATE tables. Deliberately not in visitor_sessions, session_pageviews or session_custom_events. This is not tidiness: those three tables carry no column stating where a row came from, because everything in them was measured here. An imported row would be indistinguishable from a measured one, and with that the meaning of every derived figure would change silently — the behaviour profiles in A.12, the per-visitor valuation, the friction analysis. The import therefore sits beside the measured data, as ai_visitor_value and mp_profile already do.
Legal basisInstruction of the controller (Art. 28(3)(a)), given by switching on the respective path and supplying a dedicated service account. The original collection at Google is the controller's responsibility, not MousePlayer's: it happened before anything is fetched here and cannot be verified after the fact. A technical bar does exist nonetheless — only rows carrying a user_pseudo_id are imported. Where consent is denied, GA4 sends cookieless hits under Consent Mode and those carry none; they drop out mechanically rather than on the strength of an assurance. That is an indication, not proof, and it is recorded as such among the gaps in section 10.
Categories of recipientsNo new ones. The path PULLS, it does not deliver: no visitor's data leaves the house. The recipient is the customer, in their own interface. Google appears here not as a recipient but as a SOURCE — from an account the controller runs and whose contents they determine.
Third-country transferNo transfer of visitor data. The request goes to analyticsdata.googleapis.com and to BigQuery; what is transmitted is the service account credential and the query text, not any visitor data. The data flows in the opposite direction. Where the GA4 data resides at Google and under which safeguards follows from the controller's own account and is a matter for their contract with Google, not for this record.
Retention180 days, anchored to the day the row DESCRIBES (ga4_daily.tag, ga4_sessions.start_time) and not to the moment of import. The latter is carried forward on every run, because GA4 backfills for up to 48 hours and the same days are therefore fetched again; a period hanging on it would never expire. Enforced by the daily run in srv/db_worker.php, events before their sessions. An erasure request under Art. 17 takes them along as of 01.09.2026, as far as the row can be attributed through the customer identifier; the tier deletion run does NOT — GA4 data runs on its own clock, and a customer on the free tier would otherwise lose it after 7 instead of 180 days. With the project they go in any case, including the rows that never belonged to one of our sessions. What this does not reach is set out in section 10.

A.15 Exit prediction and the offer it can trigger

FieldEntry
PurposeEstimating, from the first seconds of a visit, how likely the visitor is to leave soon without a second page view. The controller may attach an offer to that estimate, shown to the visitor on HIS website. Unlike A.8 to A.14 this processing does not end in an analysis for the customer but acts back on the visitor — which is why it has its own entry rather than being a sub-item of A.8. In addition, the controller may have the text and design of such an offer proposed: a language model drafts them from the context of his website. A proposal never goes live by itself; it is taken over only part by part by the controller.
Categories of data subjectsVisitors to customer websites, and only on the FIRST page view of a session: a later view is no longer an arrival and is not scored. None of it runs without consent — the scoring sits behind the same gate as the recording.
Categories of personal dataSixteen measured features of the current visit, all formed in the browser: device (mobile yes/no), returning visitor, paid click, search engine, social network, entry on the home page, path depth of the entry page, night hour, number of mouse movements, scroll and click events within the window, the seconds until the first input, the deepest scroll position as a percentage of the page, the number of scroll reversals, whether the tab lost focus and whether there was no input at all. From those comes ONE percentage and up to three plain-language reasons. No content, no coordinates, no field values. The marker mp_seen_before in the browser's local storage records that the visitor has been here before; mp_offer_<date> counts how often an offer was shown to him today. The offer proposal adds no information about individual visitors but information about the website: title, description, headings and navigation from at most ten page snapshots, the at most 40 addresses that human visitors opened at least five times in 30 days, the share of page languages, and the colours, corner rounding and font of the page background and the most frequent button, measured in the browser. For this the language is recorded per page view (the page's lang attribute). Addresses and headings can contain whatever the website itself outputs (see A.17).
Where storedThe MODEL in projects.exit_modell (migration 100) — weights, means, deviations, quality and training time; it holds no personal data, only figures about the population. The RESULT per visit as an event in session_custom_events: exit_risk with the percentage and the reasons, plus exit_offer_shown, exit_offer_click and exit_offer_dismiss where an offer was shown. The latest value is additionally held, transiently, in the WebSocket server's connection record (Redis, mp_websockets) so the live view can display it; it disappears with the connection. The offer proposal is stored in projects.exit_offer_vorschlag (migration 114), the design measurements as the latest 60 per channel in Redis (mp_design:<channel>), the page language in session_pageviews.lang. For the phrasing the context travels through ai_ki_queue to our own machines running the language model (section 4).
Legal basisInstruction of the controller. Training is per project and only on that project's own sessions, at least 200; below that no model is created. The visitor's consent covers the recording the features come from — the same basis as A.8. Whether an offer is shown is for the controller alone to decide; for that display on his website he is the controller, not we.
Categories of recipientsThe customer in his interface. The percentage and the reasons additionally reach the VISITOR'S BROWSER — they are formed there rather than sent there — and are available to the controller's own code through MousePlayer.on('exit_risk') and the data layer. An offer that then appears is seen by the visitor himself. An offer proposal is seen only by the controller in his interface; it reaches the website only once he takes it over. Nothing goes to third parties.
Third-country transferNone. Training and scoring run on the infrastructure in Łódź, or in the visitor's browser. The offer proposal is phrased by the language model on our own hardware in Łódź (section 4); nothing goes to an external AI service.
RetentionThe events share the retention of the session they belong to (A.7) and fall with it. The model is overwritten on every run; there is no history. The two markers live in the visitor's local storage: mp_seen_before with no expiry until he clears it or withdraws his consent, mp_offer_<date> only for the current day (mp.js removes older entries the next time it sets one). In addition, mp_design_sent in the tab's session storage records that the design has already been measured. On withdrawal mp.js removes all three. The offer proposal stays until a new one replaces it; the design measurements expire in Redis after 30 days; the page language shares the retention of its page view.

A.16 Churn indicator for returning visitors

FieldEntry
PurposeEstimating from the same person's earlier visits whether he is drifting away, so the controller can respond. Unlike A.15 this assessment does not look at the current visit but at the history.
Categories of data subjectsReturning visitors to customer websites with at least 2 sessions. Anyone who came once gets no row. The key is the person in the sense of A.4 — the customer identifier set by the controller, otherwise the visitor identifier.
Categories of personal dataA risk value between 0 and 1, up to four plain-language reasons from a fixed list (falling engagement, short visits, friction, overdue; a purchase in the last two visits lowers the value), the number of visits and the time of the last one. No content, no coordinates. It is not a learned model but four countable observations — each stands in the row as a reason, so that a value never appears without one.
Where storedai_churn (migration 100): channelid, person_key, visitor_id, risiko, gruende, sitzungen, letzte_sitzung, berechnet_am. Additionally one Redis hash per channel, mp_churn:<channel>, expiring after two days, from which the WebSocket server reads on connect; only those above the threshold are placed there.
Legal basisInstruction of the controller; the underlying sessions rest on the visitor's consent (A.1). Calculated per project, never across customers.
Categories of recipientsThe customer in his interface. The value is also sent to the VISITOR'S BROWSER as soon as he connects and is available to the controller's own code through MousePlayer.on('churn_risk'); he may attach an offer to it. This transfer to the data subject's own device is why this entry stands here and not under A.8. Nothing goes to third parties.
Third-country transferNone.
RetentionThe row is rewritten for the channel on every run and falls with the sessions it comes from. The Redis entry expires by itself after two days.

A.17 Summary report about a visitor

FieldEntry
PurposeBringing all visits of ONE person together into a readable report: where he came from, what was paid for, where he got stuck and whether a goal came of it. The findings are calculated; a language model at most rephrases them. A report is requested either by the customer for a particular visitor or automatically for at most 5 visitors per project and night — chosen among paid visits without a purchase.
Categories of data subjectsVisitors to customer websites for whom more than one visit exists. The key is the person in the sense of A.4.
Categories of personal dataAt most fourteen calculated findings in text form: number of visits and pages, period, devices, source including campaign, purchases and goals reached, the three largest bottlenecks with their path, the last session, and the LABELS of the elements clicked (up to five, 60 characters each). Plus the generated text. Form field values are not among them — they never leave the browser. Labels and paths, by contrast, come from what the controller's website itself outputs and can therefore contain a name or an address where he displays one there or writes one into the URL; recorded as a gap in section 10.
Where storedai_besucher_bericht (migration 100) with channelid, person_key, lang, the findings, their hash, the text and the model name. For the phrasing the FINDINGS — not the recording — travel through ai_ki_queue to the machines running the language model (own hardware in the same local network, see section 4). Each report consumes a quota (ai_ki_kontingent per workspace and month, 3 on the free plan, otherwise 200).
Legal basisInstruction of the controller; the underlying sessions rest on the visitor's consent (A.1). The language model is neither trained nor modified; it only phrases.
Categories of recipientsThe customer in his interface. The visitor learns nothing of it — unlike A.15 and A.16, nothing here acts back on him.
Third-country transferNone. The language model runs on our own hardware in Łódź (section 4); nothing goes to an external AI service.
Retention180 days from the request, enforced by the follow-up pass of the AI process. If the visitor's last session falls away earlier, the report falls with it.

A.18 Visitor self-service access and erasure request

FieldEntry
PurposeShowing a visitor what is stored about him and letting him have it erased — assisting the controller with data subject rights under Art. 28(3)(e) GDPR. Reachable at /my-data, /meine-daten and /moje-dane.
Categories of data subjectsVisitors to customer websites, and only those who bring their own identifier. The controller must place the link; for his visitors he is the controller, and the Art. 13 notice belongs in his own policy.
Categories of personal dataShown are the number, period and domains of the sessions — no recordings. The identifier is a bearer token; whoever knows someone else's should not get to see a stranger's behaviour. For the same reason the answer stays with the DEVICE and does not use mp_person_sql() to bring together what belongs to one person: joining would disclose MORE, not less. Of the erasure request we store the identifier, the time of the request and the time of execution.
Where storedvisitor_deletion_requests (visitor_id, requested_at, done_at) — 0 rows. The page itself stores nothing; it reads. The request is carried out by the worker through the same deletion chain the controller triggers — a second deletion path would be a second place for the same truth.
Legal basisArt. 28(3)(e) GDPR (assisting the controller) and his instruction. The identifier is checked against its form (^v_[a-z0-9]{9}_\d{13}$); anything else is refused.
Categories of recipientsThe visitor himself. Nothing goes to the customer — he learns of an erasure request only because the sessions disappear.
Third-country transferNone.
RetentionThe row remains as evidence that and when erasure took place. It holds no behavioural data. Where a backup is restored, the recorded requests are executed again before the system goes back into service (clause 24.2 of the Terms of Service).

A.19 Follow-up review of bot detection — operator decisions as learning examples

FieldEntry
PurposeSessions that the wave detection has marked as bots retroactively are checked once more by a watchdog, and doubtful cases are reported in Urgent Logs. The operator decides there with one click: Human or Bot. Each decision becomes a learning example from which the weights of the seven signals are recalculated — only the weights, not the threshold, and only from a minimum number of decisions that can be set in the admin area (bot_revision_lern_min). Below that, fixed values apply.
Categories of data subjectsVisitors to customer websites whose session was reported as a doubtful case, and the person who decided (staff of the customer or of ours).
Categories of personal dataNo visitor content. Stored are the channel, the session identifier, the decision, the score, the names of the signals that applied (for example: identifier deviates from the wave, referrer present, address from a private network) and the identifier of the decider. The feature vector is part of the example because the session itself is gone after its retention period and the learning still needs it.
Where storedbot_review_labels (migration 140): one row per session; anyone who changes their mind overwrites. It is read by cfg/bot_revision_lib.php. Without the table the watchdogs run with fixed weights.
Legal basisInstruction of the controller; A.1 applies to the sessions themselves. The procedure improves the separation of bots from humans and thus the accuracy of the analytics (Art. 5(1)(d) GDPR). No decision about the visitor depends on it; a release frees a session from the marking, nothing more.
Categories of recipientsNone outside the interface; the examples are visible to the operator only.
Third-country transferNone.
RetentionDefault: no automatic deletion — an example is pseudonymous and carries no visitor content, and its value grows with the stock. The period can be set in years in the admin area (retention_bot_labels_years, 0 = unlimited); from 1, the status watchdog (srv/cron_status.php) deletes rows after that many years. It is in no session deletion chain: when the session goes, the example stays — intended, but recorded as a gap in section 10.

A.20 Element tracking — clicks on marked page elements

FieldEntry
PurposeIn the element wizard, the controller marks elements of its pages — individually or as a group of similar elements, by CSS selector — and receives clicks, sessions with a click, visitors and click rate for each marking. Counting is retroactive: srv/element_worker.php assigns clicks that have already been recorded (A.1, mouse_events_analytik) to the marked element in the snapshot of their own page view. A marking can also be a goal or a funnel step; its hits then count as a reached goal or step.
Categories of data subjectsVisitors to customer websites whose clicks were recorded.
Categories of personal dataA supplement to click recording (A.1), otherwise no new collection: when a visitor clicks an element that only appeared after the snapshot (for example an opened menu or a pop-up), mp.js has since 27.09.2026 also sent its structure — tag, classes, a few attributes (role, type, name, link path without query, data-*), the position among siblings of the same type and the visible label; in masked areas only tag and identifier. It is stored with the events of the page view (A.1) and shares their retention. For each hit the following are stored: channel, session and page view identifiers, start of the session, the kind of click (click or tap), the visible label of the element that was hit, taken from the snapshot — masked exactly as the recording contains it — and, if the controller chooses so, the value of an attribute of that element (for example data-id) for the breakdown. Since 27.09.2026 the element worker keeps a note of clicks whose target is missing from the snapshot and for which no structure was sent (older clicks): channel, session and page view identifier, time and a hash of page path and label — the label itself is not stored there. From resolved clicks it learns, per marking, which label on which page path hits the marking (path, label, two counters, no link to a visitor). When all observations agree, a noted click is assigned as a hit — marked as transferred — or as no hit. The marking itself (name, selector, page area) and the daily coverage counters carry no reference to a visitor.
Where storedmp_element_regeln, mp_element_klicks and mp_element_tag (migration 152), mp_element_offen and mp_element_muster (migration 155). Read by cfg/element_lib.php.
Legal basisInstruction of the controller (Art. 28(3)(a) GDPR), given by creating a marking; A.1 applies to the clicks themselves. The evaluation summarises existing recordings; the only addition is the structure of click targets created after the snapshot (above), which describes the same as the snapshot of the page. No decision about the visitor depends on it.
Categories of recipientsNone outside the interface as long as the marking is only evaluated. If the controller uses it as a goal or as a funnel step, reached goals and funnel drop-offs go to the transmissions they have switched on (4.1) — with the session identifier, the goal or funnel name and the label of the element clicked, and only for clicks no older than set in the admin (default two hours). Older hits count in the interface but are not transmitted.
Third-country transferNone.
RetentionA hit goes with the session it comes from: mp_element_klicks is part of the deletion chain (mp_sitzungen_loeschen() in srv/db_worker.php), and the element worker removes hits whose page view no longer exists every hour — so also after deletion paths outside that chain, within one hour at the latest. Noted clicks (mp_element_offen) go with their session along the same paths. Deleting a marking deletes its hits, counters, notes and patterns; the patterns carry no link to a visitor and also go with a changed selector or after a year without a new observation. There is deliberately no separate period: it would be longer or shorter than that of the recording, and either would be wrong.

A.21 Feedback surveys — one question to the visitor, the answer next to the recording

FieldEntry
PurposeThe controller asks visitors to their site one short question — likelihood to recommend from 0 to 10, five faces, yes/no or a choice — and, if they wish, a follow-up as free text. The answer is attached to the visit so that they can watch the recordings of the visitors who were unhappy. Question, timing, share of visitors and language are set by the controller.
Categories of data subjectsVisitors to customer websites who were shown a survey — according to a sample set by the controller and at most once per pause set by the controller —, and visitors who opened a survey themselves through a button at the edge of the page; there the pause starts only with an answer. Only what the visitor enters is collected: whoever dismisses the question leaves only the fact that it was shown and closed.
Categories of personal dataThe rating (a number or the chosen option) with its sentiment (negative, neutral, positive), time and survey identifier as an event of the visit (A.7): survey_shown, survey_answer and survey_dismiss. For a follow-up, a FREE TEXT of at most 500 characters with the language of the page. Before sending, mp.js removes e-mail addresses, IP addresses and phone numbers, and the worker does so a second time (mp_umfrage_text_filtern()); below the field visitors are asked not to enter personal data. In the visitor’s browser, mp_umfrage (localStorage) keeps, per survey, the sample draw and the time it was last shown, for the button at the edge of the page the time of the last answer.
Where storedThe rating in session_custom_events and session_custom_event_params (A.7); the free text in mp_umfrage_text (migration 157) — deliberately separate: it is the most sensitive part of a survey and does not belong in the table that webhooks and bridges read from. The surveys themselves, i.e. only the controller’s text, in projects.survey_json.
Legal basisInstruction of the controller (Art. 28(3)(a) GDPR), given by creating and switching on a survey. Answering is voluntary. Whether a legal basis exists for the question and the follow-up, and whether they are named in their privacy notice, is the controller’s responsibility; the text module for customers names the survey as a separate item. No decision concerning the visitor depends on it.
Categories of recipientsThe customer, in the results view and in the player. The rating is a custom event and therefore goes to the transfers the controller has switched on for such events (4.1). The free text goes to no bridge: mp_umfrage_text is read only by cfg/umfrage_lib.php.
Third-country transferNone — except through a transfer switched on by the controller (4.1), and there only the rating.
RetentionWith the session: mp_umfrage_text is part of mp_sitzungen_loeschen(), the player’s deletion path, mp_konto_loeschen() and the project and workspace deletion in the dashboard; the rating goes with session_custom_events. There is deliberately no period of its own — it would be longer or shorter than that of the recording, and both would be wrong. The mp_umfrage key in the browser is removed on withdrawal.

A.22 Search for displayed text — which visits had a given text on the page

FieldEntry
PurposeThe customer finds the visits in which a given text stood on their page — on load or shown later, such as an error message, a notice or “Sold out” — to watch their recordings. The search is in the User Explorer.
Categories of data subjectsVisitors of customer websites whose visit was recorded (A.2) and is not recognised as automated.
Categories of personal dataThe pieces of text that stood in the snapshot and the changes of the page (A.2), one per text node, plus their assignment to the page view. What is read has already been recorded: masked areas and form input are already replaced there and are not found. If a page itself shows personal data (a confirmation page with name and address), it is here too — as in the recording.
Where storedmp_text_block (dictionary per project, each piece of text once), mp_text_pv (which page view carried which piece) and mp_text_stand (which view has been read), migration 175. Filled every ten minutes by srv/textsuche_lauf.php on our own server.
Legal basisInstruction of the controller (Art. 28(3)(a) GDPR): the search opens up the same recording the controller commissioned. No decision about the visitor depends on it.
Categories of recipientsThe customer, as a filter in the User Explorer. No transfer, no bridge.
Third-country transferNone.
Retention30 days from the start of the page view, shorter than the recording: after that assignment and state are removed, a piece of text with its most recent view. If the session is deleted earlier (retention period, erasure request, deletion in the player, project or account deletion), the assignment goes with it — directly in mp_sitzungen_loeschen() and the project deletion, on all other paths at the latest after ten minutes through the run that removes assignments without a page view.

4. Part A — recipients, sub-processors and third-country transfers

The recording path itself contains no outgoing call to third parties. This was established on 10 Aug 2026 by searching cfg/, srv/, class/ and www/ (excluding vendor and node_modules) for curl_init, curl_exec, file_get_contents with http, fsockopen and stream_context_create, and for all https literals. Geo resolution runs against local MaxMind files, the interface stylesheet is served as a pre-built file from our own server, and flag images come from /img/flags/. Since 23 Sep 2026 e-mail is sent via SMTP through Namecheap Private Email (mail.privateemail.com, port 465, TLS) with the sender office@mouseplayer.com; the PHP function mail() would only be used if no SMTP server were configured, which is not the case. Limit of this search: the vendor directories (Stripe SDK, Google Cloud SDK, PHPMailer, Symfony) were not searched — that is where the endpoints of the services named below are located.

RecipientRole and what is transferredDestinationSafeguardStatus
StripePayment processing for CUSTOMER accounts. E-mail, name, billing address and payment data of customers. www/billing_ajax.php:134 sets the API key; lines 381 and 687 pass customer_email; line 48 sets billing_address_collection='required'; line 54 customer_update for address and name; lines 462/527/672 pass metadata. Return channel www/api/webhook_stripe.php, table stripe_events. No visitor data.USADPA accepted on 1 Aug 2026 as part of the terms accepted when the account was opened (statement of the controller). Contracting party: Stripe Payments Europe, Ltd.; for transfers to the USA, Stripe, Inc. Module 2 (controller to processor). This follows from the allocation of roles rather than from a lookup: MousePlayer is the controller for the account and billing data transmitted, Stripe the processor for that part. The accepted version itself has not been inspected; if it differs, it prevails.As of 16 Oct 2026 not live: both key slots hold test keys, and no real payments are processed.
VIES (European Commission)Verification of a VAT identification number provided by a CUSTOMER via the public REST interface of the VAT Information Exchange System (cfg/lib.php). Country code and VAT number are transmitted; the validity and, where available, the company's name and address are returned and stored with the account (B.1). No visitor data.EUPublic body of the Union; not a processor, no contract required. The query serves to verify the customer's tax details.In use as soon as a customer enters a VAT number.
TelegramSupport notification. Until 11 August 2026 cfg/lib.php transmitted the ENTIRE support case: user status including the user ID, e-mail address, subject, 'IP / Kraj' with the UNTRUNCATED IP address, the source URL and the full message text. Since 11 August 2026 (commit 02846c2, cfg/lib.php) one fixed string and nothing else goes out: 'MousePlayer: es liegt eine neue Supportanfrage vor.' Not a single variable, no parse_mode, and deliberately no case number and no link taken from the request. The request itself is still stored unchanged in support_requests — the data no longer leaves, it does not disappear.Outside the EUUntil 11 August 2026: none — Telegram offers no data processing agreement for bots. Since 11 August 2026 none is required: the notification contains no personal data, so Telegram is neither a processor nor a recipient. Owner's decision of 11 August 2026: the channel stays, the message is reduced to a bare signal, the IP address is removed. A guard (ai/pruefstand/test_telegram_signal.php) checks not the result but the possibility — which variables can reach the message at all; four are permitted, 15 attributes are forbidden, and the text must be a single-quoted string with no interpolation.ACTIVE, but carrying no personal data — defused on 11 August 2026. TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID are still set in .env; only the signal goes out.
Google reCAPTCHALogin, registration, support form, billing page. Server-side verification at www/login.php:31-45 and cfg/lib.php:6337 against https://www.google.com/recaptcha/api/siteverify; browser-side at www/billing.php:1290.USAreCAPTCHA remains in production. Google data processing terms accepted on 1 Aug 2026 (statement of the controller). Contracting party: Google Ireland Limited; for transfers to the USA, Google LLC. On the development system the service is off (mp_recaptcha_aktiv() returns !mp_ist_dev()), in production it is on — there the transfer occurs on every sign-in, registration, password and billing action.Inactive on this instance only: mp_recaptcha_aktiv() is defined at cfg/config.php:131-133 as !mp_ist_dev() and APP_ENV=dev. Active in production.
Google BigQueryNightly export into the customer's OWN GCP project. srv/cron_integration_bigquery.php:95-100 selects session_id, channelid, visitor_id, custom_user_id, ip_address, country, referrer, user_agent, screen_width, screen_height, start_time, end_time, duration_seconds, device_type and loads them as NDJSON into visitor_sessions_YYYYMMDD.Region chosen by the customerThe destination is the customer's own project, so the customer is the importer. The service account key is stored encrypted in projects.bigquery_sa_json (decrypt_sa_key, key BIGQUERY_SA_AES_KEY from .env).INACTIVE. projects.bigquery_active=0 for all 7 projects, bigquery_project_id NULL everywhere, and the process is not running.
Customer webhookssrv/cron_integration_webhook.php:63-80. Body built at srv/db_worker.php:1313-1323: timestamp, event_type, channel_id, session_id, a direct player link 'https://<host>/player?id=...&session=...' and the event parameters. Special formatting for Slack and Discord (mp_format_webhook_body, cfg/lib.php:5978).Undetermined — set by the customerThe destination is chosen by the customer and is therefore within the controller's own responsibility. Yes, the destination is recorded per Customer (decision of the controller, 17 Aug 2026). It already sits on the project; what is missing is the record of WHO set it and when. Since 17 Aug 2026 every change of the webhook destination appears in the change log (log_audit, action update_integrations, field webhook_host). Only the host is recorded, not the full address: a webhook path often carries a secret — Slack and Discord put the key in the URL — and a change log is the wrong place for that. For the question of where event data leaves the house, the host is enough.INACTIVE. projects.webhook_active=0 and webhook_url NULL for all 7 projects. The dispatcher process is running.
Meta (Conversions API)Only if the controller switches the bridge on and stores their own access token. What is transmitted are exclusively fulfilled goals of the given project: goal name, timestamp, an event identifier derived from session and goal for deduplication, value and currency where set, plus the cookies _fbp and _fbc and the user agent string. Name, email address, phone number and IP address are not transmitted — MousePlayer only holds a truncated IP anyway, which would be worthless for matching. Sending happens exclusively for sessions with consent_state = 'granted'; without _fbp/_fbc the transmission is omitted entirely, because without a matching key it would serve no purpose. Code: mp_capi_meta_ausloesen() and mp_capi_meta_zustellen() in cfg/lib.php, dispatch via srv/cron_integration_webhook.php.USAMeta is not a sub-processor of MousePlayer. MousePlayer carries out the transmission on the controller's instruction — that instruction consists of switching the bridge on and storing the token. The recipient is the controller's own ad account; the contractual relationship with Meta, including the safeguards for transfer to the USA, exists between the controller and Meta, not between Meta and MousePlayer. Meta processes the data for its own purposes and, to that extent, as its own controller.Bridge available since 19.08.2026, switched on for no project as shipped.
HubSpot / PipedriveOnly if the controller switches the bridge on and stores their own API token. What is transmitted is the customer identifier the controller set themselves (setUserId()), used to locate the contact, followed by a note containing the session replay URL. No behavioural data, no recording content. If the lookup finds no contact, the operation ends without any further transmission. Code: mp_crm_kontakt_suchen() and mp_crm_notiz_anhaengen() in cfg/lib.php.USA (HubSpot), EU (Pipedrive)As with Meta: the recipient is the controller's own CRM account, and the contractual relationship exists between them and the provider. MousePlayer transmits on their instruction.Bridge available since 19.08.2026, switched on for no project as shipped.
Google Cloud Storage (Warsaw)Off-site copy of the daily backup. Documented in OFFENE_PUNKTE.md section 3.3: 'mysqldump --all_databases' and 'tar /var/www/' — every database and the entire web tree, including every .env. Locally evidenced at 653 MB database and 678 MB files. Until 11 August 2026 the backup ran daily at 02:00 and went out unencrypted. Since 11 August 2026 it is encrypted on our own server before it is transferred (rclone crypt; file and directory names are encrypted as well), and the two key files are held on that same server with mode 600 — Google does not have them; the scheduled run has taken place at 03:00 since then. The previous, unencrypted destination folder holding the copies from before 11 August 2026 was deleted on 12 August 2026.Poland (EU) – bucket in region europe-central2 (Warsaw), since 5 September 2026. Until then the copy was held in a private Google Drive account for which no data region can be booked; the destination had to be stated as USA / worldwide.Processing on our behalf under the Google Cloud Data Processing Addendum, accepted for the Cloud project; contracting party Google Ireland Limited. This removes the deviation stated here from 17 August 2026 to 5 September 2026: those terms are not available for a private Google account, and the general terms of use do not establish processing on behalf under Art. 28 GDPR. The copy is still encrypted before transfer (rclone crypt, including file and directory names) and the keys are held solely by the controller — Google sees blocks without meaning. That takes the content from Google, not the role.ACTIVE. Until 11 August 2026 unencrypted and unverified at the destination — the run used root's rclone configuration and the script logged nothing. Since 11 August 2026 encrypted; the runs of 11 August 2026 (started by hand) and 12 August 2026 (scheduled, at 03:00) are logged and both passed a check at the destination (byte count of the database dump). Daily operation is therefore evidenced, not merely the first attempt. The operator has tested decryption; no measurement of our own exists for that. The previous, unencrypted destination folder was deleted on 12 August 2026. Since 5 September 2026 the copy has gone to Google Cloud Storage in Warsaw; the copies previously held on the Drive account were deleted on 6 September 2026, after a restore from the new destination had been verified. Account type, data region and contract are therefore settled. Since 6 Oct 2026 the backup also contains the archive of the moved-out recording events (/mnt/mouseplayer-dev-archive, A.1); locally this archive lies on the same disk as the system, and only the encrypted copy provides storage at another location. Restore tests from the copy were carried out on 5/6 Sep 2026 and on 7 Oct 2026 (complete restore of the data). Daily backups are kept for 7 days, weekly backups for 364 days.
NamecheapDomain registration, authoritative DNS, forwarding of incoming e-mail; since 23 Sep 2026 also sending of outgoing e-mail through Namecheap Private Email. Domain data; content and sender of e-mail addressed to MousePlayer. For outgoing e-mail: name, e-mail address and content of the message to the account holder, for the sign-in notice also IP address, device and approximate location.USAData Processing Addendum accepted on 1 Aug 2026 (statement of the controller); it covers the current Standard Contractual Clauses. Incoming mail runs permanently through the Namecheap forwarding; no further mailbox provider is involved (decision of the controller, 17 Aug 2026). Since 23 Sep 2026 outgoing mail leaves through the Namecheap Private Email SMTP service (TLS): contract notices, account notices, sign-in notices and the alerts of the monitors.In use.
Let's Encrypt (ISRG)Issuance and renewal of TLS certificates. Domain names only.USAPublic certification authority; processes no personal data on our behalf.In use.
consented.euCMP on MousePlayer's OWN pages, loaded in the visitor's browser: cfg/header.php:48 (stub.js) and line 50 (cmp.js). Concerns visitors to mouseplayer.com, not the recording at customers.Place of processing: the same server as MousePlayer (/var/www/html/ConsentedEU on the same machine, measured 17 Aug 2026). Contractual basis: not applicable — consented.eu is operated by the same legal entity (statement of the controller). There is no transfer to a third party.Not applicable for the same reason: no third party, hence no agreement.In use on own pages.

Hosting: not applicable — self-operated on own hardware on the Toya connection (AS16342, Łódź), no transfer to a third party (decision of the controller, 17 Aug 2026); if operations move to a data centre or cloud provider this paragraph must be rewritten. The own hardware comprises the server running the application, the database and the recording, and the machines with a graphics card running the language model — currently one, running our own AI, self-hosted on our own hardware. All of them stand at the same site in the same local network and belong to MousePlayer; the same applies to any further machine. They fetch the calculated findings over HTTPS from ai_ki_queue, every request signed with its own secret, and return the phrased text. The recording itself never reaches them, and nothing goes to an external AI service (A.8, A.12, A.15, A.17). While such a machine is down for maintenance, the jobs wait until it is back; in the meantime the interface shows the calculated version, and no job is passed to another service.

4.1 Recipients switched on by the controller

The table above lists recipients MousePlayer engages itself. Alongside them are bridges the controller switches on and for which they store their own credentials. They are not sub-processors of MousePlayer; the contractual relationship with them exists between the controller and the recipient, including the safeguards for third countries. What is named are categories rather than providers — which providers are available within a category changes with the feature set; what governs is the selection in the project settings. Every transmission is triggered server-side from srv/cron_integration_webhook.php; the outcome is recorded in the delivery log (A.11).

CategoryWhat goes outWhat triggers it
Webhook to a freely chosen addressEvent name, channel and session ID, replay URL, event parameters. Signed with HMAC-SHA256 over timestamp and body.an event the controller has subscribed to
Customer relationship system (CRM)The customer ID the controller set themselves for the visitor, plus the replay URL, attached as a note to an existing contact. If no contact exists, none is created.session end with a customer ID set
Helpdesk and ticketingThe same item as an internal note on the sender's most recent open ticket. If there is no open ticket, nothing happens.session end with a customer ID set
Email and lifecycle marketingCustomer ID, event name and replay URL as an event in the controller's system.session end with a customer ID set
Advertising networks (Conversions API)The click ID from the advertising network's own cookie, event name, value and currency. Name, email address, phone number and IP address are not transmitted.a fulfilled goal where consent was given
Object storage and spreadsheetsCustomer ID, timestamp and replay URL as an object or a row in the controller's own storage.session end with a customer ID set
Issue and task trackingTitle and description of an item carrying the replay URL.a frustration signal or a JavaScript error
The controller's AI agents via the MCP serverRead-only: projects, key figures, session lists with replay link, individual sessions with page views and events, pages, frustration signals and goals — not the page snapshot. Authentication uses a read key of the controller's REST API.a query by the agent, only where access is enabled for the project (projects.mcp_active) and on a paid tier

Third-country transfer: undetermined. The controller chooses the destination and the provider; the safeguard under Chapter V GDPR therefore rests with them. In no case is the recorded page content, the mouse trail or form text transmitted — the bridges carry identifiers and event names, not the replay.

5. Part B — Art. 30(1): activities carried out as controller

Controller for everything in this part: LW IT Solutions Company Lukas Wójcik, al. Tadeusza Kościuszki 80/82 lok. 301, 90-437 Lodz, Poland, office@mouseplayer.com. No representative required (established in the Union). No data protection officer designated — section 9. Technical and organisational measures: section 8.

B.1 Customer accounts, workspaces and billing

FieldEntry
PurposeOperating MousePlayer's own customer accounts, workspace membership and billing.
Categories of data subjectsCustomer users. Measured: 4 accounts; workspace_users 4 owner, 1 admin, 1 viewer.
Categories of personal dataE-mail, normalised e-mail, first and last name, business type, account type (business or consumer, customer_type), language, theme. Company data: company name and address, country and VAT identification number together with the result, time and source of the check, and the name and address according to VIES. Company logo (file name and time; the file itself outside the web directory). Password hash, verify and reset tokens, reset expiry. Two-factor sign-in (TOTP, migration 132): the secret in encrypted form, the last time step used, the time of setup and hashed recovery codes. Switch for the sign-in notification (login_alert, B.7). Device identifier (device_id), suspicion flag is_suspicious. Last FULL IP address, country, city, ASN number and ASN organisation. Billing data: tier, currency, Stripe customer ID, Stripe subscription, billing period, status, cancellation at period end. Time of a warning about inactivity. IP history per user.
Where storedusers (user_id, email, normalized_email, device_id, password_hash, first_name, last_name, business_type, customer_type, status, lang, verify_token, reset_token, reset_expires, is_superadmin, is_suspicious, new_email, email_verify_token, last_country, last_city, last_asn_number, last_asn_org, last_ip, created_at, last_login, subscription_tier, stripe_customer_id, stripe_subscription_id, billing_period_end, billing_status, retention_grace_until, retention_grace_days). user_ip_history (user_id, ip_address, asn_number, asn_org, last_seen) — 5 rows, 0 of them truncated. workspace_users (workspace_id, user_id, role); workspace_invites (token, workspace_id, email, role, invited_by, created_at). Additionally named in users since this version: company_name, company_street, company_postcode, company_city, company_country, vat_country, vat_number, vat_valid, vat_checked_at, vat_source, vat_name, vat_address, company_logo, company_logo_at, login_alert, totp_secret, totp_enabled_at, totp_last_step, totp_recovery, theme_mode, cancel_at_period_end, inactive_warned_at, sessions_revoked_at. Row counts in this entry: as of 10 Aug 2026.
Legal basisNot documented in the code. Art. 6(1)(b) for performance of the contract (account, workspace, billing) and Art. 6(1)(c) for the statutory retention of invoices (decision of the controller, 17 Aug 2026). The period is stated in the retention row.
Categories of recipientsStripe (billing, see section 4). VIES of the European Commission (verification of the VAT number, section 4). Namecheap Private Email as the dispatch service for e-mails to the account holder: account confirmation, password reset, invitations, sign-in notification, warning before deletion for inactivity, notice of changed contract documents, weekly report, support replies (section 4). Members of the same workspace see the name, e-mail address and role of the other members as well as the account type of the workspace, i.e. that of its owner (Terms of Service clause 6.6), and the owner's company logo; an invited person sees this logo through the invitation link while the invitation is open.
Third-country transferStripe (USA) and Namecheap (USA); contracting parties and safeguards in section 4. VIES is located in the Union.
RetentionIn full when the account ends — and it ends even without the owner acting. If the owner triggers deletion in the account settings, mp_konto_loeschung_ausfuehren() (cfg/lib.php) clears, among others, user_ip_history, live_tokens, login_sessions, remember_tokens, report_subscriptions, the counters of the recording quota, api_keys, workspace_invites, workspace_users, workspaces and users; audit_logs, bot_review_labels and the entries in mp_loesch_archiv are depersonalised. Since 7 Oct 2026 (Migration 189) this path archives first: the account is suspended, sign-ins are revoked, its own workspaces and projects are hidden and no longer recorded; what can be restored is held in mp_loesch_archiv. After 30 days – or at once if the owner chooses so – the db_worker erases permanently through the chain named above and then counts whether anything is left. An account erased permanently leaves neither an identifier nor a name in the archive. If the owner stays inactive, the daily run warns after 24 months without a sign-in and deletes 30 days later through the same chain. The clock runs from the last sign-in, or from sign-up for an account never used. Deletion only ever follows a warning that was delivered; if sending fails, the period never starts. Both values live in site_settings and are configurable in the admin area. Invoices and the associated accounting records are kept for five years, counted from the end of the calendar year in which the tax payment deadline fell (statement of the controller, 17 Aug 2026: "5 lat, licząc od końca roku kalendarzowego, w którym upłynął termin płatności podatku"). Invoice data is therefore exempt from account deletion; the remaining account fields go with the account. Since 16 Oct 2026 only accounts on the free tier are warned and deleted for inactivity: own tier free and no membership of a workspace whose owner is on a paid tier. This deletion does not archive first but deletes directly through the chain named above. The company logo is deleted with the account, as the last step of the deletion.
Company logo (since 7 Oct 2026)The customer can upload a logo in the settings (PNG, JPEG, WebP). What is stored is not the delivered file but an image redrawn on the server, outside the web directory (daten/firmenlogos/, migration 184). It is visible to the account itself, to the superadmin, to the members of the workspaces owned by the account, and to invited persons through the invitation link while the invitation is open; all other requests receive the same response as for a missing logo.
Account export (Art. 15(3), Art. 20 GDPR)After entering their password, the account holder can download in the settings a ZIP archive containing the data of their account as JSON and the company logo. The tables are determined from the schema (columns that refer to the account); secrets such as the password hash, tokens and the TOTP secret are left out. Failed attempts are counted in the change log and block the export after five attempts within 15 minutes. The archive is created only as a temporary file for delivery.
Further account data (as of 16 Oct 2026)legal_acceptances: acceptance of the contract documents (including the withdrawal notice and the request for immediate start) and delivered notices of changes, each with account, full IP address, browser identifier, language, checksums of the versions and origin of the entry; the most recent acceptance (not the notices) of an existing account is kept for as long as the account exists, so that it can be shown which version a current customer accepted (since 16 Oct 2026); older acceptances, notices and the rows of deleted accounts are deleted 730 days after the respective acceptance or notice (classification of the legal basis for retention after the end of the account outstanding with the controller). report_subscriptions: subscription to the weekly report with unsubscribe token and last dispatch; deleted with the account. workspace_transfers: transfer of a workspace to an e-mail address with token and expiry; deleted with the workspace. mp_dashboards: an account's own dashboards per project; deleted with the account or project. Enterprise SSO (sso_providers, sso_identities, sso_requests, sso_assertions, sso_policies, migrations 187 and 188): the configuration of the identity provider chosen by the workspace owner encrypted, per account only a hash of the identifier at the provider, sign-in requests valid for five minutes; the server calls the endpoints of that provider, the recipient being the provider chosen by the customer. As of 16 Oct 2026 no workspace has a provider set up. Recording quota (mp_aufnahme_monat, mp_aufnahme_sitzung, migration 178): counters per owner and month, per session only a hash of project and session identifier; a daily run deletes the hashes of past months, the counters are deleted with the account.

B.2 Portal access, security and audit logs

FieldEntry
PurposeAccess, security and change logging for MousePlayer's own portal.
Categories of data subjectsVisitors to and users of mouseplayer.com and www.mouseplayer.com.
Categories of personal dataFULL IP address, user agent, complete request path, country, city, device type, operating system, browser, ASN. Security events with partly truncated IP: WSS_CONNECT 1261, url_excluded 71, consent_blocked 24. Change log: user, workspace, action, target, details as JSON, IP.
Where storedmouseplayer_logs (log_id, ip_address, user_agent, page_url, created_at, country_code, asn_number, asn_org, city, device_type, os_name, browser_name) — on 10 Aug 2026 36409 rows, 8.5 MB, with no truncated IP values. Since 17 Aug 2026 log_mouseplayer_traffic() (cfg/lib.php) only places one raw row per request into the Redis queue queue:traffic_logs; GeoIP and ASN resolution, classification of browser and device and the writing — including users.last_ip and the account's location fields — are done by the db_worker. The address is determined by getBestClientIP(); headers are trusted only when the connection comes from the upstream nginx. security_access_logs (id, ip_address, user_agent, event_type, details, created_at) — on 10 Aug 2026 1354 rows, 350 of them truncated. audit_logs (log_id, user_id, workspace_id, action, target_id, details, ip_address, created_at) — on 10 Aug 2026 666 rows, untruncated. banned_ips (ip_address, scope, reason, banned_at, expires_at).
Legal basisNot documented in the code. The only recorded reasoning is the comment at class/Server.class.php:421-425, which distinguishes Rule A (full address, for DIRECT access to our own application) from Rule B (truncated, for visitor data). That explains the design decision but is not a legal basis. Art. 6(1)(f). Purpose: defence against abuse and accountability (confirmed by the controller, 17 Aug 2026). Balancing: without logs neither an attack can be detected nor can it later be established who made a change. There is no milder means achieving the same — an anonymised log loses precisely the attribution at issue. The interference is bounded by the periods (72 hours for the security log, 60 days for the access log, 60 days for the change log) and by the fact that the logs concern only access to the application itself, not the recorded visitors. Those affected are Customer users who use the service deliberately and expect their administrative actions to be logged.
Categories of recipientsMousePlayer itself. audit_logs is additionally visible to the superadmin (www/admin.php).
Third-country transferNone.
Retentionmouseplayer_logs: 60 days. security_access_logs: 72 hours. banned_ips: on expiry of expires_at; entries set by hand without expiry remain until the operator lifts them. audit_logs: 60 days, new as of 17 Aug 2026. user_ip_history: 90 days from the last time an address was seen, new as of 24 Sep 2026; at the latest with the account. All values live in site_settings and are configurable in the admin area. That the change log outlives the security log is deliberate: the access log answers who was there, the change log answers who altered something. The latter is the accountability record under Art. 5(2) and has to survive a dispute that only surfaces months later.

B.3 Support requests

FieldEntry
PurposeReceiving and handling support requests. Since 30 September 2026 this includes having a message translated and a draft reply prepared by a language model on our own hardware; a person reviews every draft and sends it.
Categories of data subjectsPersons making a request, whether logged in or not.
Categories of personal dataE-mail address, subject, free text of the message. FULL IP address — REMOTE_ADDR, overwritten with HTTP_X_FORWARDED_FOR, without truncation. User agent, source URL. The field for a country code is read from the HTTP_CF_IPCOUNTRY header; since no Cloudflare is placed in front, it always holds the fallback value XX or nothing.
Where storedsupport_requests (id, user_id, channelid, email, reason, message, status, ip_address, user_agent, source_url, country_code, created_at). Since 30 September 2026 also mp_support_ki: one row per job (translation, draft, translation of our own draft) with the text prepared for the model, the result, the model name and the duration; bound to the request by a foreign key.
Legal basisArt. 6(1)(b) where the person asking is a customer and the request concerns performance of the contract; otherwise Art. 6(1)(f). The full IP address rests on the same basis: it serves to defend the support channel against abuse. Bulk or repeated submissions, threats and other detriment can neither be detected nor stopped without it, and a truncated address does not carry that distinction. Decision of the controller, 17 Aug 2026.
Categories of recipientsUntil 11 August 2026: Telegram. cfg/lib.php sent user status, e-mail address, subject, the UNTRUNCATED IP address with country, the source URL and the full message text to api.telegram.org. Since 11 August 2026: none. All that reaches api.telegram.org is the fixed string 'MousePlayer: es liegt eine neue Supportanfrage vor.', with no variable of any kind; Telegram therefore receives no personal data and is not a recipient. The request itself remains unchanged in support_requests. Language model (since 30 September 2026): none. It runs on our own machines with a graphics card, in the same local network and at the same site as the server; they fetch the job over HTTPS, every request signed with its own secret. They receive the message text, the subject, whether the person asking has an account and the path of the source page – e-mail addresses and digit sequences of six or more digits are replaced beforehand (mp_supki_saeubern() in cfg/support_ki_lib.php), in our own draft only e-mail addresses. Not the IP address, not the e-mail address of the person asking. No external AI service, not even as a fallback.
Third-country transferUntil 11 August 2026: Telegram, outside the EU; no data processing agreement is available for bots — see section 4. Since 11 August 2026: none. The notification to api.telegram.org no longer contains personal data, so no transfer to a third country takes place. The channel continues to exist as a bare signal — see section 4.
Automated decisionNone. The model only proposes text; nothing is sent automatically – a person reads the draft, changes it where needed and sends it. Not a decision within the meaning of Art. 22 GDPR.
Retention730 days, enforced by the daily run in srv/db_worker.php; the value lives in site_settings under retention_evidence_days and is configurable in the admin area. On account deletion the request remains but loses its link to the account (user_id becomes NULL); the privacy policy states this expressly. Rows in mp_support_ki fall with their request (ON DELETE CASCADE, migration 168), on every deletion path.

B.4 Customer acceptance of regional recording rules

FieldEntry
PurposeEvidencing the customer's acceptance of the regional recording rules — a contractual declaration.
Categories of data subjectsCustomer users.
Categories of personal dataUser ID, e-mail, regions, text version, text checksum, language. FULL IP address and user agent — deliberately, and the reason is recorded in ai/migrations/018_consent.sql: this is the acceptance of a contract by a business partner, not visitor data.
Where storedconsent_region_acceptances (id, channelid, workspace_id, user_id, user_email, regions, text_version, text_checksum, text_lang, ip_address, user_agent, accepted_at) — 0 rows.
Legal basisEvidence of a contractual declaration, as reasoned in the migration header.
Categories of recipientsMousePlayer itself.
Third-country transferNone.
Retention730 days from acceptance — the same period as the other records of consent to terms (support_requests, legal_acceptances; for legal_acceptances the most recent acceptance of an existing account is kept beyond that, see B.1), because they answer the same question: who agreed to what, and when. The value lives in site_settings under retention_evidence_days; enforced by the daily run in srv/db_worker.php.

B.5 Visitors to our own website

FieldEntry
PurposeOperation of mouseplayer.com itself: consent dialogue, defence against automated access, the demonstration on the home page and the web server logs.
Categories of data subjectsVisitors to mouseplayer.com.
Categories of personal dataIP address — untruncated in the server logs —, time, requested address, status code, browser identification. For the demonstration additionally pointer and click positions, a randomly assigned session identifier and the country derived from the address. For reCAPTCHA the address plus device and behaviour characteristics.
Where storedAccess logs of nginx (/var/log/nginx/) and Apache, both with the full address, logrotate daily with 14 rotations. Since 16 Aug 2026 nginx is the first layer that sees a visitor address; Apache sits behind it on the loopback and receives it via the PROXY protocol or X-Forwarded-For. The demonstration data is not stored, only passed on in the moment.
Legal basisDemonstration: consent, Art. 6(1)(a) — the gate for it has been in place since 17 Aug 2026. reCAPTCHA and server logs: legitimate interest, Art. 6(1)(f). Art. 6(1)(f). Purpose: defence against abuse and accountability (confirmed by the controller, 17 Aug 2026). reCAPTCHA: without protection against automated access, the sign-in, registration, support form and billing pages lie open to attack. The milder means — a plain rate limit — has been additionally in place since 17 Aug 2026 but does not fully replace the scoring. Server logs: the untruncated address is needed to attribute repeated or bulk access to the same sender; a truncated address does not carry that distinction. The interference is bounded by the 14-day period and by the fact that the logs are not combined with other data.
Categories of recipientsGoogle Ireland Limited (reCAPTCHA) and consented.eu (consent dialogue). For the demonstration additionally the other visitors who have the home page open at the same moment — they see the pointer and the country, not the address.
Third-country transferUnited States via reCAPTCHA; basis and acceptance date see section 4.
RetentionServer logs 14 days. The demonstration stores nothing.

This entry was missing until 17 Aug 2026, even though section 4 already named the consent dialogue and reCAPTCHA as recipients — the corresponding processing appeared nowhere. The public privacy policy was extended on the same day to cover the same subject (its section 10); the two texts are to be read against each other.

B.6 Accesses to a share link

FieldEntry
PurposeEvidence of how often and from where a share link was used. Such a link opens a complete recording without a login; without this log it cannot afterwards be established how often it was used or whether it left the intended circle.
Categories of data subjectsPeople who open a share link — usually the Customer's staff or recipients chosen by them. Not the recorded visitors; those are different people.
Categories of personal dataIP address of the person accessing (untruncated), browser identification, time, identifier of the link.
Where storedshare_views (migration 043), tied to session_shares by a foreign key with ON DELETE CASCADE.
Legal basisArt. 6(1)(f). Purpose: defence against abuse — a link without a login is exactly the kind of address that gets forwarded. Balancing: there is no milder means: a truncated address does not carry the question whether two accesses come from the same place, and without it forwarding is neither detectable nor provable. The interference is narrowly bounded — it does not affect the recorded person but the one accessing, and the row dies with the link. Those affected are people opening a link to someone else's recording, who must reckon with that access being logged. (decision of the controller, 17 Aug 2026)
Categories of recipientsNone. The log does not leave our own database.
Third-country transferNone.
RetentionWith the link. When it expires or is revoked, the log goes with it through the foreign key. A link lives 90 days at most, 7 by default — the application cannot create a longer one.

That the row dies with the link is not a convenience here but half the legal basis: a log outliving the link no longer has a purpose to carry it.

B.7 Active sign-ins and their revocation

FieldEntry
PurposeShowing the account holder where their account is signed in, and letting them end a single device or all the others. Without this list a hijacked sign-in can neither be spotted nor ended: until 28 August 2026 a password change only invalidated the “stay signed in” cookies, while the running sessions lived on as files — measured at 24 to 54 minutes.
Categories of data subjectsHolders and members of a customer account. Not the recorded visitors; those are two different people.
Categories of personal dataIP address (unshortened), user agent, and derived from it the browser, operating system and device type, coarse location (city and country) from the address, time of sign-in and of last activity. The session identifier itself is not stored, only its SHA-256 hash. Alongside it a device fingerprint — a hash of browser, operating system, device type and address range (/24 or /48, never the full address), by which an already known device is recognised.
Where storedlogin_sessions (migration 094), linked through remember_selector to remember_tokens (migration 037).
Legal basisArt. 6(1)(b) read with Art. 32. Purpose: account security — this processing is the technical measure, not a by-product of one. Balancing: there is no milder means. A shortened address fails at precisely the question the list exists for (“was that me?”), and without browser and location one row cannot be told from another. The data arises on every sign-in anyway; what is new is only that it becomes visible and revocable. It is visible to the account holder alone.
Categories of recipientsNone. The list does not leave our own database.
Third-country transferNone.
Retention90 days from the last activity, and in full when the account ends. Anchored to last activity rather than to creation: a device signed in for months but used yesterday is a current sign-in and belongs in the list.

Since 28 August 2026 the same data additionally produces a notice to the account holder when their account is opened from a device that has not appeared before. It goes to the data subject's own address on file and to nobody else; no recipient within the meaning of Art. 4(9) is added by it. Any account can switch it off in the settings without losing the list itself — the default is on, because a security notice you have to enable first fails to reach exactly those who need it. The fingerprint deliberately rests on the address range rather than the full address: a household gets a different IP on its nightly reconnect, and with the full address a notice would arrive every morning — the signal would be worthless, and with it the notice.

Two decisions here need explaining. What is stored is the hash of the session identifier and never the identifier itself: otherwise a database dump would hand out valid session cookies, and a dump in the wrong hands is exactly the case this table is built against. And a revoked row is not deleted but marked as revoked — a deleted row would be an unknown session to the check that runs on every request, and an unknown one may be written back. The revocation would have undone itself.

B.8 Operational alerts and withheld e-mails (urgent_logs)

FieldEntry
PurposeOperational alerts for the superadmin: faults detected by the watchdogs in srv/cron_status.php, doubtful cases from the bot review (A.19), at the superadmin's choice a project's anomalies instead of an e-mail, and outgoing e-mails held back by a block list. The block prevents e-mails from going out to listed own addresses or to reserved test and example domains; the withheld e-mail is stored here so that it remains traceable what would have been sent.
Categories of data subjectsCustomer users and other recipients of a withheld e-mail; indirectly visitors whose session ID appears in a bot-review alert.
Categories of personal dataSource, subject, alert text, times of creation, last repetition, resolution and e-mail dispatch. For a withheld e-mail, the recipient address and the full text of the e-mail in clear text — depending on its kind, for example name and e-mail address, and for a sign-in notification additionally IP address, device and approximate location.
Where storedurgent_logs (id, quelle, betreff, meldung, erstellt_am, zuletzt_am, erledigt_am, mail_status, mail_am, stoerung_beendet_am).
Legal basisArt. 6(1)(f). Purpose: operational security and traceability of e-mail dispatch. The alerts are accessible only to the superadmin and are deleted within a set period after resolution.
Categories of recipientsThe superadmin. Fault alerts are additionally sent by e-mail to the operator's alarm address (via Namecheap Private Email, section 4); a withheld e-mail goes to nobody.
Third-country transferNone; for dispatch to the alarm address see Namecheap in section 4.
RetentionA row remains until it is resolved — by the watchdog once the fault has cleared, or manually; a withheld e-mail is never resolved automatically. The status watchdog deletes resolved rows 180 days after resolution (site_settings.retention_urgent_days, configurable in the admin area, at least 7 days).

6. Retention — what the daily purge covers, and what it does not

There is an automatic deletion, and since 17 Aug 2026 it covers all tables holding personal data, either through a period of its own or through the session's deletion chain. Its core is in srv/db_worker.php in the block marked DAILY DATA PRUNING and runs inside the permanently running worker process once per calendar day (controlled by $lastPurgeDate). The periods for bot sessions (hourly), the final deletion of archived entries (every 30 seconds) and the deletion requests from the self-service disclosure have their own cycles in the same process. Since 7 Oct 2026 scheduled tasks run from /etc/cron.d/mouseplayer via srv/cron_run.sh with one lock per task, among them moving events into the archive (hourly) and maintaining the text index (every ten minutes); the daily aggregate of the click map is built nightly.

Evidence that it runs (as of 16 Oct 2026): the worker's log records the daily run every day, most recently on 16 Oct 2026 at 00:00:11, completing ten seconds later, and the hourly period for bot sessions with deleted sessions and rows (for example at 10:00:18 the same day 478 sessions with 3294 rows). Up to that day the tier period had nothing to delete: the projects with older sessions are on the enterprise tier (365 days), and the oldest sessions on the free tier are younger than its period. Whether a scheduled run fails or does not take place is checked by the cron watchdog in srv/cron_status.php, which reports it in Urgent Logs (B.8) and by e-mail.

6.1 Covered by the daily purge

TableTrigger and periodEvidence
mouseplayer_logsOlder than 60 days (site_settings.log_retention_days)srv/db_worker.php, DAILY DATA PRUNING
security_access_logsOlder than 72 hours (site_settings.security_log_retention_hours)srv/db_worker.php, DAILY DATA PRUNING
banned_ipsexpires_at lies in the past; entries without expires_at remain until lifted by the operatorsrv/db_worker.php, DAILY DATA PRUNING
visitor_sessions and everything attached to it (mouse_events, session_pageviews, session_custom_events with parameters and the other tables of the deletion chain)Per session, once the tier period has expired: free 7 days, small 30, business 90, enterprise IFNULL(enterprise_custom_tiers.retention_days, 365). Special case free + is_suspicious = 24 hours. Grace period after a downgrade via GREATEST(...) with users.retention_grace_until and retention_grace_days. Deleted via mp_sitzungen_loeschen().srv/db_worker.php, DAILY DATA PRUNING
payload_blob and mouse_events of bot sessionsThe recording of a session recognised as a bot is cleared once the period has passed (site_settings.retention_bot_recording_days, period in days: 1; 0 switches it off)srv/db_worker.php, hourly
visitor_sessions of bot sessions with everything attached to them14 days after the start of the session (site_settings.retention_bot_session_days, at least 8; 0 = no separate period)srv/db_worker.php, hourly, mp_sitzungen_loeschen()
mouse_events → archive filesmoved into compressed archive files per session after 7 days; the files are deleted with their sessionsrv/cron_archiv.php (hourly), mp_sitzungen_loeschen()

6.2 What has additionally been covered since 17 Aug 2026

This section used to list thirteen tables with no period at all. That is settled. Five tables have been part of the shared deletion chain mp_sitzungen_loeschen() since 12 Aug 2026, six more were given a period of their own on 17 Aug 2026, and two were not on the list at all: the per-visitor, per-day scoring and the texts generated from it.

The figures in the table below are not transcriptions. They are read from site_settings when this page is served — from the same row the purge reads — and are configurable in the admin area. A transcribed number would be a second place for the same truth, and the two would drift apart eventually.

TablePeriodWhat enforces it
ai_session_featureswith the sessionmp_sitzungen_loeschen()
ai_timelineswith the sessionmp_sitzungen_loeschen()
session_commentswith the sessionmp_sitzungen_loeschen()
session_shareswith the session; the link itself expires after at most 90 days, 7 by default; the row of an expired or revoked link is deleted 90 days later, and the accesses in share_views go with itmp_sitzungen_loeschen(), mp_share_erstellen(), daily run
visitor_nicknameswith the visitor's last session — since 28.08.2026 this also covers the name assigned by the controller (A.13), which sits in the same rowmp_sitzungen_loeschen()
ai_visitor_value60 days from the day scored; for Enterprise, the retention of the tier. In addition with the visitor as soon as no session of theirs is leftdaily run, mp_sitzungen_loeschen()
ai_insights (scope “visitor”)likewisedaily run, mp_sitzungen_loeschen()
consent_events730 days from the withdrawal — never while a session still refers to itdaily run
ip_lifetime_stats365 days from first_seendaily run
audit_logs60 daysdaily run
support_requests730 daysdaily run
consent_region_acceptances730 daysdaily run
legal_acceptances730 days after the acceptance or notice; the most recent acceptance of an existing account is kept for as long as the account exists (since 16 Oct 2026)daily run
user_ip_history90 days from the last time the address was seen; at the latest with the accountdaily run
users (including last_ip), workspace_users, workspace_inviteswith the account; without a sign-in after 24 months plus a warning period of 30 daysmp_konto_loeschung_ausfuehren(), daily run
heatmap_page_statsnot applicable — the table was removed on 18 Aug 2026 (migration 045)—
mp_profil_sitzung180 days from the START of the session, at least the computation window; without any period at all as soon as the session itself is gonedaily run, mp_sitzungen_loeschen()
mp_profile, mp_sequenzmustermp_profile as soon as no assignment is left for it; mp_sequenzmuster after 180 days without a recomputation, and at once when its channel has no session leftdaily run
ga4_daily, ga4_sessions, ga4_events180 days from the day described, respectively from the start of the GA4 session — not from the moment of import (A.14)daily run
mp_hm_tag, mp_hm_tag_standno period of its own: a stored day is recomputed from the sessions that still exist at the latest 7 days after its last computation, the next time the list is opened; days before the oldest remaining session of the project are removed on that occasion; everything together with the project (A.8)on opening the heatmap list; mp_konto_loeschen()
mp_umfrage_textwith the session (A.21) — deliberately no period of its ownmp_sitzungen_loeschen(), the player’s deletion path, mp_konto_loeschen()
mp_loesch_archiv, mp_loesch_archiv_sitzungarchived accounts, workspaces, projects and archived visitors or IP addresses (GDPR erasure): at most 30 days, then erased permanently. What remains is a record without a name – for visitors and IP addresses an HMAC of the identifier, so that the erasure can be repeated after a backup is restored, for accounts no identifier at all – which is removed after 730 daysdb_worker, every 30 seconds
mp_hm_klick_tag, mp_hm_ansicht_tag730 days from the day the row describes; immediately with the project. No deletion per person: the rows carry no personal reference (A.1)srv/hm_aggregat_lauf.php, mp_konto_loeschen()
mouse_events_analytikwith the page view – since 7 Oct 2026 also on every path that deletes individual sessions; before that only together with the whole project (A.8)mp_sitzungen_loeschen(), the player’s deletion path, mp_konto_loeschen()
login_sessions90 days from the last activity; entirely with the account (B.7)daily run, account deletion
share_viewswith the share link, via the foreign key with ON DELETE CASCADE (B.6)foreign key
ai_churnrewritten per project on every run; deleted with the sessions from which the row is derived (A.16)srv/ai_worker.php
ai_besucher_bericht180 days from the request; earlier with the visitor's last session (A.17)follow-up run in srv/ai_worker.php
mp_cwv, mp_element_klicks, mp_element_offenwith the session or page view; rows whose page view is missing are removed hourly (A.7, A.20)mp_sitzungen_loeschen(), srv/cwv_nachtragen.php, srv/element_worker.php
mp_text_block, mp_text_pv, mp_text_stand30 days from the start of the page view; earlier with the session (A.22)srv/textsuche_lauf.php, mp_sitzungen_loeschen()
bot_review_labelsdefault: no automatic deletion; configurable in years (retention_bot_labels_years) (A.19)srv/cron_status.php
urgent_logs180 days after resolution (site_settings.retention_urgent_days) (B.8)srv/cron_status.php
mp_aufnahme_quittung7 days (A.1)srv/db_worker.php
mp_native_apps, mp_native_sessionsuntil the period set per session expires or until revocation; the preview is switched off and exists only on the development instance (A.1)cfg/native_store.php

Two points that were measured rather than assumed. First, the daily run really does run. This document said at one point that it never had; the run of 17 Aug 2026 at 17:37:09 is on record in the worker log. It deleted nothing that day because both projects holding sessions are on the Enterprise tier and the oldest session was 27 days old — that is the intended state, not a failure. Second, the period for the derived scoring is anchored to the day the row describes, not to when it was computed. Measured, 167 rows carried 28 distinct described days but only 6 distinct computation times, the most recent a few minutes earlier: the precomputation recomputes, and every recomputation would have reset a period hanging off it. It would never have expired, and nobody would have noticed.

6.3 Findings on the deletion chain (as of 16 Oct 2026)

  • THE CHAIN RUNS IN OPERATION. On 10 Aug 2026 it had never run, which is why it was described here as untested. Since 17 Sep 2026 the period for bot sessions deletes hourly through the same deletion chain mp_sitzungen_loeschen() that is also used by the tier period and by deletion requests; on 16 Oct 2026, for example, 401 and 478 sessions per run. The tier period itself had nothing to delete up to that day (section 6).
  • DELETED PROJECTS. The selection of the tier period still joins visitor_sessions by INNER JOIN with project, workspace and owner and does not see sessions without a project. In the regular process, however, such a session no longer arises: since 7 Oct 2026 a project is archived first and, on final deletion, removed with all its sessions through the channel deletion (mp_kanaele_loeschen()). As of 16 Oct 2026 there is no session without a project.
  • ORPHANS. Whatever is derived from a session is part of the deletion chain or has a period of its own (section 6.2). Page views whose session is missing — for example after an interruption in the middle of a deletion — are counted once a day by the status watchdog and removed automatically together with their events once they are older than ten minutes.
  • THE PURGE DEPENDS ON THE WORKER RUNNING. A stopped worker also stops the deletion. Since 19 Aug 2026 the watchdogs report failures by e-mail, since 23 Sep 2026 this mail actually goes out via SMTP, and since 2 Oct 2026 a separate machine of our own in the same local network checks every minute from outside whether database, Redis, WebSocket server, permanent processes and queue are in order. It remains to be noted that ActiveState=active is no proof that the worker is alive; the process list is reliable.

Manual deletion paths, for completeness: the player (delete_session and delete_visitor, limited to the channelid, roles owner and admin) and mp_sitzungen_loeschen() in srv/db_worker.php. A withdrawal deletes only where projects.consent_delete_on_withdraw is set; as of 16 Oct 2026 the value is set for no project. There is no ON DELETE CASCADE from visitor_sessions downwards: the chain runs by hand across all dependent tables, and the stumbling block is mouse_events, because the events hang off pageview_id rather than session_id; since 7 Oct 2026 it also takes the click copy mouse_events_analytik with it.

Outside the database: the access logs of nginx and Apache contain full IP addresses and are kept across 14 daily rotations (logrotate). The development instance's Redis listens only on 127.0.0.1 (port 6381), is access-restricted and writes its data to disk for persistence (AOF and RDB snapshots); the queues hold the truncated IP, the full URL and the DOM payload until the worker collects them, and mp_autoban: holds the full address of a blocked sender for the duration of the block (24 h). The archive files of the moved-out events lie under /mnt/mouseplayer-dev-archive on the same disk as the system and have been included in the backup since 6 Oct 2026.

7. Automated decision-making, profiling and scoring

Scoring and classification take place. No decision producing legal effects concerning a VISITOR, or similarly significantly affecting a visitor, was found — no credit, pricing or access decision, and no disclosure of the assessments to anyone other than the customer. On the evidence, Art. 22 is not engaged in respect of visitors. Three automated consequences do exist, and two of them deserve a closer look than a record usually gives. The third was added on 2 September 2026 and is the only one that acts back on the VISITOR: the exit prediction (A.15) or the churn indicator (A.16) can lead to an overlay on the controller's website. It appears only if he switches it on, its content is his own, and it can be dismissed. No legal effect or similarly significant impact within the meaning of Art. 22(1) GDPR follows from that — it is a prompt, not a ruling on price, access or creditworthiness. It still belongs on the record, because here an assessment leaves the customer panel for the first time.

MechanismWhat it doesWhere
Engagement scoreA small integer built from points for depth (duration, page count, scroll depth) and conspicuousness (rage and dead clicks, back-jumps, parallel tabs). Bots and trivial sessions score 0. Stored in ai_session_features.engagement_score (TINYINT UNSIGNED), index idx_engagement. Effect: ordering of the recommendation list in the customer panel.ai/features.py:33-70; cfg/project_ai_overview.php:126-136 and 150; cfg/project_live_users.php:190
Session qualitySeparates sessions where almost nothing happened.visitor_sessions.session_quality; srv/db_worker.php:706-780 and 1103-1140
Bot classificationSets visitor_sessions.is_bot and bot_reason (varchar 32), index idx_is_bot. Note that the bot check in mp.js runs BEFORE the consent gate.srv/db_worker.php:963-1000
Frustration detection in the browserGenerates rage_click, dead_click and mouse_thrashing. Measured: dead_click 355, rage_click 52, mouse_thrashing 5. On rage_click the script additionally passes a flag to third-party chat widgets present in the VISITOR'S browser — Intercom('update', {'mouseplayer_rage_click':'DETECTED_NOW'}) and LiveChatWidget.call('set_session_variables', ...). This is an automated onward disclosure of a behavioural judgement to a third system on the customer's page.www/mp.js:5786 and 5793-5794
Session text logMachine-generated prose about the course of the session, produced by ai/build.py. Since 12 Aug 2026 srv/ai_worker.php invokes the build itself (with working directory and PYTHONPATH); as of 16 Oct 2026 it runs, and the most recent timeline dates from the same day. The error messages in ai_build.log stem from the earlier, since removed invocation and were last written on 18 Aug 2026.ai/build.py; srv/ai_worker.php; ai_timelines
is_suspicious at registrationSet automatically at registration, using device_id and the normalised e-mail among other signals. Consequence: for the free tier the retention period is cut to 24 hours.www/register.php:159-191; srv/db_worker.php:824
Automatic blockMore than 15 connections within 10 seconds, more than 15 simultaneous connections from one IP, more than 50 messages per second, and messages that are too large (except a large page snapshot) or unreadable lead to a block of 24 h in the WebSocket server (site_settings.autoban_stunden, 1 to 168), held in Redis (mp_autoban:) and logged without the full address. In front of the web server, fail2ban blocks an address for one hour if it hits the nginx rate limit more than 100 times within one minute (jail mp-nginx-429). According to the code as of 16 Oct 2026, entries in banned_ips are now set only by the operator; earlier automatic entries expired after 7 days.class/Server.class.php (autoBanIp); /etc/fail2ban/jail.d/mp-nginx-429.conf; /etc/nginx/conf.d/mouseplayer.conf
Behaviour profiles — grouping of visits (A.12)k-means over seven characteristics per session assigns every watchable visit to one of at most eight groups and names it from the relative position of the group centre. Trivial sessions and those recognised as bots are separated beforehand by a rule and are not clustered at all. Effect: a DISPLAY — a tab, a filter pill and a play queue. No decision hangs on it.ai/profile.py; cfg/project_profiles.php; mp_profil_sitzung
Friction score and anomaliesPer session a value from 0 to 100 derived from weighted frustration and error signals together with reasons (since 26 Sep 2026); it sorts and filters lists and can trigger a webhook subscribed to by the customer when a threshold is reached. Once a day the previous day of each project is compared with the preceding weeks (sessions, goal rate, share with friction or JavaScript errors, Web Vitals) and an anomaly is reported — counts across the project, not an assessment of individual visitors. No decision concerning the visitor depends on it.cfg/reibung_lib.php; cfg/anomalie_lib.php; visitor_sessions.friction_score; mp_anomalie

The automatic block is the only mechanism here that denies access to a service without human involvement, and it can hit a customer user, not only a visitor. Whether that reaches the threshold of Art. 22(1) is not decided by this record. Assessed on 17 Aug 2026, updated on 16 Oct 2026: the block in the WebSocket server expires by itself after 24 h, the fail2ban block after one hour; the earlier automatic block in banned_ips expired after 7 days. An objection goes through the support form or to office@mouseplayer.com and is handled by a human; the operator can lift a block early. The process thus has a limited effect, human review on request and a named route to it — the features on which Art. 22 turns. The classification itself remains a legal question; what has been decided is the safeguard, not the interpretation. (Decision of the controller, 17 Aug 2026)

The grouping in A.12 is profiling within the meaning of Art. 4(4) GDPR: behavioural data are evaluated automatically in order to assess aspects of behaviour. Art. 22(1) requires more than that — a DECISION producing legal effects or similarly significantly affecting the person. None was found, checked along three lines. Group membership is written by ai/profile.py and read on exactly four paths: the reader functions in cfg/lib.php, the tab cfg/project_profiles.php, the player www/player.php and the sentence generator cfg/ki_lib.php — and nowhere else. It enters none of the bridges under 4.1: a search for profil_key across cfg/, srv/, www/ and class/ returns neither www/mp.js nor class/Server.class.php, so it does not reach the visitor's browser either. And it changes neither the retention period nor the recording nor anything the visitor encounters on the customer's site. What is grouped, moreover, are VISITS and not people: a profile per person would presuppose the customer identifier from setUserId, and 0 of 4226 sessions carry one. What remains are Art. 13(1)(c) and Art. 30 — the information duty and this entry. Both are met. The classification itself stays a question of law; what is settled is the measured state of affairs, not its interpretation. (Assessed 27.08.2026.)

8. Technical and organisational measures (Art. 32) — general description

This section is the general description required by Art. 30(1)(g) and 30(2)(d). Each measure below was verified in code or configuration; the file and line are given so a reader can check rather than trust. The gaps at the end of this section are equally verified and are stated in the same breath, because a TOM description that lists only what exists is an advertisement.

AreaMeasure in forceEvidence
Transport encryptionConnections from the internet are TLS-encrypted: TLS terminates in nginx (192.168.20.2:443), and every HTTP request is redirected to HTTPS. The WebSocket server listens only on 127.0.0.1:8081 without TLS of its own and can be reached exclusively through nginx (path /mp_wss/); likewise the HTTP tunnel /mp_wst, the upload of large page snapshots /mp_dom and the measuring point /mp_wsfail run over HTTPS. Within our own local network at the same site, database and Redis connections run unencrypted (see 8.1)./etc/nginx/conf.d/mouseplayer.conf; srv/server.php (MP_WSS_BIND, MP_WSS_TLS); www/.htaccess
Data minimisation at collection — IPThe visitor's IP address is truncated immediately after it is determined and therefore before any consumer (last IPv4 octet, last IPv6 group). On 10 Aug 2026, 236 legacy rows still held full addresses; as of 16 Oct 2026 no row in visitor_sessions or ip_lifetime_stats holds an untruncated address.class/Server.class.php (rule B)
Data minimisation at capture — form inputMasking with a hard floor: at level 0 every character is replaced; regardless of level, passwords and payment data are always replaced (categories password and payment, types password/email/tel/hidden, class and name patterns card|cc|cvv|cvc|password|credit|social|pesel|nip). All 7 projects run at smart_masking=0.www/mp.js:4818-4853
Data minimisation at capture — DOMInside data-mp-mask or .mp-masked, every non-whitespace character is replaced by '*' and all attributes outside MASK_KEEP_ATTRS are set to '[FILTERED]'. Applies to running mutations as well.www/mp.js:3934-3970 and 4732-4768
Data minimisation at capture — field namesThe raw name or id of an input field is not transmitted; only the classified category is sent, because on many shops the raw name is descriptive enough to identify the person.www/mp.js:4653-4674
Consent enforcementPath exclusion list evaluated BEFORE the consent gate, matching on the path rather than the full URL (71 blocked connections). Server-side enforcement as a second net: only GRANTED, GRANTED_LATE or an explicit regional exemption pass; WITHDRAWN, unknown country and anything unclear lead to close(1008) (24 blocked connections). Kill switch site_settings.consent_gate_active=1.class/Server.class.php:963-975 and 977-1004
Consent evidence minimisationThe TC string is not stored in clear text; only its SHA-256 is kept.visitor_sessions.tcf_hash CHAR(64); ai/migrations/018_consent.sql
Application securityCSRF tokens from random_bytes(32) compared with hash_equals(). Session cookie with httponly=true, samesite='Lax' and secure conditional on HTTPS. Passwords stored as hashes and verified with password_verify(). Prepared statements throughout the audited write paths. Login rate limiting.cfg/lib.php:118-132 and 110-112; www/login.php:60 and 28
Abuse preventionAutomatic block and connection limit in the WebSocket (Redis counter with 10 s expiry; 15 simultaneous connections per IP; block of 24 h in Redis, truncated in the log). Rate limiting and connection caps per address in nginx for pages and WebSocket handshakes. fail2ban with the jail mp-nginx-429: whoever is rejected more than 100 times within one minute is blocked for HTTP and HTTPS for one hour.class/Server.class.php; /etc/nginx/conf.d/mouseplayer.conf; /etc/fail2ban/jail.d/mp-nginx-429.conf
Access controlRole model per workspace (owner / admin / viewer). Deletion in the player is limited to owner and admin, and every deletion is additionally scoped to the channelid — described in the code as the actual security boundary.workspace_users.role; www/player.php:267 and 275-282
Change logginglog_audit() records user, workspace, action, target, details and IP. 666 entries, including create_share, revoke_share, delete_comment, player_delete_*, firewall_auto_ban (24) and superadmin_*.cfg/lib.php:2878-2890
Share linkschannelid and session_id come exclusively from the token; the remaining query string is REPLACED rather than ignored; user_role stays NULL so deletion and re-sharing fall away server-side, not only in the interface; all three endpoints restrict to the token's session; invented, expired and revoked tokens return the same response.www/share.php; www/.htaccess:77
API surfaceThe full replay payload is deliberately not part of the public API.www/api/v1_session_detail.php:25
Key managementThe Google service account key is stored encrypted in projects.bigquery_sa_json and decrypted through decrypt_sa_key() with BIGQUERY_SA_AES_KEY from .env; the interface names AES-256.srv/cron_integration_bigquery.php:57; cfg/lib.php:993
Secrets outside version controlConfirmed on the current index: .gitignore lists .env, and git ls-files finds only .env.example.git ls-files
Network exposureOnly ports 80 and 443 via nginx are reachable from the internet; there is no port forwarding for the database or Redis. The development instance's Redis is bound only to 127.0.0.1 (port 6381); an older, shared Redis instance (port 6379) additionally listens in the local network. MariaDB listens on 127.0.0.1 and in the local network. Database and Redis are access-restricted and reachable only from our own local network. An additional access restriction sits in front of www.mouseplayer.com.ss -ltn; Redis configuration; www/.htaccess

8.1 Measures that are demonstrably NOT in place

  • No encryption at rest. innodb_encrypt_tables, innodb_encrypt_log and innodb_encrypt_temporary_tables are all OFF, and there is no encrypted volume (lsblk shows sda1 without crypt).
  • No TLS on the database connections. have_ssl=YES, but ssl_cert, ssl_key and ssl_ca are empty. MariaDB listens on 127.0.0.1 and in the local network (192.168.20.2:3306); connections from the local network therefore run unencrypted. They are access-restricted and cannot be reached from the internet (no port forwarding).
  • No transport encryption for Redis. Connections to Redis run unencrypted over the local interface or in the local network; both instances are access-restricted. The development instance writes its data to disk for persistence (AOF), including the queues with truncated IP, full URL and DOM payload and, for the duration of the block (24 h), the full address of blocked senders.
  • NO LOG OF WHO VIEWED WHICH RECORDING. audit_logs records only write actions; a call to player.php, ajax_get_events.php or player_theatre.php is not logged at all. It is therefore impossible to answer, after the fact, who watched a given session.
  • Apache access logs contain full IP addresses, kept for 14 days.
  • The archive files of the moved-out recording events lie on the same disk as the system and the database. A failure of this disk affects both; only the encrypted copy of the backup, which has contained the archive since 6 Oct 2026, provides storage at another location.
  • All limits against overload — nginx, fail2ban and the block in the WebSocket server — count per address. Nothing placed in front of the machine helps against a distributed attack from many addresses: no CDN and no filtering service is placed in front.
  • Physical security: Access is held solely by the controller. The room is locked by key and under video surveillance; no other persons — including landlords or cleaning services — have access (statement of the controller, 17 Aug 2026). The precise address is deliberately not stated here.
  • Monitoring and recovery: since 19 Aug 2026 the watchdogs in srv/cron_status.php report faults, low disk space, missing backups and failed scheduled runs in Urgent Logs (B.8) and by e-mail; since 23 Sep 2026 this mail actually goes out via SMTP. Since 2 Oct 2026 a separate machine of our own in the same local network checks database, Redis, WebSocket server, permanent processes and queue every minute through a health endpoint (www/health.php) and reports a failure; it cannot see a failure of the internet connection itself. Target recovery time: at most 24 hours. Restore tests from the off-site copy were carried out on 5/6 Sep 2026 and on 7 Oct 2026 (complete restore of the data); they are intended monthly (statement of the controller, 17 Aug 2026). The USB backup disk sits in the same room as the server — a single event at this location therefore affects both; only the encrypted copy provides storage off-site. The two key files are kept printed in a safe to which only the controller has access, and thus survive the loss of the machine.

9. Data protection officer — assessment under Art. 37 GDPR

RESULT: no obligation to designate a data protection officer is demonstrated on the figures available today — but the assessment turns on a single variable, that variable moves in only one direction, and until 19 August 2026 nothing watched it. It is now measured daily (see Consequence 2). The answer is "not yet", not "no".

Art. 37(1) requires designation in three cases. Case (a), a public authority or body, does not apply. Case (c), core activities consisting of large-scale processing of special categories under Art. 9 or of criminal-conviction data under Art. 10, is not met: no special-category data is processed intentionally. That is not the end of the point — as noted in A.2, the DOM snapshot stores whatever the customer's page renders, so special-category data can enter a recording incidentally on a health or finance site. Incidental capture is not a core activity consisting of such processing, so case (c) is not triggered; but the exposure is real and belongs in customer onboarding rather than being written away here.

Case (b) is the one that matters, and it has two limbs. The first limb — core activities consisting of processing that requires REGULAR AND SYSTEMATIC MONITORING of data subjects — is met without argument. Session replay is continuous, automated observation of individual behaviour; it is not ancillary to the business, it IS the business. Anyone reading this record who is tempted to argue the first limb should stop: it is met, and arguing it wastes the credibility needed for the second.

The second limb is LARGE SCALE, and this is where the answer currently comes out negative. The Article 29 Working Party guidance weighs four factors: the number of data subjects, the volume and range of data, the duration or permanence of the processing, and the geographical extent. Measured on 10 Aug 2026: 706 visitor sessions, 329 distinct visitor records, 7 projects, 4 customer accounts. On the number of data subjects, that is nowhere near large scale — and these are development-instance figures at that. As of 16 Oct 2026: 3514 distinct visitor IDs classified as human in the last 30 days across all projects, still far below the threshold in consequence 2.

Two of the four factors nevertheless point the other way, and the record should say so rather than lean on the comfortable one. The RANGE of data per person is exceptionally broad: full page HTML including whatever the page displays, keystroke-level form input, movement and click behaviour, derived behavioural scoring, and a prose narrative of the session. The DURATION is long: depending on the tier, recordings remain for up to 365 days and in the backup for up to 364 days beyond that, even though deletion periods have existed and been applied for all tables since August 2026 (section 6). Only the headcount keeps the assessment below the threshold, and the headcount is precisely the quantity MousePlayer does not control: it is set by how much traffic each customer's website receives. A single mid-sized e-commerce customer could carry the product over the line on its own, without any decision being taken here.

  • Consequence 1 — write the assessment down with its date. Art. 5(2) makes accountability the obligation; a negative assessment that was never recorded is indistinguishable from one that was never made.
  • Consequence 2 — define the trigger that reopens it. The sensible trigger is the number of visitors monitored per month across all customers, plus a review on each new customer of significant size. Threshold and cadence: The Art. 37 assessment is repeated as soon as more than 100000 end users per month are observed across all Customers, and in any event annually. Responsible: Lukas Wójcik, owner. (decision of the controller, 17 Aug 2026) Implemented on 19 August 2026: the number of distinct end users observed in the last 30 days is counted once a day by the watcher process and held against this threshold, and against the date of the last recorded assessment; exceeding either sends a warning by e-mail. Threshold and date are settings, read from the same row that this sentence quotes, so the document and the measurement cannot drift apart. A trigger nobody measures is a note, not a control – that was the gap this paragraph described and no longer describes.
  • Consequence 3 — check national law. Art. 37(4) allows Union or Member State law to require designation in further cases. to be completed: confirmation from Polish counsel whether Polish law requires a data protection officer in further cases beyond Art. 37(1)
  • Consequence 4 — voluntary designation remains open. If a DPO is designated voluntarily, Art. 37 to 39 apply in full, including the publication and notification of the contact details. That is a decision, not a default.
  • Consequence 5 — separate but adjacent: a data protection impact assessment under Art. 35 is very likely required of each CUSTOMER as controller, given systematic monitoring combined with evaluation and scoring. As processor, MousePlayer must assist under Art. 28(3)(f). A product-level impact assessment exists - it is the one published under “Data Protection Impact Assessment”. A separate copy is not handed to Customers (decision of the controller, 17 Aug 2026). Support under Art. 28(3)(f) is given on request and rests on the publicly available documents: this record, the published impact assessment, the technical and organisational measures and the sub-processor list.

10. Known gaps

Everything below is proven, not suspected. This section exists because a record of processing activities is a working instrument: the point is to be able to see the open items, not to present a clean sheet. Items are ordered by how badly they would read in an audit, not by how hard they are to fix.

GapWhat is provenWhy it matters
Full IP addresses in logs of our own applicationAs of 16 Oct 2026 the legacy data in visitor_sessions and ip_lifetime_stats is truncated (on 10 Aug 2026, 236 and 291 untruncated rows respectively). mouseplayer_logs, audit_logs, user_ip_history, login_sessions, share_views and support_requests (part B) as well as the access logs of nginx and Apache deliberately remain untruncatedFor part B the full address is justified (B.2 to B.7) and limited by periods. It is listed here nonetheless because these tables go into every backup.
Legacy data with clear-text form inputOn 10 Aug 2026, 173 rows in mouse_events with event_type='input' contained unmasked values from 2026-07-21 to 2026-07-27, including personal names. The rows were deleted on 17 Aug 2026, before the archive files were introduced on 18 Aug 2026; as of 16 Oct 2026 none of them is in mouse_eventsCurrent rows are masked, so the control works. The old values cannot be in the archive or in today's backup; no backup from before 17 Aug 2026 is known, but its absence has not been verified in detail.
Share links bypass login entirely/s/<40-hex-token> leads directly to player.php?share=<token>; the token is the entire barrierThe link can be passed on to anyone, which is why the recipient category for A.1, A.2, A.3 and A.9 is in fact open. This is limited by the maximum lifetime (7 days by default, at most 90), revocation at any time and the access log (B.6).
No log of who viewed a recordingaudit_logs records only write actions; calls to player.php, ajax_get_events.php and player_theatre.php are not loggedAfter an incident it is impossible to establish who watched which session. This is the single measure whose absence is hardest to explain to a supervisory authority.
The entire server is copied off-site every daysrv/backup-crypt.sh backs up all databases and the entire web tree including every .env every day, and since 6 Oct 2026 also the archive of recording events; the copy is encrypted on our own server before transfer (rclone crypt) and since 5 Sep 2026 stored in a bucket at Google Cloud Storage in Warsaw, covered by the Google Cloud data processing terms (section 4)What leaves the premises every day is not just a copy of the session recordings but the complete server, with the production secrets of several systems. Encryption takes the content away from Google, not the scope. If both key files are lost, the backup is irrecoverable; they are therefore additionally kept printed in the safe (section 8.1).
Whether the backup arrives is monitoredUnproven until 11 Aug 2026. Since then the run writes a log and verifies at the destination; since 19 Aug 2026 a watchdog checks the last successful run against a deadline and reports a missing run in Urgent Logs and by e-mailThe gap in this row is closed. It remains listed so that it is traceable that the backup ran without proof until 11 Aug 2026.
Old backup scriptsThe old backup scripts were removed on 16 Oct 2026. Neither was still being executed; since 11 Aug 2026 the daily backup runs via srv/backup-crypt.shThe gap in this row is closed. It remains listed so that it can be traced that until 11 Aug 2026 whoever obtained the SSH account pi had root at 02:00.
No encryption at rest, no TLS on database and Redis connectionsinnodb_encrypt_* all OFF, no encrypted volume; ssl_cert/ssl_key/ssl_ca empty, MariaDB also reachable in the local network; Redis access-restricted but unencrypted and with persistence on disk (AOF)The recordings — including the DOM payload in the Redis queue and the archive files — are readable by anyone with file-system access; in the local network database connections run in clear text. None of these paths can be reached from the internet.
Some of the consent records are test dataOn 10 Aug 2026, 671 of 706 sessions carried consent_source='testdata' with consent_ts IS NULL, set by ai/devdata/001_altsitzungen_markieren.sql; as of 16 Oct 2026 there are still 566 such sessionsFor these sessions the consent log cannot serve as proof. This is an artefact of the development system, but the same query in production would need the same check.
Withdrawal deletes nothingAs of 16 Oct 2026 projects.consent_delete_on_withdraw is set for no project (evaluated in the consent_withdrawn branch in srv/db_worker.php)Withdrawal stops further recording — as Art. 7(3) requires — but a customer who assumes it also deletes the earlier recording is mistaken. This distinction must be stated in the customer-facing documentation.
Everything here is measured on the development instanceAPP_ENV=dev; the test project mpconsenttest01 and the s_mptest_ fixtures are included in the countsThe structure of this record transfers to production; the numbers do not. Production must be measured before this record is relied on externally.
The search for outbound calls was not exhaustivecfg/, srv/, class/ and www/ were searched for curl_init, curl_exec, file_get_contents with http, fsockopen, stream_context_create and all https literals; vendor directories were not searchedThe vendor trees hold the endpoints of Stripe, Google Cloud, PHPMailer and Symfony. Those services are named in section 4, but the statement 'no other outbound call exists' is bounded by the search.
No per-project switch for the behaviour profilesprofile_aktiv is a global operator setting. mp_ki_module_katalog() in cfg/lib.php lists nine selectable AI sections and none of them is the profiles, so projects.ki_module does not cover this tab; the navigation entry in www/project.php is unconditional. Measured 27.08.2026.A customer who does not want their visitors' visits grouped cannot switch it off. He controls the recording; the derivation from it he does not. Every other derived section can be deselected per project — this one is the exception. The retention gap that stood here until 27.08.2026 is closed: mp_profile and mp_sequenzmuster now have periods of their own, see 6.2.
An erasure request reaches the imported GA4 sessions only where the analytics bridge is onAs of 01.09.2026 mp_sitzungen_loeschen() takes ga4_sessions and ga4_events along on an Art. 17 request, linked through ga4_sessions.user_pseudo_id = visitor_sessions.custom_user_id and bound to the channel. That link only exists where the customer has switched on the GA4 analytics bridge — it is what writes the client ID from the _ga cookie there. If they instead fill custom_user_id through setUserId() with an identifier of their own, there is no path at all from a visitor to their imported row. The case is carried as its own test (test_ga4_import.php, section J) so that it is not forgotten.For those customers the imported row stays after an erasure request until the period from A.14 clears it. It is pseudonymous and not attributable by us — which is precisely why it cannot be deleted in a targeted way. A deletion path through the GA4 property itself is a matter for the controller who runs it.
Consent for imported rows cannot be evidenced, only inferredWhat is imported is what carries a user_pseudo_id; cookieless hits under denied consent carry none and drop out (A.14). There is no record for these rows comparable to consent_events — GA4 provides no consent attribute in the export that could be stored here.For its own recordings MousePlayer can show per session what they rest on; for an imported session it cannot. The bar is mechanical and it works, but it evidences only that a cookie was set — not what the visitor agreed to. Responsibility for that lies with the controller, who governs the collection at Google.
The custom visitor name is unchecked free textA.13, since 28.08.2026. mp_besucher_label_setzen() truncates to 64 characters, strips line breaks and verifies that the visitor belongs to the project — nothing checks the CONTENT, and there is no specification of what kind it should be.The controller can write special categories under Art. 9 into it („Mr X, wheelchair“) without any barrier or warning. Compliance is their liability, not the processor's — but a processor who offers a free-text field and says nothing about it has created the opportunity. A technical check is not possible: a name cannot be told apart from a health detail by machine. What would be possible is a note at the field.
Bot follow-up examples without a deletion periodOne pseudonymous feature vector per decided session (bot_review_labels, A.19) remains after the session for as long as no period is set. The default of retention_bot_labels_years is 0 (unlimited); the operator can set it in years.They are not visitor content, but they carry the session identifier. Whether to set a period is the operator's decision.
The free text of a survey is only roughly filteredA.21, since 27.09.2026. mp_umfrage_text_filtern() replaces e-mail addresses, IP addresses and phone numbers, in the browser and again in the worker. It does not recognise names, postal addresses, customer numbers or health information.A visitor can write anything into the follow-up, including special categories under Art. 9. The note below the field asks them not to; it is not a lock. As with the controller’s own visitor name (A.13), the content cannot be classified reliably by machine.

11. Maintenance of this record

Art. 30(1) and 30(2) require the record to be kept in writing and made available to the supervisory authority on request. That makes two things necessary that are not in the document itself: someone who owns it, and an interval at which it is checked against the system rather than against the previous version of itself.

  • Owner and review interval: Responsible: Lukas Wójcik, owner. Reviewed annually and on every change to the processing. The dated versions are kept in version control under ai/cms/. (decision of the controller, 17 Aug 2026)
  • Events that force an update regardless of the interval: a new sub-processor or a change of destination; a new table holding personal data; a change to the deletion chain mp_sitzungen_loeschen() or to the daily purge in srv/db_worker.php; activation of the BigQuery export or of webhooks for any customer; a change to the masking defaults; the first customer whose site renders special-category data.
  • Method: every figure in this record came from a measurement against code, schema or database on a stated date. A review that only re-reads the text has not reviewed anything — the numbers must be measured again, and the placeholders must be either filled or explicitly carried forward with a reason.
  • This record does not replace the sub-processor list, the technical and organisational measures document, the data processing agreement or the privacy notice. Where those documents and this record disagree, the discrepancy is a finding, not a formatting issue.
MousePlayer

The ultimate AI real-time session replay engine.

Įmonė
DPA (Data Processing Agreement) Etika ir duomenų privatumas Pagalba Abuse Prevention & Fraud Fighting Technical and Organizational Measures (TOM) Sub-processors Privacy Policy Imprint Access and Erasure: How to Identify Your Data Terms of Service Right of withdrawal EU AI Act: How MousePlayer Uses Artificial Intelligence EU Data Act: Switching, Portability and Access to Your Data Complaints Contract changelog
Funkcijos
Naudojimo atvejai Kainos Kiek kainuoja paspaudimas be atsako? Everything your legal team will ask for
Vadovai
Kaip įdiegti? GTM integracija ir CAPI Viešoji API ir API raktai JavaScript API Kaip tai veikia: technologija, raiškos ir ribos MousePlayer ir sutikimas Privacy Policy Text for Your Website
Paskyra
Prisijungti Išbandyti nemokamai! Mano duomenys Slapukų nuostatos
Integracijos
Google BigQuery jungtis GTM ir GA4 įvykių integracijos tiltas Intercom, LiveChat ir Zendesk valdikliai Sentry, Bugsnag ir Datadog RUM integracijos tiltas Zapier, Make.com ir gaunami Webhook PostHog, Mixpanel & Amplitude integracijos tiltas Konversijų API Kliento gyvavimo ciklas (serveryje) Pagalbos tarnyba ir užklausos HubSpot, Pipedrive ir Salesforce Užduočių sekimo sistemos (Jira, Linear, Asana, Trello, GitHub) Google Sheets ir SharePoint MCP serveris (AI agentai) A/B bandymai ir funkcijų vėliavėlės Žymų tvarkytuvės ir CDP El. pašto rinkodara (naršyklėje) Objektų saugykla (S3, R2, GCS, Azure) Apklausos ir kliento balsas Google Analytics 4 (importas)

© 2026 MousePlayer. All rights reserved.

Systems fully Operational
Other projects reactive.chat Live chat that watched the session. answely.com AI answers with sources for website search, hosted in the EU. formexposed.com Checks the spam protection of contact forms from the outside. form.exposed Free check: can bots spam your contact form? consented.eu Consent management for websites. Stats4U Visitor counter without an account, servers in EU. getSend Disposable addresses for one-off sign-ups. LW IT Solutions Data analytics and web development from Łódź.