Human Scoring¶
Read whether the current session comes from a real person at the moment your app makes a decision. The SDK keeps the score up to date on the device as the user interacts, so reading it is fast and never calls the network.
In this use case¶
| Page | What it covers |
|---|---|
| Human Scoring (this page) | Read the classification at a decision point, and name your views |
| User Consent | Ask the user for permission before anything is recorded |
| Sensitive Screens | Pause capture while a password or payment field is on screen |
| Custom Events | Mark moments in your app on the session timeline |
| Sessions | Start a new session on logout or account switch |
When to use it¶
- Before creating an account, submitting a form, or redeeming a promotion
- Before a step that bots abuse: posting, voting, adding to cart, claiming a reward
- To decide which branch of your own flow to take — let the user through, or add a challenge
Prerequisites¶
- The SDK is installed and initialized. See the Quick Start.
- The user has consented. Without consent nothing is recorded and the classification stays
insufficient. See User Consent. - The user has interacted with the app. Until the session has as many
actions as the
model asks for — 8 with the built-in model — it is not
sufficient and the result is
insufficient. Right after launch there are none. See Evidence.
Usage¶
Read the classification¶
import * as Dea from '@dea/react-native';
const {result} = await Dea.getClassification();
switch (result) {
case 'human':
// Allow the action.
break;
case 'bot':
// Block it, or take your strictest fallback.
break;
case 'undetermined':
// A score between the thresholds. Your app decides.
break;
case 'insufficient':
// Not enough evidence yet: too early to tell, not a bot. Your app decides.
break;
}
getClassification() reads the score and the loaded model's two thresholds in one snapshot, and
includes the touch the user made a moment ago:
result |
When |
|---|---|
'insufficient' |
Not enough evidence yet, or scoring not available. value is null |
'human' |
The score at or above the model's human threshold |
'bot' |
The score below the model's bot threshold |
'undetermined' |
A score at or above the bot threshold but below the human threshold |
The thresholds come from the loaded model only — there is no threshold to configure in your app.
modelVersion names the model they come from; 0 is the model built into the SDK. A model that
sets no human threshold has one bar: a score at or above its bot threshold is 'human'. See
How scoring works.
Tell "not enough evidence yet" apart from an uncertain score¶
import * as Dea from '@dea/react-native';
const c = await Dea.getClassification();
if (c.result === 'insufficient') {
// Fewer actions than the model asks for (8 with the built-in model). Let them keep going,
// or ask again later.
console.log(`scored on ${c.actions} actions so far`);
} else if (c.result === 'undetermined') {
// Enough evidence, and the score is at or above c.botThreshold but below c.humanThreshold.
}
Handle a model without thresholds¶
If the loaded model carries no bot threshold, getClassification() rejects with code
threshold_missing, and error.userInfo.modelVersion names the model. This is a model problem to
report to DEA support, not a classification:
try {
const {result} = await Dea.getClassification();
} catch (error: any) {
if (error.code === Dea.THRESHOLD_MISSING) {
// Take your fallback path, and report error.userInfo?.modelVersion.
}
}
Check that the SDK can score¶
init resolves with whether scoring started:
const started = await Dea.init({appId: 'YOUR_APP_ID'});
if (!started.ready) {
console.warn('DEA cannot score:', started.coreFault);
}
When ready is false, the SDK still captures but cannot score, and
getClassification() stays insufficient.
Name your views¶
The SDK records which kind of control each touch landed on — a button, a link, a text field, a
scrolling list — and a short name for it. Native apps get this for free; React Native views are
created in code, and a Pressable is a plain view the platform does not know is pressable. Left
alone, every press in a React Native app reads as a touch on the background, which weakens the
evidence the classification is built on.
Name the controls that matter with deaTarget(name, bucket), and spread the props it returns onto
the view:
import {Pressable, Text} from 'react-native';
import {deaTarget} from '@dea/react-native';
export function BuyButton({onPress}: {onPress: () => void}) {
return (
<Pressable {...deaTarget('checkout-buy', 'button')} onPress={onPress}>
<Text>Buy now</Text>
</Pressable>
);
}
bucket |
Use it for | Sets accessibilityRole to |
|---|---|---|
'button' |
Buttons and anything that acts on press | button |
'link' |
Navigation: list rows, cards, links | link |
'input' |
Custom inputs | search |
'canvas' |
Images, drawing surfaces, game boards | image |
'scroller' |
Custom scrolling containers | scrollbar |
| omitted | Only name the view | — |
deaTargetsetsnativeIDandtestIDto the name, andaccessibilityRoleto match the bucket. If the view already has its owntestIDoraccessibilityRole, the one spread last wins.- The name is hashed on the device. Use a control's name, never its content —
'story-row', not the story's headline or the user's email. - Names and roles are read from the touched view upward, so the
Textinside a named button counts as that button. TextInputfields are recognized without any props, and so are scrolling containers (ScrollView,FlatList) whose content actually scrolls.- A view that already has an
accessibilityRolesuch asbuttonorlinkis bucketed by it even withoutdeaTarget.
Note: The SDK offers no way to check what your presses resolve to with a production
appId.countTargetsandgetTargetTally()count nothing on a production account: the tally always resolves an empty object.
Examples¶
Gate a sign-up button¶
import {useState} from 'react';
import {ActivityIndicator, Pressable, Text} from 'react-native';
import * as Dea from '@dea/react-native';
export function SignUpButton({onSignUp, onChallenge}: {onSignUp: () => void; onChallenge: () => void}) {
const [checking, setChecking] = useState(false);
const onPress = async () => {
setChecking(true);
try {
const {result} = await Dea.getClassification();
if (result === 'human') {
onSignUp();
} else {
onChallenge(); // 'bot', 'undetermined' or 'insufficient': e.g. show a captcha
}
} catch {
onChallenge(); // the SDK was not initialized, or the model has no thresholds
} finally {
setChecking(false);
}
};
return (
<Pressable {...Dea.deaTarget('sign-up', 'button')} onPress={onPress} disabled={checking}>
{checking ? <ActivityIndicator /> : <Text>Create account</Text>}
</Pressable>
);
}
API¶
| Member | Returns | Description |
|---|---|---|
getClassification() |
Promise<Classification> |
human, bot, undetermined or insufficient, with the score, the evidence and the model's thresholds behind it |
deaTarget(name, bucket?) |
DeaTargetProps |
Props that name a view and set its kind |
getTargetTally() |
Promise<Record<number, number>> |
Presses per TargetBucket code, with countTargets; always empty on a production appId |
Related¶
- User Consent — nothing is scored before consent
- How scoring works
- Options:
countTargets - API Reference