> ## Documentation Index
> Fetch the complete documentation index at: https://heedeo-e1965c.manicule.dev/llms.txt
> Use this file to discover all available pages before exploring further.
> For every page in one file, fetch https://heedeo-e1965c.manicule.dev/llms-full.txt (large).
> To search and read these docs as tools, connect the MCP server at https://heedeo-e1965c.manicule.dev/api/mcp.
> Every agent surface: https://heedeo-e1965c.manicule.dev/.well-known/llms-manifest.json

# 7. Privacy: purpose, footing and what Heedeo never does (/privacy)

<!-- agent-signals: reading_time_min: 21 · est_tokens: 12074 · updated: 2026-11-11 -->
Related: [Integrating Heedeo](/index.md), [Connect your app](/connect-your-app.md), [1. Quick start](/quick-start.md), [2. Serve Heedeo from your own domain (do this before you go live)](/serve-heedeo-from-your-own-domain.md), [3. One server call: evaluate or log](/one-server-call.md), [4. The fields to act on](/the-fields-to-act-on.md)

## Purpose

Heedeo processes device and account evidence for one purpose: to secure the authentication and signup your users ask you for, and to prevent fraud on your service. It does so for you alone: every identifier is hashed separately for your account, nothing is shared between customers or compared across them, nothing is used for advertising, and nothing is sold.

## Where that stands

This is how Heedeo is built to fit each regime; it is not legal advice, and your counsel decides your own footing.

* **EU.** Reading the browser and setting the visitor cookie is storage and access on the user's device (the ePrivacy Directive, Article 5(3)). Heedeo's footing is the exemption for what is strictly necessary to provide the service the user asked for, here the security of the signup or login they started; fraud prevention beyond that rests on legitimate interest under the GDPR (Article 6(1)(f)), which Recital 47 names for preventing fraud. Your page calls `identify()` only at an action you send to events, and the EU collection profile (below) leaves out the readings that do most to single out a machine.
* **UK.** PECR has the same rule for storage and access, and the Data (Use and Access) Act 2025 adds to it an exception for what is strictly necessary to prevent or detect fraud; the rest rests on legitimate interests under the UK GDPR, as in the EU.
* **US.** Under the CCPA, as the CPRA amended it, helping to ensure security and integrity and to detect and prevent fraud is a business purpose; Heedeo processes the data for you as your service provider, and never sells or shares it (no cross-context behavioural advertising). The state privacy acts (Virginia, Colorado, Connecticut, Texas and the others) exempt processing to prevent, detect or respond to fraud and security incidents from their opt-outs.

## What Heedeo never does

Heedeo never blocks anyone: it answers what it saw and what it suggests, and your code decides on every event. It sets no caps and no rate limits on what your end users do; counts are facts for your rule. It never flags an agent for being an agent: a declared or verified agent's automation and virtual machine are recorded, never reasons; only a claim nothing backs or a sudden crowd is. Automation with no agent declared weighs 10, which alone never reaches verify. And it never does these:

* **Personal data through the browser.** The script reads the browser and nothing else. Emails, phone numbers, account ids, principals and card fingerprints reach Heedeo only from your server, server to server.
* **A cross-customer graph.** Every hash is made separately for your account, so the same email at two customers is two unrelated hashes; no link, count, label or score crosses customers, and there is no shared reputation.
* **Raw addresses past the request.** The client IP and the email address are used while the event is answered (the IP to read its network and to ask IPinfo and Spur about it, the address to hash it, and to ask Have I Been Pwned only if you turned breach history on), then kept only as below.
* **Fingerprinting in a native app.** There is no mobile SDK: Apple forbids fingerprinting in apps whatever the consent. From an app, send events with the evidence your backend holds.
* **Selling or sharing visitor data.** Not to anyone, not for advertising, not for another customer's benefit.
* **A competitor's device intelligence as an input.** Fingerprint, Stytch, Castle and the like are never read. A paid email or phone service's results reach Heedeo only as evidence you send (section 3).

## What Heedeo keeps, and for how long

Heedeo never stores a raw email address, only one-way hashes of it (of its normalised form, and of the address as typed when that differs). It never stores a raw network address, only its approximate network (the /24, or the /48 for IPv6) and one-way hashes of it. Of the headers you send, only the user agent, the languages and the client hints are kept; the rest are read and discarded. The email's domain, never the address, is kept where a reason names it and with the domain lookups below. What has expired is deleted every hour.

