Hopp til innholdet

DokumentasjonPersonvernMarkdown

Privacy: access, erasure, consent and bots

Tools for GDPR-style requests and consent, and for keeping bots out of the visitor list.

Right of access: export

Everything Snakkbar stores about one visitor, as JSON (VisitorExport): the visitor row (profile, attributes, live state, device, location), page views, conversations with every message, their translations and attachments (with signed links valid for 10 minutes), and callback requests made in those conversations, and the visitor's feedback (thumbs on answers, conversation ratings). Internal tokens and assistant bookkeeping are left out, and so are internal notes between staff (kind: "note") with files attached to them, and the staff-only rating transcript lines.

WhoRequest
Admin UIVisitor detail → Personvern → Eksporter data, or the visitor panel of a conversation
Admin operator (API)GET /operator/visitors/:id/export · SDK operatorClient.visitors.export(id)
Your backendGET /server/visitors/:externalId/export (secret API key; identified visitors)

Right to erasure

Slett alle data om denne besøkende (admin, with a typed confirmation) or the API deletes, in one transaction:

  • the visitor: profile, attributes, live state (current page, device, location), page views;
  • their conversations with all messages, translations, read markers and participants;
  • their images, also from storage (unsent uploads included);
  • callback requests made in those conversations;
  • knowledge suggestions drawn from those conversations that were not approved (approved ones are team knowledge and stay, without the link to the conversation);
  • outbox events and webhook deliveries that mention the visitor or their conversations, and rate-limit counters keyed by the visitor.

Then the assistant runtime (eve) is asked to clear its copy of each conversation (best effort; an expired session follows eve's own retention), staff views refresh, and a visitor.deleted event goes out ({ visitorId, externalId, by }), so webhooks can propagate the erasure to your CRM. This cannot be undone.

WhoRequest
Admin UIVisitor detail → Personvern, or a conversation's visitor panel
Admin operator (API)DELETE /operator/visitors/:id → 204 (404 if unknown) · SDK operatorClient.visitors.remove(id)
Your backendDELETE /server/visitors/:externalId → 204, also when unknown or already erased

Erasing does not touch the browser: an anonymous visitor's stored token simply stops working, and the SDK creates a new visitor next time. Data also leaves Snakkbar by itself after the tenant's retention period (Settings → Slett samtaler etter).

Tenant setting Vent på samtykke før sporing (TenantSettings.consent, "none" by default, or "required"; also in PublicConfig.consent). What it means, precisely:

Consent not given (pending or denied)Granted
Live tracking (heartbeats, page views, cursor while watched)Off. Nothing is sentOn (if live visitors is on)
Creating a visitorNot for tracking or for setUserAs needed
Opening the chat (a user action)Creates a visitor for the chat; the conversation works fully. Pages are still not trackedSame
setUser / data-user-*Held in the page; sent only once the visitor opens the chat (or consent is granted)Sent
Storage on the deviceNo visitor token until the visitor opens the chat; then the chat's own keys (visitor token, open conversation). A choice, once made, is rememberedSame

With "none", tracking runs by default, but an explicit denial always wins: a site that reports denied is never tracked, whatever the tenant setting.

Withdrawing (consent(false)) stops tracking at once, closes the chat, and forgets the anonymous visitor on this device (snakkbar:<tenant>:visitor and the open conversation). The conversations stay on the server until retention or erasure. Granting again later tracks as a new visitor. For identified visitors (token from your backend) the token is yours and is kept; tracking stops.

The choice is remembered per tenant in localStorage (snakkbar:<tenant>:consent = granted/denied). Consent is enforced in the browser (the SDK and widget); the server cannot know a visitor's consent.

Telling Snakkbar

WhereHow
Script tagdata-consent="granted", "denied" or "pending" (wait for a call, even when the tenant does not require consent). granted/denied are remembered like a call
Global APISnakkbar.consent(true | false) (queued until the widget has mounted)
mountWidgetmountWidget({ …, consent: "granted" | "denied" | "pending" }), later controller.consent(true | false)
SDKcreateVisitorClient({ …, consent }), client.setConsent(granted), client.getConsent() → "granted" | "denied" | "pending". startTracking() waits while consent is needed and starts by itself on setConsent(true)

A pending from the script tag or option never overrides a choice the visitor made earlier.

Most consent managers (CMPs) call you back when the visitor decides and again when they change their mind. Map the category that covers analytics/statistics to Snakkbar:

html
<script src="https://<host>/widget.js" data-tenant="your-tenant" data-consent="pending" defer></script>
<script>
  // Generic: call this from your CMP's "consent given/changed" callback.
  function snakkbarConsent(granted) {
    if (window.Snakkbar && window.Snakkbar.consent) window.Snakkbar.consent(granted);
    else {
      // The widget has not loaded yet: it reads the attribute when it starts.
      var tag = document.querySelector("script[data-tenant][src*='widget']");
      if (tag) tag.dataset.consent = granted ? "granted" : "denied";
    }
  }

  // Cookiebot
  window.addEventListener("CookiebotOnConsentReady", function () {
    snakkbarConsent(Boolean(window.Cookiebot && Cookiebot.consent.statistics));
  });

  // Google Consent Mode (gtag) users can call snakkbarConsent from the same place they call
  // gtag("consent", "update", { analytics_storage: "granted" | "denied" }).
</script>

Headless with the SDK:

ts
const client = createVisitorClient({ host, tenantId, consent: cmp.hasConsent("statistics") ? "granted" : "pending" });
const stop = client.startTracking();
cmp.onChange((categories) => client.setConsent(categories.includes("statistics")));

Bot protection

Obvious bots never become visitors. In the browser, the SDK does not start live tracking (and so creates no visitor) in automated browsers (navigator.webdriver) and for crawler/HTTP-library user agents (Googlebot, Bingbot, GPTBot, HeadlessChrome, curl, python-requests, link previewers, …). On the server, POST /visitors refuses the same user agents with 403 forbidden. The check only looks for clear signals, so unknown browsers and apps pass. The per-IP limit on creating visitors stays (120 per 10 minutes). The list is packages/sdk/src/bots.ts (isBotUserAgent, isAutomatedBrowser, exported from the SDK).

Note for test automation: headless Playwright/Puppeteer counts as a bot. Set a normal user agent and hide navigator.webdriver (see packages/widget/dev/visual.mjs) to test tracking.

Cloudflare Turnstile (optional). Set both environment variables on the deployment:

Variable
TURNSTILE_SITE_KEYPublic site key; sent to browsers in PublicConfig.turnstileSiteKey
TURNSTILE_SECRET_KEYSecret key; the server verifies tokens with Cloudflare

With both set, POST /visitors requires a token (turnstileToken in the body): 403 challenge_required without one, 403 challenge_failed for a rejected one. The SDK handles it: it loads https://challenges.cloudflare.com/turnstile/v0/api.js only then, solves the challenge invisibly (appearance: "interaction-only": a small box appears at the bottom left only if Cloudflare needs a click), and sends the token. Creating a visitor happens once per device, so returning visitors are not challenged again. If Cloudflare cannot be reached, visitors are let through (logged) rather than locked out. Sites with a Content Security Policy must allow https://challenges.cloudflare.com in script-src and frame-src. Use Cloudflare's widget mode Managed or Invisible. Clients without a browser (the SDK in Node) cannot solve the challenge; use identified visitors (POST /server/visitor-tokens) there, which never need it.