Skip to content

Troubleshooting

init throws TypeError: crypto.randomUUID is not a function

Cause: The page is not a secure context — it is served over plain HTTP from a host other than localhost — or the browser is older than the minimum versions. Browsers provide crypto.randomUUID() only on secure pages, and the SDK needs it to start a session.

Solution: Serve the page over HTTPS, or from localhost during development. To keep a page working in a browser the SDK does not support, call init inside a try block.

getClassification() always returns 'insufficient'

Cause: One of the following:

  1. The session has fewer actions than the model asks for. With the built-in model, a desktop browser needs 20, from at least two kinds of input (pointer, scroll, keyboard) with at least three each, and a browser on a phone or tablet needs 8 — see Evidence. A user who only clicks gives one kind of input: on a desktop, the result stays 'insufficient' until they also scroll or type.
  2. Capture was paused and never resumed.
  3. The consent gate is on (requireConsent: true) and setConsent(true) was never called.
  4. The scoring engine failed to load: dea.error is set.

Solution: Check each in turn:

import { DeaSdk } from '@dea/sdk';

const dea = DeaSdk.current!;
console.log(dea.consent.decision);    // 'granted' is needed when requireConsent is on
await dea.ready();                    // waits for consent when requireConsent is on
console.log(dea.error);               // not null: the engine failed to load — see below
console.log(dea.getClassification()); // result, value, actions and both thresholds

Make sure every pause() has a matching resume().

getClassification() always returns 'undetermined'

Cause: There are enough actions, and the score lies between botThreshold and humanThreshold. A model that sets no humanThreshold has no such band: a score is then 'bot' or 'human'.

Solution: This is the model's answer for this user, not a fault. Take your fallback path, such as a challenge or a manual review.

getClassification() throws ThresholdMissing

Cause: The loaded model has no bot threshold. Its version is on the error's modelVersion.

Solution: Catch the error where you read the classification and take your fallback path, as for 'undetermined'. Report the modelVersion to DEA: the model comes from DEA, not from your configuration.

import { DeaSdk, ThresholdMissing, type ClassificationResult } from '@dea/sdk';

function readResult(): ClassificationResult {
  try {
    return DeaSdk.current?.getClassification().result ?? 'insufficient';
  } catch (error) {
    if (error instanceof ThresholdMissing) return 'undetermined';
    throw error;
  }
}

dea.error is set after ready()

Cause: The SDK's WebAssembly scoring engine could not load. dea.error is a CoreLoadError; its reason says why:

reason Cause
'core_not_loaded' The engine could not be fetched or compiled. Usually a Content Security Policy blocks it, or coreUrl points at a file that cannot be fetched
'core_abi_mismatch' coreUrl points at an engine from a release this package cannot read
'core_wrong_profile' coreUrl points at the engine for the other profile (desktop or mobile)

Solution: Read dea.error.message, which names the blocked request or the mismatch, and the browser console, which reports a Content Security Policy violation.

  • Blocked by CSP: allow data: in connect-src (or the coreUrl you set), and 'wasm-unsafe-eval' in script-src. See Content Security Policy.
  • 404 or a mismatch: check coreUrl, or remove it to use the engine embedded in the package.

The staging package also logs this as sdk.core_failed, with the same reason.

init options are ignored

Cause: init was already called. A later call returns the running instance and ignores its options.

Solution: Call init once, as early as possible. If you really need different options, call DeaSdk.current?.dispose() first.

attest() returns unsupported

Cause: The browser could not create a signing key: it refuses IndexedDB (common in private browsing), or it has no Web Crypto.

Solution: Nothing in the page can create the key in such a browser. Scoring still works; only attestation and ad proofs are unavailable.

attest() returns failed

Cause: One of the following:

  1. A production appId with no appKey: the SDK makes no request.
  2. DEA refused the key registration — for example because appKey is not the key issued with this appId.
  3. DEA could not be reached: no network, or your Content Security Policy blocks https://poh-api.dep-protocol.com.

Solution: Check DeaSdk.current?.appKey is the key DEA issued with your appId — see Accounts and keys — and allow DEA in your Content Security Policy. The staging package logs the refusal as attest.failed with a reason, such as app_key_invalid or app_key_missing. A failed result is not cached: call attest() again to retry.

ads.session_opened() resolves null

Cause: One of the following:

  1. The classification is still 'insufficient': no proof is signed until it is not, so the impression is not recorded. An ad shown at page load, before the user has interacted, is in this case.
  2. No appKey, or no browser key.
  3. A browser key DEA has not registered.
  4. DEA could not be reached.

Solution: For cause 1, check getClassification().result before you open the ad session, and open it once it is no longer 'insufficient', while the ad is still on screen. Otherwise, call attest() and check it answers 'passed' before opening an ad session. To learn which cause it was, compare ads.counts before and after the call — see Find out why no ad session opened.

ad.click() returns false

Cause: One of the following:

  1. end() was already called, or a click was already accepted for this ad session.
  2. An element was passed to session_opened(), and no press or release landed inside it before the click.

Solution: For cause 2, make sure the element passed to session_opened() is the one the ad renders into, and that it is visible and not zero-sized. Presses inside an iframe never reach the SDK: for a creative rendered in an iframe, open the session without an element. Presses made while capture is paused, or before the user consented, are not seen either.

Nothing appears in the console

Cause: The production package carries no log.

Solution: While you integrate, install the staging package — see Verify your setup. It logs INFO, WARN and ERROR entries.