| What                           | Holds                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    | Kept                                                                                                                                          |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| Identifications                | the browser readings the script sent (under the EU profile, without canvas, audio, fonts, speech voices, text and emoji measurements, media formats and the keyboard layout), the user agent, languages and client hints, the approximate network, what the readings showed of the browser (such as a private window, a virtual machine, an automation tool or a disguised browser), how many times the person used the pointer, the keyboard and the touch screen before the action, and how Heedeo recognised it; when Heedeo is served through the Domains page, or through the `@heedeo/sdk` proxy on Vercel or in a Cloudflare Worker, how the browser connected: its encryption (with the size and one-way hashes of the settings it offered, among them the JA4 hash Vercel and Cloudflare Enterprise compute), protocol and round trip, and the city of the Cloudflare data centre that answered | 90 days                                                                                                                                       |
| Events                         | one-way hashes of the email, the account, the principal and the email's pattern; the approximate network and a one-way hash of the address; the address's country, region and city, and what IPinfo and Spur said about its network; the user agent, languages and client hints; the evidence you sent, phone and card hashed; the answer                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | 30 days; an event a flag points to stays with its flag                                                                                        |
| Visitors                       | the visitor cookie's id, first and last seen, the readings Heedeo recognises each browser by (most hashed); the person's browsers, the ids merges retired, and what it did of each action (counts, first and last)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | no automatic expiry                                                                                                                           |
| Actions                        | per event: the action, its browser and visitor, when, the band the answer suggested, your outcome on it, and the kind of network (home, business, hosting, carrier, relay, Tor)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          | 13 months                                                                                                                                     |
| Identities a browser presented | per browser, one-way hashes of each email, account, principal, phone and card it presented, when first and last                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          | no automatic expiry                                                                                                                           |
| Links between browsers         | two of your browsers, what they share (as a hash) or that they are probably the same device, and how strongly that links them                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | a shared account, email, principal, phone or card, and probable matches: no automatic expiry; a shared address: 7 days after it was last seen |
| Merge history                  | which visitor each browser belonged to and from when to when, and what joined it (its own cookie, a shared identity as its hash, or a device match and its confidence); the operator who pressed Not them; what later confirmed or contradicted a match                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  | no automatic expiry                                                                                                                           |
| Flags, labels and outcomes     | the reasons, your notes, your labels (an email as its hash, a network as its /24 or /48), outcomes (an account as its hash), and each decision on a flag with the outcomes it replaced, so it can be undone                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | no automatic expiry                                                                                                                           |
| Phone and card hashes          | the phone and card hashes                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | with their event (30 days), and as long as the identities and links they made                                                                 |
| Network lookups                | IPinfo's answer for an address, with the address hashed                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  | 7 days; 1 day when it says residential proxy                                                                                                  |
| Spur lookups                   | Spur's answer for an address, with the address hashed                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    | 1 day                                                                                                                                         |
| Mail-server lookups            | the email domain's mail servers                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          | 24 hours                                                                                                                                      |
| Domain-age lookups             | when the email's domain was registered                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | 30 days                                                                                                                                       |
| Breach lookups                 | Have I Been Pwned's earliest breach for an address, with the address hashed                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | 30 days                                                                                                                                       |
| Request ids                    | each `request_id` of a label write or a report (`POST /v1/lists`, `POST /v1/outcomes`), with the write and its answer, so a retry is answered as the first attempt was; once you delete the person a report was on, a one-way hash of its id alone, unless the report's browser is another of your visitors' by then (an account they hold too, or the side a split left them): that report stays with them, and so do its id and its answer, which then names them                                                                                                                                                                                                                                                                                                                                                                                                                                      | 72 hours from the write                                                                                                                       |
| Webhook deliveries             | each message sent to your endpoints (section 12), as sent, and of your server's last answer its status code, the time it took and its first 1,000 characters                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | 30 days; deleting an endpoint deletes its deliveries                                                                                          |

A webhook message holds ids and labels as the API answers them, and the event's answer without its reasons' text: never a raw email or network address, a person's name or an operator's email. What your server answers is kept as it answered, so leave personal data out of your webhook answers.

A visitor's profile (its browsers, identities and the counts of what it did) comes from the records above. Undoing a merge (Not them) gives each side its own counts again: Heedeo recounts exactly what happened within the 13 months actions are kept, and nothing older.

## Merges

Heedeo decides who is one person, explains it and can undo it; your code stores nothing.

