Privacy and data
Betterloop is built to answer "what should we fix?", which needs behaviour, not identity. This page is the factual description of what the SDK sends and what the server keeps, so you can check it against your own policy. It is a technical document, not legal advice.
What is stored, per event
| Field | Contents | Note |
|---|---|---|
| path | Pathname only, capped at 500 characters. Query strings and fragments are removed. | Common numeric, email, UUID and token-route identifiers become :id. See the limits below. |
| type | One of the nine event types. | |
| sid | A random 16-character session id generated in the browser. | Per tab, kept in sessionStorage under _fp_sid. Not a cookie, not stable across tabs or visits. |
| ts | Event timestamp, clamped to a window around server time. | |
| origin | location.origin of the page that produced the event. | Origin only, never the full URL. |
| referrer | Referring origin only, such as https://example.com. | No referring path, query, fragment or URL credentials. |
| vw, vh | Viewport width and height in pixels. | Used for layout and responsiveness signals. |
| ua | The User-Agent request header, truncated to 300 characters. | Read server-side from the request, not from JavaScript. |
| attrs | Per-event details: structural element descriptor, error category/source, vital name/value and sanitised custom attributes. | Page titles, click labels and detailed errors require captureText: true. Attributes are bounded and common sensitive fields are removed. |
What is never collected
- No cookies. The SDK sets none and reads none. The only storage it touches is one sessionStorage key.
- No IP addresses in the database. The client IP is used in memory as a rate-limiting key and is never written to an event row.
- No form or input values. Clicks record the shape of the element using tag names and sibling positions, up to four levels. IDs and class names are omitted. Clicks inside form controls, editable fields and marked private elements are ignored.
- No cross-site or cross-visit identity. There is no device fingerprinting, no user id, no persistent identifier. Close the tab and the session id is gone.
- No screen recording, no session replay, no keystrokes, no mouse paths. Betterloop records that a click happened on a described element, not how the pointer got there.
- No third-party trackers loaded by the SDK. It has zero dependencies and talks to exactly one endpoint - yours.
Do Not Track and Global Privacy Control
If navigator.doNotTrack is "1" or navigator.globalPrivacyControl is set, init() returns before anything happens: no listeners, no observers, no session id, no requests. Those visitors are invisible to Betterloop and are not counted anywhere.
This is the default. The script-tag build has no way to turn it off at all. The bundled build exposes respectDNT: false, which we would rather you did not use.
Collection defaults and their limits
The SDK and ingest both remove query strings and fragments, reduce referrers to origins, and redact common identifiers in paths. They remove common sensitive attribute fields such as email, password, token and phone, including nested fields. URL values lose query strings; email-shaped strings and common token patterns are redacted. These rules also apply to newly received events from older SDKs. Historical rows are not retroactively changed by this release.
- Text capture is off by default. Page titles, click labels, error messages and stacks can contain personal data. Automatic errors normally retain their category and source only. Set captureText: true(or data-capture-text="true") only after auditing your page. Pattern redaction still runs, but it cannot recognise every name, address or secret.
- Exclude sensitive surfaces. Mark a container with data-betterloop-ignore or data-privateto exclude its clicks. Configure excludeSelectors for additional containers. Configure excludePaths: ["/admin"] to suppress all events, including custom events, on that path and its descendants.
- Custom events and unusual paths still need review.Route slugs can contain names and other information no pattern can reliably recognise. Pass category labels and counts to track(), never user identities or free-form messages. Redaction is a safeguard, not a guarantee that arbitrary input is anonymous.
Where data goes when the AI layer runs
If the instance is configured with an Anthropic API key, each insight run sends an aggregate snapshot to the Anthropic API: weekly counts, up to 15 top pages with view and session counts, web vitals p75 values, up to 10 error messages truncated to 150 characters with their counts, and the rule findings with their evidence. No raw events, no session ids, no user agents, and no referrers are included. If no key is configured the layer is skipped and no data leaves the instance at all. See How insights work.
Controls you have
- Origin allowlist. Project Settings takes a comma-separated list of origins. With it set, ingest rejects any request whose Origin header is missing or not on the list, so a leaked write key cannot be used to write junk into your project from elsewhere.
- Do not install it everywhere. The SDK only sees the pages you put it on. Leaving it off authenticated admin screens is a legitimate way to keep sensitive paths out entirely.
- Delete a project. Project Settings has a delete button. It removes the project and cascades to every event, insight, and usage counter belonging to it.
There is no automatic retention cutoff today: the detectors only look at the last 7 days, but raw events are kept until you delete the project. If you need a shorter retention window than that, deleting and recreating the project is the blunt tool that exists right now.
Contacting support
When you use the support form, your reply email, optional name, topic and message are sent to our support inbox through Resend or the configured email provider. They are used to answer your request and remain in the support mailbox and provider's mail records. Message text, your name and reply address are not saved in the application's email log. That log keeps delivery metadata and a reference; separate hashed rate-limit counters help prevent abuse. Support messages are not sent to the AI analysis layer. Do not include passwords or API keys.
Betterloop on Betterloop
This site runs the Betterloop SDK, on the marketing pages and inside the app. A product that documents everyone else's data collection field by field has to account for its own, so: while you use Betterloop, the same nine event types listed above are collected about your use of Betterloop itself, into a project we own.
The rules are not special-cased for us. No cookies, no identities, Do Not Track and Global Privacy Control both honoured - if your browser sends either, nothing is collected here either. Paths inside the app include project ids, which are random and identify a project rather than a person. We use it for the same reason you would: to see what breaks and what is slow.
Keys and access
- bl_ write keys are public by design, embedded in your page like any analytics key. They can only append events to one project. They cannot read anything.
- blk_ API keys are secret, are shown once at creation, and are stored only as a SHA-256 hash - a database dump does not yield usable keys. They are read-only across your account and can be revoked at any time.
- Every read endpoint checks project ownership on every request. A key can never see another account's data, and a project it does not own returns 404 rather than confirming the project exists.
Want the field-level detail for a specific event type? The install page lists what each collector captures, and the API reference documents the exact wire format.