botcheck
Experimental

botcheck for your site

One script tag. Ask whether this visitor's browser looks automated.

The same checks as the botcheck page, packaged for any website. The script runs in the visitor's browser, sends nothing anywhere and stores nothing. Your page decides what to do with the answer. Version botcheck-0.1.0.

1. Try it on this page

This page uses the hosted script itself. Move your mouse, click, or tap; the box updates after each click, tap or key.

Checking…


2. Quick start

Put this in your page's <head>, so it starts listening before the visitor does anything:

<script src="https://botcheck.traland.com/botcheck-0.1.0.js"
        integrity="sha384-fK7ONY3RqtwMSv2VAvqJKsveItwLUQskstqX5D5ABeyAInbTEC/ZZsDNHZUJsVX/"
        crossorigin="anonymous"></script>

Then, whenever you need to know (before a sign-up, a checkout, a vote):

const result = botcheck.check();
if (result.isBot) {
  // add friction: a second step, a CAPTCHA, a delay, a review queue
}

That's all. Loading the script creates window.botcheck and starts recording right away; there is nothing to configure.

Always-latest instead? https://botcheck.traland.com/botcheck.js follows each new version without an integrity hash. It is easier, but your site then runs whatever botcheck publishes next. Prefer the pinned file above with integrity: the browser refuses it if a single byte changes.

When is the answer ready?

3. Protect a form

One line. On submit, botcheck adds a hidden field botcheck holding the verdict, and when the verdict is flagged it stops the submit and calls your onBot:

botcheck.protect(document.querySelector('#signup'), {
  onBot(result) {
    document.querySelector('#signup-msg').textContent =
      'Your browser looks automated. Please try again or contact us.';
  }
});

Options: onBot(result, event); field (hidden field name, default "botcheck"); block: false to let flagged submits through and only label them, so your server decides. protect() returns a function that removes the protection.

A script calling form.submit() skips submit events in every browser, so it skips this check too. Your server can treat a missing botcheck field as a reason to look closer.

4. React as the visitor interacts

const stop = botcheck.onResult(result => {
  badge.hidden = !result.isBot;
});
// later: stop();

onResult runs after each click, tap and key press. It is the same object check() returns.

Single-page apps (React, Vue, Svelte, …)

Load the script once in index.html, then use the global anywhere:

// React
useEffect(() => window.botcheck?.onResult(r => setLooksAutomated(r.isBot)), []);

function onSubmit(e) {
  if (window.botcheck?.check().isBot) { e.preventDefault(); showExtraStep(); }
}

Call botcheck.reset() when you want a fresh start, for example after a route change, so evidence from earlier pages does not count.

5. Check again on your server

A verdict computed in the browser can be faked by the browser (see trust). For a decision that matters, send the raw evidence to your own server and run the same analysis there:

// browser
await fetch('/api/signup', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ form: formData, botcheck: botcheck.data() })
});
// Node server (save https://botcheck.traland.com/lib/botcheck.mjs into your project; no dependencies)
import { analyze } from './botcheck.mjs';

const result = analyze({ events: body.botcheck.events, env: body.botcheck.env });
if (result.verdict === 'flagged') { /* review, slow down, or ask for more */ }

Sending is your choice and your responsibility: the data holds pointer positions and timings, key classes (letter, delete, enter, …, never which key) and browser facts. Tell your visitors in your privacy notice.

6. API reference

CallReturnsWhat it does
botcheck.check()resultRuns every check on what has been recorded so far. Cheap; call it as often as you like.
botcheck.onResult(fn)unsubscribe functionCalls fn(result) after each click, tap or key press.
botcheck.protect(form, options)remove functionAdds the verdict to the form on submit; blocks flagged submits and calls onBot. See 3.
botcheck.data(){ events, env }A copy of the raw evidence, for analyze() on your server.
botcheck.analyze({ events, env })resultThe analysis itself, for evidence you hold.
botcheck.reset()Forgets everything recorded so far.
botcheck.stop() / botcheck.start()botcheckStop or resume listening. Started automatically on load.
botcheck.versionstringFor example "botcheck-0.1.0".

7. The result object

{
  isBot: true,                       // verdict === 'flagged'
  verdict: 'flagged',                // 'flagged' | 'suspicious' | 'clear' | 'inconclusive'
  version: 'botcheck-0.1.0',
  score: 0.9,                        // 0 to 1, combined weight of the failed checks
  headline: 'Strong signs of automation found',
  checks: [                          // every check, whether it ran or not
    { rule: 'webdriver_flag', layer: 'environment', status: 'fail',
      title: 'Automation flag is off (navigator.webdriver)',
      detail: 'navigator.webdriver reports true' },
    { rule: 'click_without_movement', layer: 'mouse', status: 'not_tested', … },
    …
  ],
  reasons: [ { rule, layer, severity, weight, summary, limitation } ],   // failed checks, with why each can be wrong
  layers: { … }, stats: { … }, notes: [ … ], limits: [ … ]
}
verdictMeaningSuggested use
flaggedStrong signs of automation (score ≥ 0.70).Add friction or review. isBot is true.
suspiciousSome checks failed, not enough to call it a bot.Log it; maybe a light extra step.
clearEvery check that could run passed.Carry on. It does not prove a person.
inconclusiveNothing failed, but too little interaction to judge.Ask again later, or treat as clear.

Each check's status is pass, fail or not_tested; the botcheck page shows all of them with explanations.

8. What it can and cannot tell you

9. Privacy

10. Content-Security-Policy

If your site sends a CSP header, allow the script's origin:

Content-Security-Policy: script-src 'self' https://botcheck.traland.com

botcheck needs nothing else: no connect-src, no inline code, no eval.

11. Versions and caching

FileChanges?Cache
/botcheck-0.1.0.jsNever. A new version gets a new file name.One year, immutable. Use with integrity.
/botcheck.jsAlways the newest version.Revalidated on every load.

Both files are served with Access-Control-Allow-Origin: *, which crossorigin="anonymous" and integrity need. The script is about 34 KB (12 KB compressed) and has no dependencies. You may also download it and serve it from your own domain.