**How.** Two browsers become one visitor when they present the same account, or the same email and both verified it (`evidence.email.verified` on an event of each that presented it). An email that is only typed joins no one: anyone can type an address, so it is a weak link in `related` that lends 50 at most. Merge on, under Settings, Advanced, Weights, chooses which shared identities merge; phone and card can be turned on, though a shared card or phone is also a household's. Two browsers also become one visitor when Heedeo recognises them as the same device, with confidence at or above your merge threshold. A device match is never certain, so its confidence stays below 1, and people whose machines only look alike (one laptop model across a class, two in a household) stay separate visitors. A shared network alone never merges anyone: a shared address is a link in `related`, not a merge. No merge crosses customers.

**The threshold** is 0.86 by default (Settings, Advanced, Weights, Merge threshold, 0.70 to 0.95): how sure Heedeo must be before it counts two browsers as one visitor. Lower catches more repeat signups, and joins more people who are not the same person; the default keeps look-alike devices apart. Raising it splits nothing already merged. Merge reliability (Settings, Advanced, Weights) shows the device merges of the last 90 days by confidence, how many were later confirmed or contradicted, and the precision; a threshold is safe when the precision in its row is at or above it.

**The alias.** The older visitor keeps its id; the other id becomes an alias that resolves to it forever, listed in `visitor.aliases` and answered with `merged_into`, so an id you stored keeps working. A merge never rewrites your events or actions: each keeps the browser and the visitor it had when it was written. A visitor has one outcome of yours, the latest: when both sides of a merge hold one, the newer stands. What joined each browser is shown on the visitor page in words ("same email", "device match 0.95") and in `visitor.browsers[].evidence`, never as a hash.

**The undo.** A device match can be wrong (two people on one model of phone), so on the person page an operator can press **Not them** on a browser a device match placed. It becomes a visitor of its own again, each side gets its own counts of the last 13 months back, and no later match puts the two back together. The id stays with the side that presented an email, account, phone or card (or, when both or neither did, the side seen most recently), and an id a merge retired goes with its own browser. Not them refuses while the person has a label, because which side it meant is your call: clear the label, press Not them, then label the right person. An outcome on the visitor stays with the side holding the browser it was reported on, under that side's id, and the other side goes clean. From then on neither side is listed in the other's `related`, or lends it anything. A merge by a shared email or account is not split; turning its kind off under Merge on stops future merges of that kind and splits nothing.

## Deleting a person

When someone asks you to delete their data, `DELETE /v1/visitors/<visitor_id>` with the secret key removes everything Heedeo holds of that person for you, all at once. An id a merge retired deletes the visitor it joined. The answer is the visitor's id and how many records of each kind were deleted. The answer also says when, as `deleted_at`. The call is safe to send again: an id you deleted before answers 200 with that id, an empty `deleted` and the first `deleted_at`, so a deletion whose answer you lost ends the same way on a second try. An id Heedeo never held for you (a typo, another workspace's, another kind of id) answers 404, and one of your event ids answers 400 `invalid_request` saying to send its `visitor_id`: a deletion request sent with the wrong id never reads as done. Check the status, not only that the call returned.

```sh
curl -s -X DELETE -H "authorization: Bearer $HEEDEO_SECRET_KEY" https://app.heedeo.com/v1/visitors/8855…
# {"id":"8855…","deleted_at":"2026-11-11T08:25:01.069+00:00","deleted":{"flag_decisions":1,"flags":1,"actions":12,"outcomes":1,"outcomes_folded":0,"merge_outcomes":0,"event_requests":0,"write_requests":0,"events":12,"farm_group_members":0,"farm_group_pairs":0,"observations":3,"browser_links":4,"browser_edges":2,"browser_visitors":3,"visitor_alias":1,"browsers":2,"visitors":1}}
```

