Skip to content

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 —
  • deaTarget sets nativeID and testID to the name, and accessibilityRole to match the bucket. If the view already has its own testID or accessibilityRole, 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 Text inside a named button counts as that button.
  • TextInput fields are recognized without any props, and so are scrolling containers (ScrollView, FlatList) whose content actually scrolls.
  • A view that already has an accessibilityRole such as button or link is bucketed by it even without deaTarget.

Note: The SDK offers no way to check what your presses resolve to with a production appId. countTargets and getTargetTally() 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