Skip to content
Anti-Bot

What Is Input Provenance Detection?

Pim

Pim · Scrappey Research

July 30, 2026 5 min read

On this page

Input provenance detection determines whether an interaction came from a physical input device routed through the browser or from JavaScript running on the page. It is distinct from behavioural analysis, which asks whether a movement looks human: provenance asks the narrower and much harder question of where the event entered the system. The browser itself answers it, because the engine marks every event it generates from real hardware and refuses to let page script forge that mark.

Quick facts

Primary signalEvent.isTrusted - true only for events the browser generated from real input
Cannot be forgedisTrusted is a read-only engine-set flag; dispatchEvent always produces false
Secondary signalUser activation state, granted only by a trusted interaction
Ordering signalskeydown before keypress, pointerdown before click, input before change
Physics signalsPress dwell duration, pointer travel before a click, landing point dispersion

Event.isTrusted and the activation gate

Every DOM event carries a read-only isTrusted boolean. The engine sets it to true for events it created in response to a real device, and to false for any event constructed and dispatched by script. It is not a property page JavaScript can write, and dispatchEvent has no option to request a trusted event. That makes it the cleanest provenance signal in the platform: a click listener that only ever sees isTrusted === false is watching scripted clicks.

Sitting alongside it is user activation, the browser's internal record that the user has genuinely interacted with the page. Activation is what gates popups, fullscreen requests, clipboard writes, and audio playback. Because only a trusted event grants it, a probe can call an activation-gated API and observe whether the browser allows it - an indirect but very reliable read of the same fact. Scripted clicks never grant activation, so the gated call fails even though a click handler ran.

Automation frameworks that drive a browser through its debugging or automation protocol generate genuinely trusted events, because the input is injected below the JavaScript layer, at the same level the operating system would deliver it. This is the key structural difference between driving a browser and scripting a page, and it is why provenance checks alone do not identify protocol-driven automation - the ordering and physics checks below are what carry that load.

Ordering: real input arrives as a sequence, not an event

A real interaction produces a specific, spec-defined cascade of events. Script that dispatches only the event a handler listens for produces a fragment of that cascade, and the missing members are the signal.

InteractionReal sequenceCommon scripted shortcut
Typing a characterkeydown then keypress/beforeinput then input then keyupSet el.value directly - no events at all
Clicking a controlpointermove then pointerdown, mousedown, pointerup, mouseup, clickCall el.click() - a click with no pointer history
Choosing from a dropdowninput then change, both trustedAssign select.value, dispatch change only

The most conclusive member of this family is value without events. Setting input.value from script changes the field contents and fires nothing. A page that watches an input for the full keystroke cascade and then reads a populated field it never saw filled has observed something that cannot happen through a keyboard.

Physics: the durations and distances real hardware produces

The last layer measures the parts of an interaction that come from a human body and a physical device rather than from an event constructor.

  • Dwell duration - the time a mouse button or key is held. Real presses last tens of milliseconds; synthesised pairs frequently land in the same millisecond or at a suspiciously fixed interval.
  • Approach - a real pointer travels to a control before pressing it, producing pointermove events along a curved path with varying velocity. A click that appears with no prior movement toward the target has no approach.
  • Landing dispersion - repeated clicks on the same control land on slightly different pixels. Identical coordinates across several interactions indicate a computed target rather than a hand.
  • Event cadence - pointer events arrive on the input device's hardware sampling interval. Events spaced on a suspiciously regular software timer do not match any real device rate.

These overlap with behavioural detection, but the framing differs: behavioural models score how human a pattern looks, while provenance checks ask whether the structural preconditions of real input were met at all. The first is statistical and tunable; the second is closer to a proof.

For most public-data collection this whole layer is avoidable rather than solvable. Interaction-heavy flows are the expensive path; where the data is reachable from a request that does not require simulating a person, that path is both cheaper and free of this entire class of signal. A managed web data API that handles session and rendering concerns lets a scraper stay on it.

Code example

javascript
// What a page can observe about where an interaction came from.
const audit = { trusted: 0, scripted: 0, cascades: 0, dwellMs: [], points: [] };
let downAt = 0;

addEventListener('pointerdown', (e) => {
  downAt = e.timeStamp;
  audit.points.push([e.clientX, e.clientY]);
}, true);

addEventListener('click', (e) => {
  e.isTrusted ? audit.trusted++ : audit.scripted++;
  if (downAt) audit.dwellMs.push(Math.round(e.timeStamp - downAt));
  downAt = 0;
}, true);

// A value that appears without the keystroke cascade that should have produced it
const field = document.querySelector('input[name="q"]');
let sawInputEvent = false;
field?.addEventListener('input', (e) => { if (e.isTrusted) sawInputEvent = true; });

setTimeout(() => {
  const uniquePoints = new Set(audit.points.map(p => p.join(','))).size;
  console.log({
    trusted: audit.trusted,
    scripted: audit.scripted,                       // any > 0 means script dispatched clicks
    instantPresses: audit.dwellMs.filter(ms => ms < 8).length,
    landingDispersion: uniquePoints,                // 1 across many clicks is a computed target
    valueWithoutEvents: Boolean(field?.value) && !sawInputEvent
  });
}, 5000);

Next in How automation gets detected · 6 of 6

Provenance asks where input came from. Behaviour asks whether it looks human.

What Is Behavioural Bot Detection?

Related terms

Concept map

Concept map

How Input Provenance Detection connects

The terms most directly tied to this one. Hover a node to see its neighbours, click to preview, drag to rearrange.

0 terms · 0 connections
You are here · Anti-Bot
Building map…

Frequently asked questions

Can Event.isTrusted be set to true from JavaScript?

No. It is a read-only attribute the engine stamps on events it generates from real input, and the DOM provides no way to request a trusted event when dispatching one. Redefining the property on an event instance is possible but self-defeating, because doing so leaves an own data property on an object where the value should be inherited from the prototype - which native function integrity checks read as modification.

Do Playwright and Puppeteer produce trusted events?

Yes, when they drive input through the browser automation protocol rather than by executing JavaScript in the page. The input is injected below the JavaScript layer, so the engine treats it as real and marks it trusted. That is why isTrusted alone does not identify protocol-driven automation - the ordering, dwell, and approach signals are what distinguish it.

Why is setting an input value directly such a strong signal?

Because assigning to the value property changes the field contents without firing any events. A real keystroke always produces a cascade the page can observe. When a page sees a populated field but never saw a single trusted input event for it, the only explanation is that something wrote the value programmatically.

Last updated: 2026-07-30 · Facts last verified: 2026-07-30