| Deleted              | What it held of the person                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `flag_decisions`     | the decisions on their flags, with the outcomes those decisions replaced; a decision that also covered someone else's flag stays, without anything of theirs                                                                                                                                                                                                                                                                                                              |
| `flags`              | the flags on their browsers and events, and a related flag on another browser that points at one of their events                                                                                                                                                                                                                                                                                                                                                          |
| `actions`            | what they did, 13 months of it                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `outcomes`           | the outcomes you reported on their events, their visitor and the accounts only they presented                                                                                                                                                                                                                                                                                                                                                                             |
| `outcomes_folded`    | earlier reports on them that a later one replaced when two of their visitors became one                                                                                                                                                                                                                                                                                                                                                                                   |
| `merge_outcomes`     | what later confirmed or contradicted the merges of their browsers and into their visitor                                                                                                                                                                                                                                                                                                                                                                                  |
| `event_requests`     | the request ids a repeat of their events was answered under                                                                                                                                                                                                                                                                                                                                                                                                               |
| `write_requests`     | the request ids of your reports on them (`POST /v1/outcomes`), with the answers kept for a repeat. What stays of each, until 72 hours after the report, is a one-way hash of the id and nothing of the report or of them, so a retry is refused (409) and the report is not written again. A report on an account another of your visitors holds too, or on a browser a split left with another visitor, is not theirs alone: it stays, and its repeat names that visitor |
| `events`             | their events, with the hashes, evidence and answers stored on them, and events with no browser that carry an identity only they presented; `GET /v1/evaluations/:id` then answers 404 for each                                                                                                                                                                                                                                                                            |
| `farm_group_members` | their sign-ups' places in the nightly farm groups (record-only, for review)                                                                                                                                                                                                                                                                                                                                                                                               |
| `farm_group_pairs`   | the record-only operator notes pairing one of their browsers with another of a farm group                                                                                                                                                                                                                                                                                                                                                                                 |
| `observations`       | their identifications                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `browser_links`      | the hashes of every email, account, principal, phone and card their browsers presented                                                                                                                                                                                                                                                                                                                                                                                    |
| `browser_edges`      | every link between one of their browsers and any other, either way                                                                                                                                                                                                                                                                                                                                                                                                        |
| `browser_visitors`   | which visitor each of their browsers belonged to, and what joined it                                                                                                                                                                                                                                                                                                                                                                                                      |
| `visitor_alias`      | the ids merges retired into the visitor                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `browsers`           | their browsers, with the readings Heedeo recognised them by                                                                                                                                                                                                                                                                                                                                                                                                               |
| `visitors`           | the visitor and its profile                                                                                                                                                                                                                                                                                                                                                                                                                                               |

Your own labels under `lists` (a visitor id, an email hash or a network you marked block or allow) are kept: they are yours, and a label on an id you block should outlast the data behind it. So are the request ids of your label writes, for their 72 hours. A report on an account another person holds too moves to that person and stays, with its request id, whose repeat then names them; so does a report whose browser a split left with another visitor. Another person's browser keeps its own records, even where they carry this visitor's id: from a time the two were one visitor (a browser split off this visitor keeps the events it had while in it), or because an answer about that other person named this visitor as related or as a possible match. A lookup of that id answers 404 from then on. Daily usage counts hold no identifier and stay. So does a record that the person was deleted: their id and each retired one, with the time, and nothing else of them, so a deletion sent again can answer that it was done. A browser of theirs that comes back afterwards is identified afresh, with nothing of what was deleted to match it against. A visitor that is not yours answers 404, and nothing of the other workspace's is touched, as does what is no visitor id at all. With the package: `await heedeo.deleteVisitor(visitorId)`, the same `{ data, error }` shape, tried a second time on a 5xx or a network error from 0.2.0-beta.1.

A few rows name no browser: an event sent after its token expired, and a report on a `user_id` that came before any browser presented it. The delete reaches them through the email, account, phone or card they carry, when only this person's browsers presented it: such an event goes, with its flags, its outcomes and its delivery log messages, and so does such a report. It cannot reach:

* rows that carry only an identity another of your visitors also presented, such as a login two people share: they could be either person's, so they stay, and a report on that account stays with the other visitor;
* rows that carry an identity this person's browsers never presented: nothing ties them to this person;
* someone with no visitor id, because you only ever sent events from your backend: there is no visitor to delete;
* an event you send after the delete, on a token issued before it: it is a new event, with no browser.

Deleting a person also removes, at once, the webhook messages about them that were delivered or failed from your endpoints' delivery logs, and sends `visitor.deleted` (section 12) to every endpoint that listens for it, so your app can delete its side too. That message, and any message about them still being sent at that moment, stay in the log until their 30 days are up.

## Collection: the full and EU profiles

