Battery, Sensor, and Pointer APIs: The Overlooked Fingerprint Signals in 2026
Canvas, WebGL and audio get most of the attention in fingerprinting write-ups. A quieter set of browser APIs reports on the hardware underneath: the Battery Status API, motion and orientation sensors, the ambient light sensor, and the pointer and touch event streams. Each one tells a page something about the device class it is running on.
If you run a site, test across devices, or care about what your browser reveals, these signals are worth understanding. This post covers what each API exposes and how to audit what your own browser profiles report.
What We're Covering
- Which sensor and pointer APIs carry fingerprint data, and how a page queries them
- What each signal says about the device class
- A console script to audit what a profile reports
- Why overriding these APIs from a page script backfires
- How to read the results
The audio/font/hardware fingerprinting post covered AudioContext and navigator.hardwareConcurrency. This is the sequel: different signals, same idea. Every API a page can read is part of the picture it builds of a visitor.
Prerequisites
- A multi-account browser profile (JustBrowser, Multilogin, GoLogin, or any browser you want to inspect)
- Basic JavaScript, since you will be running console commands
- Some background on how sites read a browser (if not, start with how TLS and browser fingerprints get read)
- 20 minutes
Step 1: Understanding the Battery Status API
The Battery Status API was designed to let web apps adapt to low-battery situations: show a warning before the device dies, or reduce animation to save power. Reasonable use cases.
The API also exposes:
navigator.getBattery().then(battery => {
console.log('Level:', battery.level); // 0.0 to 1.0
console.log('Charging:', battery.charging); // true or false
console.log('Charging time:', battery.chargingTime); // seconds or Infinity
console.log('Discharging time:', battery.dischargingTime); // seconds or Infinity
});
That is your exact battery percentage, whether you are plugged in, how long until full, and how long until empty.
Privacy research published in 2015 showed that battery.level combined with the charge and discharge time estimates could act as a short-lived identifier across sites, and a 2016 Princeton crawl found tracking scripts reading the API in the wild. Firefox removed the API from web pages soon after. Chrome kept it.
For a site owner, the interesting part is the device-class signal:
Desktop without battery: charging: true, chargingTime: 0, dischargingTime: Infinity, level: 1.0 (the values the specification sets when there is no battery to report)
Plugged-in laptop: charging: true, chargingTime: [some number], level: [varies]
Mobile on battery: charging: false, dischargingTime: [some number], level: [varies]
A page that sees exactly that combination knows it is probably on a desktop machine without a battery. That takes a few lines of code and needs no hashing.
Open a profile's console and run the snippet. What do you get, and does it match the kind of machine the profile represents?
Step 2: DeviceOrientation and DeviceMotion
These APIs access a device's accelerometer, gyroscope, and magnetometer:
window.addEventListener('deviceorientation', event => {
console.log('Alpha (compass):', event.alpha); // 0-360 degrees
console.log('Beta (tilt forward):', event.beta); // -180 to 180
console.log('Gamma (tilt side):', event.gamma); // -90 to 90
});
window.addEventListener('devicemotion', event => {
console.log('Acceleration:', event.acceleration);
console.log('Rotation rate:', event.rotationRate);
});
Desktop browsers behave one of two ways:
- They do not fire the events at all
- They fire events with
nullvalues for alpha, beta and gamma
Mobile browsers on real devices fire constantly, because these sensors update many times per second. The values change as the phone tilts, and even a phone lying flat reports near-constant values with tiny fluctuations from sensor noise.
A page can add an event listener, wait half a second, and count how many events fired and whether the values vary. Zero events, or events with constant null values, point to a desktop. Real sensors fluctuate by fractions of a degree between readings, while a value that never changes at all does not look like physical hardware.
For what multi-account browsers do and don't change about signals like these, see our multi-account browser myths post.
Step 3: The Ambient Light Sensor API
This one is rarely available to pages at all:
if ('AmbientLightSensor' in window) {
const sensor = new AmbientLightSensor();
sensor.addEventListener('reading', () => {
console.log('Illuminance:', sensor.illuminance); // lux value
});
sensor.start();
}
The API reports ambient light in lux, which is useful for adapting screen brightness. Indoor lighting sits around 100 to 500 lux, outdoor shade around 1,000 to 5,000, and direct sunlight above 10,000.
No major browser exposes it by default. In Chrome it sits behind a flag on desktop and Android alike, and Firefox and Safari don't ship it. So in a default install, 'AmbientLightSensor' in window returns false, and the only information a page gets is that the API is absent, which is true of almost every visitor.
Step 4: Pointer and Touch Event Signals
Pointer metadata is one of the simplest device-class checks to implement, which is why it is so widespread.
document.addEventListener('pointerdown', event => {
console.log('Pointer type:', event.pointerType); // 'mouse', 'touch', or 'pen'
console.log('Width:', event.width); // contact geometry
console.log('Height:', event.height);
console.log('Pressure:', event.pressure); // 0.0 to 1.0
console.log('Tilt X:', event.tiltX); // pen angle
console.log('Tilt Y:', event.tiltY);
});
Desktop Chrome with a mouse: pointerType: 'mouse', pressure: 0.5 on mouse down, width: 1, height: 1, no tilt values.
Mobile Chrome with touch: pointerType: 'touch', pressure that varies where supported, width and height reflecting finger contact size (much larger than a mouse's 1 × 1), and tilt values that may populate.
A page can capture a handful of click events and read this metadata. There is no visual tell for the visitor; the script simply checks whether the click came from a finger or a mouse.
TouchEvent is more specific:
document.addEventListener('touchstart', event => {
const touch = event.touches[0];
console.log('Client X:', touch.clientX);
console.log('Client Y:', touch.clientY);
console.log('Radius X:', touch.radiusX); // horizontal radius of touch
console.log('Radius Y:', touch.radiusY); // vertical radius
console.log('Rotation angle:', touch.rotationAngle); // contact ellipse rotation
console.log('Force:', touch.force); // pressure
});
Desktop Chrome does not fire TouchEvents by default, unless touch emulation is switched on in DevTools. Real mobile touch events carry radiusX and radiusY values reflecting finger size, force reflecting pressure, and rotationAngle for elliptical contact.
Step 5: Auditing a Profile
Here is a diagnostic you can run in any browser console:
// Battery API check
if (navigator.getBattery) {
navigator.getBattery().then(b => {
console.log('Battery API:', {
charging: b.charging,
level: b.level,
chargingTime: b.chargingTime,
dischargingTime: b.dischargingTime
});
});
} else {
console.log('Battery API: not supported');
}
// Sensor API checks
console.log('DeviceOrientation:', 'ondeviceorientation' in window);
console.log('DeviceMotion:', 'ondevicemotion' in window);
console.log('AmbientLightSensor:', 'AmbientLightSensor' in window);
// Pointer characteristics (interact with page to trigger)
let pointerSample = null;
document.addEventListener('pointerdown', e => {
if (!pointerSample) {
pointerSample = {
type: e.pointerType,
width: e.width,
height: e.height,
pressure: e.pressure
};
console.log('Pointer sample:', pointerSample);
}
}, { once: true });
console.log('Click anywhere to capture pointer data...');
On a desktop profile, expect:
- Battery API returning desktop values
- No DeviceOrientation events, or null values
- Pointer events with
pointerType: 'mouse'
If you also have a real device of another class to hand, run the same script there and compare. The difference between the two outputs is exactly the device-class information a page can read.
Step 6: Why Overriding These APIs From a Page Script Backfires
Privacy-minded users sometimes try to hide these values with a userscript that overrides navigator.getBattery(). It tends to reveal more than it hides, for ordinary engineering reasons:
Timing. Native API calls complete in a fraction of a millisecond. A JavaScript wrapper adds measurable overhead, and a page can time the call.
Prototype integrity. The Battery Manager object has a specific prototype chain. Wrapped objects can differ in prototype or internal slots, and fingerprinting libraries inspect that.
Worker isolation. A userscript injects into the main thread. Dedicated Workers and Service Workers call the real API, so the two contexts disagree.
Each of those leaves a trace a page can see, so the override becomes one more distinctive signal. That is why identity parameters are best handled inside the browser engine rather than through page-level scripts. The WebGL fingerprinting deep-dive explains the same native-versus-extension split for GPU data.
JustBrowser is a multi-account browser built on a custom Chromium engine with native C++ integration, not an extension. Its identity parameters (canvas, WebGL, audio, fonts, screen, timezone, navigator and hardware values) are set in the engine, so each profile keeps its own consistent identity. Battery and sensor APIs are not modified, so they report the machine the profile runs on. A mobile profile can present Chrome on Android's client hints, memory range and touch screen, which is not the same as a real phone: its battery and sensor readings still come from your computer.
Common Questions While Auditing
The Battery API shows desktop values
Cause: The profile runs on a desktop computer and JustBrowser doesn't modify the Battery API, so this is the expected result.
What to do: Nothing on a desktop profile; this is consistent with a desktop device. A mobile profile can present Chrome on Android's client hints, memory range and touch screen, not a phone's battery or sensors, so if a workflow genuinely needs a mobile device class, use real mobile hardware or the device emulation in your browser's developer tools for QA. For ad verification workflows that need to check how ads render on phones, real devices are the reliable option.
DeviceOrientation events are not firing
Cause: Desktop hardware has no accelerometer or gyroscope.
What to do: Treat that as normal for desktop. If you are building a site that depends on orientation, test it on a real phone or in DevTools sensor emulation.
Pointer events show mouse characteristics
Cause: You are using a mouse on desktop hardware.
What to do: Nothing; this is correct. For touch-specific testing, use a touch-screen device or DevTools touch emulation, which is built for exactly this.
Individual signals look fine but the combination looks odd
Cause: Signals describe a single device. Battery, orientation, pointer and user agent should all describe the same kind of machine.
What to do: Keep each profile on one coherent device class. That is part of why per-profile identity settings exist.
Next Steps
If you work across several client accounts, run QA on multiple devices, or audit privacy, these APIs belong on your checklist.
-
Audit current profiles with the diagnostic script above and record what each reports for battery, orientation and pointer.
-
Compare against real devices if you have them. The delta shows how much device-class information a page can read.
-
Cross-check with public tools. CreepJS displays some of these signals and FingerprintJS documents others. Read them as readouts of what a page can see, not as scores. To monitor conversion on tested funnels without adding fingerprint surface, JustAnalytics is cookieless.
-
Re-check after browser updates. Browsers change which APIs they expose, so a periodic spot check keeps your picture current.
Sensor APIs get less attention than canvas or WebGL, but they are part of the data a page can collect. Knowing what they return is the first step to deciding what you are comfortable exposing.
Frequently Asked Questions
Does the Battery Status API still work in 2026?
On Chromium-based browsers, yes. Firefox removed it from web pages after privacy research in 2015 and 2016 showed it could help track users. Safari never shipped it. Chrome, Edge, Brave, and Chromium forks still expose navigator.getBattery(), which returns charging state, battery level, and estimated charge and discharge time. Any Chromium-based browser inherits this API, so a desktop profile reports desktop-style values.
How do sites use DeviceOrientation for fingerprinting?
DeviceOrientation reports alpha (compass), beta (tilt forward and back), and gamma (tilt left and right) from a device's accelerometer and gyroscope. Desktop browsers either do not fire this event or return null values. Mobile browsers return real sensor data. A site can therefore tell the broad device class from whether these events arrive.
What's the difference between PointerEvent and TouchEvent signals?
TouchEvent is mobile-specific (touchstart, touchmove, touchend). PointerEvent unifies mouse, touch, and pen input but includes a pointerType property that distinguishes them. Desktop Chrome fires PointerEvents with pointerType: 'mouse'. Mobile Chrome fires them with pointerType: 'touch'. Real touch events also carry radiusX, radiusY, and rotationAngle, which mouse events do not.
Does JustBrowser patch the battery or sensor APIs?
No. The engine's identity parameters cover canvas, WebGL, audio, fonts and screen. Battery and sensors are not on that list, and JustBrowser does not synthesize mobile sensor data. A mobile profile can present Chrome on Android's client hints, memory range and touch screen, but a page reading the battery or motion sensors still sees the machine the profile runs on.
Try JustBrowser
A multi-account browser for teams, built on a custom Chromium engine with native C++ integration, not an extension. Each profile is its own browser with its own cookies, storage, proxy and 40+ identity parameters, and teammates can share profiles. Local REST API and CDP for Playwright, Puppeteer, Selenium. Windows x64 and macOS (Apple Silicon). $9.99/month or $99.99/year. 7-day free trial, card required — cancel any time in the seven days and you are not charged. Unlimited profiles.
Get started → · How it differs from Multilogin / GoLogin / AdsPower
Ready to manage multiple accounts?
Seven days free, then $9.99/month — one plan, everything included.