4. The fields to act on
The verdict
With the package, heedeo.evaluate() answers a verdict: the answer below in data, and the same answer read for you. Read the verdict first, and reach into data for a count or a fact.
const verdict = await heedeo.evaluate({ token, action: "signup", email });
verdict.flagged; // stop this action
verdict.trusted; // you pressed Trust on this person
verdict.has("vpn", "tor"); // any of these reasons fired
verdict.certain("repeat_action"); // it fired at full strength
verdict.noToken; // no usable token: ask for your own verification step
verdict.toLog(); // one object for your log line
verdict.data; // the whole answer, below; null when Heedeo did not answer| On the verdict | What it is | When Heedeo did not answer |
|---|---|---|
flagged | Stop this action: flag is true and you have not pressed Trust on the person. data.flagged, the API’s own. | false |
trusted, blocked | Your own label on the person: listed is allow, or block. | false |
band | allow, verify, review or block at your thresholds, and allow for a person you trust. | null |
has(...codes) | Any of these reasons fired, at any strength. | false |
certain(...codes) | Any of these fired at full strength, 100 at Heedeo’s defaults whatever weights you set. | false |
strength(code) | How strongly a reason fired at Heedeo’s defaults: 0 when it did not, negative for a trust reason. On a login or a name of your own, which Heedeo judges on nothing, it is 0 and certain() is false. | 0 |
noToken | The token was missing, expired, unreadable or names no visitor of yours. False for a person you trust. data.no_token, the API’s own. | false |
eventId, visitorId | The event’s id and the person’s. | null |
toLog() | One flat object for a log line: event_id, visitor_id, flagged, listed, band, score, by (what decided), reasons (their codes), error (the error’s code, null when Heedeo answered) and trace_id (@heedeo/sdk 0.2.0-beta.1 or later: the failed call’s id for Heedeo support, null when Heedeo answered). It holds no email and no address. | The same, with the error’s code |
data, error | The answer below, and why there is none. Exactly one is null. | null, the error |
Every read is safe when Heedeo did not answer: nothing is flagged, so an outage never refuses anyone, and error.code says what happened. data.flagged and data.no_token are the same reading, made by Heedeo: every answer carries them first, a stored one replayed by request_id and a webhook’s event too, so a caller without the package reads them as they are. The verdict reads them (from @heedeo/sdk 0.2.0-beta.1, the first with the verdict), and on an answer from before them reads flag, listed and the notes the same way. data.flag does not know about Trust: it is still true for a flagged person you trust, which is why a rule reads flagged. A copy (a spread, JSON) keeps the values, flagged among them, and loses the four functions. verdictOf({ data, error: null }) from @heedeo/sdk reads an answer you already hold the same way, such as the event of an event.scored webhook or an answer in a test.
Read this, not that
Some facts appear twice in an answer. Read one:
| To know | Read | Not |
|---|---|---|
| whether to stop the action | verdict.flagged, or flagged without the package | flag, decision.flag |
| the band | verdict.band, or suggested without the package | decision.band |
| the score | score | decision.score, risks[r].score |
| the flag line | thresholds.review | threshold |
| why | decision.by, decision.risk, decision.judged |
The fields under Not stay in every answer, for the code that already reads them. On a workspace still on the legacy verdict, decision is a preview of the decision and can disagree with flag, so it does not say why: read it as the reason only where decision.flag equals flag, the check toLog().by makes. The same holds for an answer stored while a workspace was legacy.
Trust and Block, in each place
One label, named four ways. It sits on a person, an email address or a network; Review and an assistant set it on a person or an email address, and a network’s comes through the package or POST /v1/lists.
| In Review | In the package | In an assistant (MCP) | On the wire |
|---|---|---|---|
| Trust | trust(), verdict.trusted | label_visitor with action: "allow" | POST /v1/lists action: "allow"; a later evaluation’s listed: "allow" |
| Block | block(), verdict.blocked | label_visitor with action: "block" | POST /v1/lists action: "block"; a later evaluation’s listed: "block" |
| Press the pressed button again (a person), or Clear (an email) | clearLabel() | label_visitor with action: "clear" | POST /v1/lists op: "remove", which answers removed; a later evaluation’s listed: null when no other label matches |
An evaluation’s listed reads every label that matches it, the person’s, the email’s and the network’s, and a Block wins over a Trust.
A Trust also keeps Auto-confirm (Settings) off the flags it covers, on the person, the email or the network: they are still raised and sent as flag.created, and wait in Review for you, unless a Block outweighs the Trust, as in listed.
Dismiss is not Trust. Dismiss on a flag records the outcome legit, as outcome(), resolve_flag with dismiss and POST /v1/outcomes do: the person’s next events score lower, and verdict.trusted stays false. Only a Trust label makes flagged false whatever the score.
trust(), block() and clearLabel() need @heedeo/sdk 0.2.0-beta.1 or later.
The answer
The answer to a signup that Heedeo flagged: the same person signed up a second time, under another email, from the same browser.
{ "flagged": true,
"no_token": false,
"flag": true,
"score": 150,
"threshold": 100,
"thresholds": { "verify": 60, "review": 100, "block": null, "action": null },
"suggested": "review",
"decision": { "flag": true, "band": "review", "score": 150, "risk": "account_abuse", "by": "risk", "judged": ["account_abuse"] },
"risks": { "account_abuse": { "score": 150, "judged": true,
"reasons": [{ "code": "repeat_action", "weight": 100 }, { "code": "many_identities", "weight": 50 }] } },
"version": null,
"reasons": [{ "code": "repeat_action", "text": "signup already done by this visitor on 7 Nov from this browser", "weight": 100, "base": 100 },
{ "code": "many_identities", "text": "This event raised the visitor's identities to 2; the threshold is 3", "weight": 50, "base": 50 }],
"visitor": { "id": "613d…", "confidence": 1, "first_seen": "…", "last_seen": "…",
"emails": { "24h": 2, "7d": 2, "all": 2 }, "accounts": { "24h": 0, "7d": 0, "all": 0 },
"addresses": { "1h": 1, "24h": 1, "7d": 1 }, "countries": { "1h": 1, "24h": 1, "7d": 1 },
"browsers": [{ "id": "613d…", "kind": "stored", "confidence": 1, "merged_by": "this browser", "evidence": {}, "first_seen": "…", "last_seen": "…" }],
"aliases": [],
"actions": { "signup": { "count": 2, "first": "2026-11-07T18:10:31Z", "last": "…" } } },
"related": [],
"account": null,
"counts": { "24h": 2, "7d": 2 },
"activity": { "last_hour": 2, "hourly_norm": 0.4, "burst": false },
"listed": null,
"email": { "disposable": false, "alias_of_existing": false, "pattern": false, "free_provider": true, "role_address": false,
"young_domain": false, "domain_registered": null, "domain_age_unknown": false, "breach": null },
"network": { "link_weight": 0.5, "datacenter": false, "vpn": false, "vpn_confidence": null, "vpn_service": null,
"residential_proxy": false, "residential_proxy_confidence": null, "proxy_services": [], "proxy": false, "tor": false,
"private_relay": false, "anonymous": false, "network_type": "isp", "asn": "AS7922", "devices": 3, "risks": [],
"country": "US", "city": "Boston", "region": "Massachusetts", "timezone_mismatch": false },
"agent": null,
"environment": { "tampered": false, "tamper": [], "tampered_confidence": null, "anti_detect": null, "virtual_machine": false, "vm": [],
"virtual_machine_confidence": null, "private_mode": false, "rarity": 1, "connection": null,
"automation_framework": null, "interaction": { "pointer": 41, "key": 52, "touch": 0 } },
"event_id": "79c7…" }flagged and no_token. flagged is whether to stop this action: flag, unless you pressed Trust on the person (listed: "allow"). no_token is whether the event came with no usable token (a note no_token, token_expired or unknown_visitor in reasons), false for a person you trust: ask for your own verification step. Each is derived from the answer when it is sent, so an answer read back later says what it said then.
flag and decision. flag is whether the action reached your flag line, or you blocked or reported the person; it does not know about Trust, so read flagged to decide. decision says why, in the same six fields on every event: flag; band, which your thresholds under Settings make of score (allow, verify, review from the flag line, block once you set one); score; risk, the question that reached your flag line; by, what decided: risk (Heedeo’s answer to that question), label (your Block label on the person, their email or their network) report (your abuse report on the person or an account they used) or retry (a once action tried again by the same person under the email or account of an earlier attempt, which keeps at least that attempt’s band until you resolve it as legit), and null when nothing flagged; and judged, the questions Heedeo judges this action on: account_abuse for the once actions of section 3, none for a login or a name of your own. That is Heedeo’s rule, read from the action’s name; nothing in the call chooses it. A flag can also appear later on an earlier signup, when Heedeo learns that two people are one, and it goes away again (withdrawn, section 12) if that turns out wrong.
risks. What Heedeo answers to each question about the person. There is one today, account_abuse: one person making more than one account. Each has its score, judged (whether Heedeo judges this action on it) and its reasons, each with the weight it contributed. Every question is scored on every event: on a login, risks.account_abuse still says what a signup would have answered, with judged: false.
The rest of the decision:
scoreandsuggested: the decision’sscoreandband, at the top level as integrations read them.thresholdandthresholds: your flag line and your Verify, Flag and Block lines under Settings, Advanced (60, 100 and none by default). An action can have its own three (Settings, Advanced, Actions): thenthresholds.actionnames it.version: when your weights last changed, ornullwhile you use the defaults (Settings, Advanced, Weights; section 9).reasons: every reason that fired, each with one line of text for you, never for the end user, itsweight, what it contributed to this action (0 on an action judged on nothing), andbase, the same at the default weights. Every code, and the four notes that ride inreasonswith weight 0 (no_token,token_expired,unknown_visitor,no_identity), is in the table under Reasons and default weights, below.listed: your own label for this visitor, email or network:"block","allow"ornull.
Heedeo enforces nothing: your code reads the answer and decides (section 5).
The visitor (visitor is null without a usable token):
visitor.id: the visitor, one id for the person across their browsers, decided by Heedeo on every event: your code stores nothing to group them. An id you stored earlier is the visitor’s id or one of itsaliases(section 7, Merges, has the undo).visitor.confidence: how sure Heedeo is that every browser invisitor.browsersis this person: 1 when it is certain.visitor.first_seenandlast_seen: when Heedeo first and last saw the visitor.visitor.browsers: the person’s browsers, the oldest first, each with the fields in the table after this list. A browser of the person is here, not inrelated.visitor.aliases: the ids a merge retired into this one, the newest first (up to 100). Each still resolves:GET /v1/visitors/<id>andPOST /v1/outcomeswith a retired id answer the visitor it joined, withmerged_intoset to its id (section 10), so a customer who stored the old id learns the new one on its next lookup, or at once from avisitor.mergedwebhook (section 12).visitor.actions: what the visitor did, by action name, across its browsers, this event included:count, and thefirstandlasttime. Kept 13 months (section 7).visitor.emails.allandvisitor.accounts.all: distinct canonical emails and account ids this visitor has shown you across its browsers, ever (also24h,7d), this event included. At signup, before a user id exists, emails is the one to use.visitor.addressesandvisitor.countries: how many different network addresses and countries this visitor’s browsers came from in the last hour, day and week (1h,24h,7d), this event included. Mobile networks, Private Relay and Tor are not counted: many people share them. Home, work and a café are three addresses in a day; many in an hour, or several countries in a day, deserves a look.- The principal (
acting_for): the people a declared or verified agent acts for are expected, and do not count as its accounts. An anonymous browser acting for many people is multi-accounting under another name, and they count.
Each browser in visitor.browsers has:
| Field | What it holds |
|---|---|
id | the browser’s id |
kind | stored (this browser itself, known for certain), declared (joined by a shared identity) or matched (joined by a device match) |
confidence | 1 for stored and declared, the match’s confidence for matched |
merged_by | what placed it, in words a support person can read: “this browser”, “same email”, “same account”, “device match 0.95” |
evidence | the same without words: {} for the browser itself, the identity’s kind for a shared one, the match’s score for a device match, never a hash or a raw value |
first_seen and last_seen | when Heedeo first and last saw the browser |
Around the visitor:
counts: this browser’s events of this action, over24hand7d.related: browsers of other visitors related to this visitor’s browsers, one entry per link, the strongest first. Each hasvisitor_id(the visitor that browser is in now),link,weight(how much the link proves, from 0 to 1) and the other browser’s ownidentities. The visitor’s own browsers are invisitor.browsers.linkis one of the values in the table after this list.account, when you sent auser_id, the facts for a login rule (section 5):visitors, the browsers that have presented this account over24h,7dandall, this one included, andnew_browser, whether this browser is new to it. They are facts for your rule, never a flag.nullwithout auser_id.email, what Heedeo read from the address (Email depth, in section 3), with the fields in the table after this list.network, what Heedeo knows of the event’s address, with the fields in the table after this list.agent, null when nothing says an agent or a program sent the request:operator(the operator the agent names or proves),verified(it signed with Web Bot Auth, or your edge verified it),declared(what its user agent claims),operator_network(the address is in an agent operator’s network),automation(the browser shows automation),http_clientandmissing_headers. A headless or automated browser shows inagent.automation, and is theautomationreason only when no agent is declared or verified.agent.http_client: the HTTP library, tool or crawler the request’s user agent names, such ascurl,python-requestsorgooglebot, ornull. A name means a program, not a person’s browser, sent the request.agent.missing_headers: the headers every browser sends that this request lacked, such as["accept-language"];[]when none was missing.nullwhen Heedeo cannot tell, as from@heedeo/sdkbefore 0.1.0-pilot.17 or a call without:forwarded(section 3). A non-empty list means the request most likely did not come from a person’s browser.- Your own server-to-server calls, your mobile app, and a CDN or proxy in front of your server that does not pass the browser’s headers through can show in either field, so check for those before a rule refuses on either. Neither field is a reason; use them in your own rule.
environment, what the identification’s browser showed of itself, null without an identification, with the fields in the table after this list.activity: your traffic for this action in the last hour against its weekly hourly norm, andburstwhen it is well above. It is about your traffic, not this visitor. On a new or quiet account a few test signups are already a burst.
The values of related[].link:
link | What it means |
|---|---|
shared_account or shared_email | the two presented the same account id or email. An email either of the two only typed, never verified, is a weaker link. |
shared_phone or shared_card | the two presented the same phone or card in evidence. |
shared_principal | the two acted for the same person: an agent’s acting_for, or the person’s own browser presenting that account id. |
probable | Heedeo found one a probable match of the other, not sure enough to make them one visitor; weight is the match’s confidence. |
same_machine | two browsers on one computer, such as an agent’s headless browser beside the person’s own. It never makes the two one visitor. |
ip | the two were seen on the same network address recently. A weak link: as often a stranger as the person. |
The fields of email:
| Field | What it holds |
|---|---|
disposable, alias_of_existing and pattern | true when disposable_email, alias_of_existing or email_pattern fired |
free_provider | the domain is a large free mail provider’s |
role_address | the address names a role, such as admin@, info@ or billing@ |
young_domain | the reason fired |
domain_registered | when the email’s domain was registered; null for a free provider’s, which is never asked, or when the registry could not say |
domain_age_unknown | true when the registry was asked and could not say; never a reason |
breach | Have I Been Pwned’s earliest breach holding the address as { "first": "2013-10-04" }, first null in none; null while breach history is off |
The fields of network. A field Heedeo could not learn for this address is null:
| Field | What it holds |
|---|---|
link_weight | what sharing this address proves about two browsers, from 0 to 1. 0 on a mobile network, Private Relay, Tor, an address many devices share and a network Heedeo could not name, where sharing it says nothing |
datacenter | the address belongs to a cloud or hosting provider, not to a home, office or mobile line |
vpn | the address is a VPN’s. It can be true where the vpn reason did not fire |
vpn_confidence | how sure Heedeo is of vpn: high or medium (below); null unless vpn is true |
vpn_service | the VPN service by name, such as MULLVAD_VPN, when known |
residential_proxy | the address is on a residential proxy list: proxy traffic has left from it. Most listed addresses are ordinary home or mobile lines with one device lent to a proxy network, so it does not mean this visitor used a proxy |
residential_proxy_confidence | how sure Heedeo is of residential_proxy: high, medium or low (below); null unless it is true |
proxy_services | the proxy services seen on the address, such as OXYLABS_PROXY; [] when none |
proxy, tor and private_relay | the address is a proxy, a Tor exit, or Apple’s iCloud Private Relay. Private Relay is how many honest Safari users browse, and is never a reason |
anonymous | true when the tor, vpn, proxy or anonymizer reason fired, so false on Private Relay and on mobile networks |
network_type | isp, business or hosting |
asn | the network’s number, such as AS16509 |
devices | how many devices recently used the address. Many means a shared address: an office, a campus, a mobile network or a proxy |
risks | what the address has recently been used for that calls for care, such as TUNNEL, CALLBACK_PROXY or LOGIN_BRUTEFORCE; [] when none |
country | the country the address is in, as its ISO 3166-1 alpha-2 code such as US: IPinfo’s, or your edge’s country header (cf-ipcountry, x-vercel-ip-country) when IPinfo cannot place the address; null when neither says |
city and region | where the address is, as named places; never coordinates |
timezone_mismatch | the browser’s time zone differs from the address’s. A traveller’s and a wrong clock differ too, so it is a hint. null when either is unknown |
A confidence says how far to trust its flag in a rule of your own. Neither vpn nor residential_proxy is enough to act on alone, at any confidence: count high or medium beside another sign, and treat low as a hint, not enough to refuse anyone (RULES.md).
The fields of environment:
| Field | What it holds |
|---|---|
tampered | the browser’s own readings disagree, on the checks behind the browser_tampered reason; null from an older script. tamper lists every disagreement found |
tampered_confidence | how sure Heedeo is of tampered: high, medium or low, read as network’s are; null unless it is true |
anti_detect | true when the browser disguises what it is, the way anti-detect browsers and stealth tools do; tamper names what gave it away. Privacy browsers such as Brave and Tor Browser change some readings too, and read null. null when nothing showed, which does not prove an honest browser; never false. A name only this check finds moves no score yet, so tampered can be false while anti_detect is true |
virtual_machine | the browser runs in a virtual machine, with vm, what showed it |
virtual_machine_confidence | how sure Heedeo is of virtual_machine: high or medium; null unless it is true |
private_mode | a private window; null when it cannot be told |
rarity | how many of your visitors in the last 30 days have a device that looks like this one; a low number is a rare device |
connection | how the browser connected: tls (such as TLSv1.3), cipher, protocol (such as HTTP/2) and rtt_ms, the round trip in milliseconds, each null when not known. null unless Heedeo is served through the Domains page (through Cloudflare once you apply the Worker’s update there), or through the @heedeo/sdk proxy running in a Cloudflare Worker. The proxy on Vercel sends only the TLS fingerprint Vercel measures, which Heedeo keeps and does not answer, so there connection stays null. Moves no score |
automation_framework | true when an automation tool was controlling the browser, or its developer tools were open: a developer testing your site shows here too. null when nothing showed either, which does not prove a person was there. Moves no score |
interaction | how often the person moved or clicked the pointer (pointer), pressed a key (key) and touched the screen (touch) on the page before you called identify(), each counted up to 1000; never which keys or where. All three 0 on a signup means nobody used the page: a script sent it, or the form filled and sent itself. An automation tool can type and click as a person does, so counts above 0 do not prove a person. null from an older script. Moves no score |
Testing: test traffic flags. Check what your rule does, not flag. Section 5, Test your rule, says what each test signup answers and why local tests score higher than real ones.
Reasons and default weights
Every reason, with its default as your Settings, Advanced, Weights table shows it before you accept your own. A trust reason speaks for the person and shows negative in reasons. A reason whose default is 0 is recorded: it shows in reasons wherever it fires, so you can see what it would catch, and decides nothing. A note says something about the call itself and decides nothing. The evidence reasons and how to send their evidence are under Evidence in section 3.
| Reason | Default | What it means |
|---|---|---|
listed_block | 100 | Strong; flags alone. You labelled this visitor, email or network block. |
receipt_reused | 100 | Strong; flags alone. A reused token: the code keeps its older name, which rules and weights are keyed by. The token was already spent by an earlier, different event: a replay, answered with visitor null. The same event sent again gets the first answer back and is not this reason. |
disposable_email | 100 | Strong; flags alone. The address is at a disposable email provider. |
no_mx | 100 | Strong; flags alone. The email’s domain cannot receive mail, so no one reads the address. The honest case is a typo in the domain. |
alias_of_existing | 100 | Strong; flags alone. The email is an alias, such as a dot or plus-tag spelling, of an address another of your visitors already holds. The person’s own address typed another way (johndoe@ then john.doe@) is never this reason. |
many_identities | 100 | This visitor has used more than one email or account. A person’s own new device brings their identities with it and adds none. |
probable_match | 100 | This visitor is probably the same person as another of your visitors, who holds other emails or accounts. |
related_identities | 100 | Another of your visitors, who holds other emails or accounts, is related to this one by a shared account, email, principal, machine or address. It appears only on an event that adds an identity or a new link; GET /v1/visitors shows the standing evidence. |
confirmed_abuse | 100 | You reported this visitor, or an account it has presented or presents now (by user_id), as abuse, or a visitor related to this one. Your report on this person flags any action of theirs. |
repeat_action | 100 | This person already did this signup, trial_start, redeem:CODE or payout under another email or account; the text says when, and from which browser. Below 100 when Heedeo is likely, not certain, it is the same person. |
account_browsers | 80 | A fact for your login rule (account.visitors, section 5), not a sign of abuse: many browsers have presented this account lately, as when a login is shared or sold. It flags nothing on its own. |
datacenter | 80 | Abnormal pattern. The address belongs to a cloud provider, not to a home, office or mobile line. |
network_burst | 80 | Unusually many new identities are arriving from this network. An honest person on a network where a farm is signing up is in the same crowd, so it never flags alone. |
email_pattern | 80 | Unusually many addresses that look generated from one pattern, such as 1001@ and 1002@ at one domain, are arriving. It never flags alone. |
domain_burst | 80 | Recorded, and weighs 0 on the default weights until it is measured: set its weight under Settings, Advanced, Weights to score it. Unusually many new identities with addresses at one email domain are arriving, against that domain’s own hourly norm, as when a farm buys a domain and signs up on it from fresh browsers. A free provider’s domain and a disposable one are never counted, and a company’s people signing up through the day stay under the norm. It never flags alone. |
device_burst | 80 | Recorded, and weighs 0 on the default weights until it is measured: set its weight under Settings, Advanced, Weights to score it. Unusually many new identities are arriving on browsers of one device configuration (platform, engine, screen class, time zone and GPU), against that configuration’s own hourly norm, as when a farm signs up from one phone model or one machine under fresh cookies and addresses. It fires on a carrier or Private Relay too, and it never flags alone. |
young_domain | 80 | Abnormal pattern. The email’s domain was registered under 30 days ago (Email depth in section 3). |
recent_domain | 50 | Recorded, and weighs 0 on the default weights until it is measured: set its weight under Settings, Advanced, Weights to score it. The email’s domain was registered 30 to 90 days ago (Email depth in section 3). |
operator_burst | 80 | Unusually many visitors from one agent operator are arriving at once. It never flags alone. |
action_rate | 80 | This visitor takes the action much more often than your typical visitor. |
declared_agent | 20 | An unverified agent claim: the browser says it is an agent and does not prove it. |
tor | 25 | Privacy network. The network hides the person’s own address. |
vpn | 20 | Privacy network. The network hides the person’s own address. |
proxy | 20 | Privacy network. The network hides the person’s own address. |
anonymizer | 20 | Privacy network. The network hides the person’s own address. |
hosting | 20 | Privacy network. The address is in a hosting network, as most VPN exits are. |
network_changed | 20 | Privacy network. The browser was identified from one network and the event came from another. |
residential_proxy | 0 | Recorded, not scored yet. Privacy network. The address is listed as a residential proxy exit (network.residential_proxy): proxy traffic has left from it, which does not mean this visitor used a proxy. Most listed addresses are ordinary home and mobile lines. |
address_rate | 0 | Recorded, not scored yet. The visitor came from many different addresses in an hour or a day (visitor.addresses). |
account_new_browser | 20 | A fact for your login rule (account.new_browser, section 5), not a sign of abuse: this browser is new to an account another browser already presented. It flags nothing on its own. |
role_address | 20 | Weak tell, signups only. A signup with a role address (admin@, info@, billing@, support@, sales@ and the like) rather than a person’s. Never for a declared or verified agent. |
email_never_seen | 20 | Weak tell, breach history on. The address appears in no known data breach, as a newly made address often does. |
browser_tampered | 80 | Record-only: reported at weight 0 until you turn its scoring on under Settings, Advanced, Weights (we suggest watching it for two weeks first). The browser misreports what it is; environment.tamper names what disagreed. Only the checks behind tampered count; a name only anti_detect finds moves no score. |
virtual_machine | 25 | Weak tell. The browser runs in a virtual machine. Never for a declared or verified agent, whose operator runs it in a cloud. |
private_mode | 10 | Weak tell, with a new identity only. A private window added a new email or account; a private window alone is ordinary, and says nothing. |
private_signup | 50 | Recorded, and weighs 0 on the default weights until it is measured: set its weight under Settings, Advanced, Weights to score it. A signup, trial_start, redeem:CODE or payout came from a private window under a new email or account. With private_mode it reaches the default Verify line (60), never Flag: ask for a check your app already has. It never flags alone. |
language_change | 50 | Recorded, and weighs 0 on the default weights until it is measured: set its weight under Settings, Advanced, Weights to score it. A signup, trial_start, redeem (bare or redeem:CODE) or payout under a new email or account, on a home or office line, from a browser that matches one that did one of those on the same address in the last day under another email or account, except for its language or locale: one person changing the browser’s language per account. Never a merge, and never a flag alone. Counts once with the identity reasons (many_identities, probable_match, related_identities, confirmed_abuse). |
automation | 10 | Weak tell. The browser is automated and does not say it is an agent; an agent that says what it is gets no reason. |
voip_phone | 80 | Evidence. A non-fixed VoIP or toll-free number. |
prepaid_card | 80 | Evidence. A prepaid card. |
verification_failed | 40 | Evidence. A check you ran failed, its kind in the text. |
email_unverified | 20 | Evidence. Once you have sent email.verified for a day, an address new to the browser that no event verified. |
card_mismatch | 20 | Evidence. The card’s country is not the country of the event’s address. |
relay_email | 10 | Evidence. The sign-in provider’s private relay address. |
verified_id | 100 | Trust, subtracted. A passed id_document or liveness check. |
paid_history | 80 | Trust. plan paid with paid_since more than 30 days ago and no abuse report. |
wallet_card | 60 | Trust. Apple Pay, Google Pay, Link or a passed 3-D Secure. |
legit_outcome | 60 | Trust. You reported the visitor, one of its events or its account as legit. |
verified_phone | 40 | Trust. A mobile line verified by you or by a passed otp. |
card_on_file | 40 | Trust. A card that passed its CVC check. |
tenure | 40 | Trust. An account created more than 90 days ago. |
email_tenure | 20 | Trust, breach history on. The address was in a breach more than two years ago, so it has existed that long. |
no_token | note | The event came with no token, so the answer has no visitor or browser facts. It weighs nothing: the email, network and label reasons still apply; the bursts and rates, which count visitors, do not. |
token_expired | note | The token had expired, so the answer has no visitor facts. |
unknown_visitor | note | The token names no visitor of your workspace, so the answer has no visitor facts. |
no_identity | note | A signup, trial_start, redeem:CODE or payout came with neither email nor user_id, so Heedeo could not count the person’s accounts. Send one. |