Under Settings, Advanced, Collection you choose what the script reads. **Full**, the default, is every source: screen, GPU, the fonts installed, how the browser draws canvas graphics and processes audio, how it measures text and emoji, the speech voices it offers, the audio and video formats it says it can play and record, the keyboard layout where the browser shares it, language and time-zone settings, the browser's own description of itself, whether an automation tool or the browser's developer tools are attached to the page, how many times the person moved or clicked the pointer, pressed a key or touched the screen on the page before the action (counts only: never which keys, what was typed or where), and signs that a script disguises the browser: which graphics card a background worker uses, whether the browser's built-in functions were replaced, and whether the window size and pixel density the page reports are the browser's own. **EU** leaves out canvas, audio, fonts, speech voices, text and emoji measurements, media formats and the keyboard layout: the script never reads them. For up to an hour after you switch, a browser that cached the earlier script may still send them, and Heedeo discards them. Switching to EU also removes what Heedeo kept of them, and Heedeo no longer uses them for any visitor. On a large workspace the removal finishes in the background: Settings says it is removing until it is done, and you can choose Full again after that. Under EU, a returning browser is still the same visitor, and browsers are still related by the email, account, phone or card they presented, but Heedeo recognises a browser that cleared its cookies and storage less reliably than under Full. Both profiles read whether an automation tool or developer tools are attached and count the person's input: these say how the page was used and single out no one. Both also check for a disguise: that says how the browser is set up, not who uses it, and the graphics card is one both profiles already read.

Under both profiles, Heedeo keeps the visitor's id on the device: in a first-party cookie, `_idd`, that identify sets for 400 days, with copies the script keeps in the browser's localStorage and IndexedDB.

## For your privacy policy

Add a line like this, naming what you send (leave out the fields you do not send): "To prevent fraud and abuse, and to secure the signup and sign-in you ask us for, we measure technical characteristics of your browser when you sign up or sign in (such as screen, installed fonts and speech voices, language settings and keyboard layout, the media formats it supports, and how it renders text, canvas graphics and audio) through our provider Heedeo, which uses them for that purpose only, for us alone, and never for advertising or sale; your email address and your network address are kept only as one-way hashes, with the network address also as its approximate network. We also share with Heedeo whether your email address is verified and when; your phone number, whether it is verified, its line type and its country; your sign-in provider and what it says of your account (whether your email is verified, your organisation's domain, Apple's real-user status, whether the address is a private relay, when the account was created and whether two-factor authentication is on); when your account with us was created, its plan and since when it has been paid; your payment card's fingerprint, funding type, card verification result, wallet, 3-D Secure result and country, and our payment provider's risk levels; and the kind, result and time of identity checks we run. Your phone number and card fingerprint are kept only as one-way hashes." Under the EU profile, write "(such as screen, language settings and how the browser describes itself)" instead of the first parenthesis.

If you turn on breach history, add: "Heedeo checks your email address with Have I Been Pwned to see whether it appeared in known data breaches; the address is not stored." The email's domain (never the address) is looked up in the public domain registries to learn its age and in public DNS (Cloudflare's, then Google's) for its mail servers; where Heedeo's server holds the keys, your network address is looked up at IPinfo and at Spur, under both collection profiles.

What Heedeo keeps of each evidence field, with the event, for 30 days (section 3); the two hashes also stay as long as the identities and links they made (above):

| Evidence field                  | What Heedeo keeps                                                |
| ------------------------------- | ---------------------------------------------------------------- |
| `email.verified`                | as sent                                                          |
| `email.verified_at`             | as sent                                                          |
| `phone.number`                  | only a one-way hash made for your account, never the number      |
| `phone.hash`                    | your hash, hashed again for your account                         |
| `phone.verified`                | as sent                                                          |
| `phone.line_type`               | as sent                                                          |
| `phone.country`                 | as sent                                                          |
| `oauth.provider`                | as sent                                                          |
| `oauth.email_verified`          | as sent                                                          |
| `oauth.hosted_domain`           | as sent (a domain, never an address)                             |
| `oauth.real_user_status`        | as sent                                                          |
| `oauth.private_relay`           | as sent                                                          |
| `oauth.account_created_at`      | as sent                                                          |
| `oauth.two_factor`              | as sent                                                          |
| `account.created_at`            | as sent                                                          |
| `account.plan`                  | as sent                                                          |
| `account.paid_since`            | as sent                                                          |
| `payment.funding`               | as sent                                                          |
| `payment.card_fingerprint`      | only a one-way hash made for your account, never the fingerprint |
| `payment.cvc_check`             | as sent                                                          |
| `payment.wallet`                | as sent                                                          |
| `payment.three_ds`              | as sent                                                          |
| `payment.country`               | as sent                                                          |
| `payment.radar_risk_level`      | as sent                                                          |
| `payment.free_trial_abuse_risk` | as sent                                                          |
| `verification.kind`             | as sent                                                          |
| `verification.result`           | as sent                                                          |
| `verification.at`               | as sent                                                          |
