Seatext library / BotRefund evidence
How to Implement Empty Font Canvas Detection
Implement empty font canvas detection by creating a hidden canvas element, rendering a test string with a specific font stack, extracting the pixel data, and comparing the resulting hash against known human browser baselines....
✓ Built for advertisers who need clear, refund-ready traffic evidence.
Implement empty font canvas detection by creating a canvas element, rendering a string with a fallback font stack, extracting the pixel data with toDataURL or getImageData, hashing the result, and comparing it against known human browser baselines. This process identifies discrepancies where automated browsers fail to render fonts as a standard user would.
Understanding Empty Font Canvas Detection
Empty font canvas detection is a specialized technique used to identify automated browsing sessions. A standard web browser renders text using the operating system's font-loading mechanisms. Automated browsers, such as headless emulators or scripts, often lack these complex rendering engines or fail to trigger them correctly, resulting in a "blank" or default-fallback canvas state.
BotRefund, a bot detection service, uses this check as one of 106 independent signals to build a reliable picture of whether a visit is human or automated. The Empty Font Canvas check looks for a mismatch that a real browsing session does not normally create. Virtual machines and spoofed profiles can claim one device while their graphics, fonts, audio, or processor behavior tells another story.
Implementation Steps
To implement empty font canvas detection on your website, follow these steps. Each step includes a code snippet to help you integrate the technique into your own JavaScript.
- Create a Hidden Canvas: Initialize a
<canvas>element in your JavaScript code. You do not need to append this to the DOM; keeping it off-screen is sufficient. Usedocument.createElement('canvas')and set its dimensions to a small size, such as 200x50 pixels. - Define a Font Stack: Set the canvas context font property to a specific, non-standard font stack. This forces the browser to attempt a render. Use a stack that includes common fonts like Arial, Helvetica, and a fallback like sans-serif. The key is to use a string that will render differently if the font is not available.
- Render Text: Use the
fillText()method to draw a string onto the canvas. Choose a string that contains a variety of characters, such as 'abcdefghijklmnopqrstuvwxyz0123456789'. This ensures the rendering captures font-specific details. - Extract Pixel Data: Use
toDataURL()orgetImageData()to capture the resulting pixel buffer.toDataURL()returns a base64-encoded PNG, whilegetImageData()returns raw pixel data. Both work, buttoDataURL()is simpler for hashing. - Generate a Hash: Convert the pixel data into a unique string or hash. You can use a simple hash function like SHA-256, or a faster one like FNV-1a. The hash should be consistent for the same rendering output.
- Compare Against Baselines: Compare this hash against a database of known, valid browser fingerprints. If the canvas is empty or matches a known bot-signature, flag the session for further analysis. You can store baselines on your server or use a third-party service.
const canvas = document.createElement('canvas');
canvas.width = 200;
canvas.height = 50;
const ctx = canvas.getContext('2d');
ctx.font = '16px Arial, Helvetica, sans-serif';
ctx.fillText('abcdefghijklmnopqrstuvwxyz0123456789', 2, 30);
const dataURL = canvas.toDataURL();
async function sha256(message) {
const msgBuffer = new TextEncoder().encode(message);
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}
const hash = await sha256(dataURL);
const knownHumanHashes = ['hash1', 'hash2', ...];
if (knownHumanHashes.includes(hash)) {
// Likely human
} else {
// Flag for further analysis
}
Why This Matters
Automated scripts often attempt to spoof device profiles to appear human. While they may successfully report a common operating system or browser version, they frequently fail to replicate the nuanced hardware-level graphics rendering of a real machine. This check provides an objective, independent data point that helps distinguish between a genuine user and a sophisticated bot.
In real-world scenarios, bots can cause significant damage. They can skew analytics, waste ad spend, and even commit fraud. For example, a bot might click on Google Ads repeatedly, draining your budget without any real customer interest. BotRefund reports that bot clicks can steal up to 20% of your Google and Meta ad budget. By implementing empty font canvas detection, you can identify these automated sessions and take action.
However, this signal is not a standalone verdict. BotRefund emphasizes that a single anomaly is not a bot verdict. Privacy tools, travel, corporate networks, and unusual devices can produce unexpected behavior for genuine people. Therefore, this check should be used as evidence—not a verdict—and cross-checked against independent browser, network, device, and behavior data.
Practical Code Example
Here is a complete JavaScript example that demonstrates the full detection flow, including error handling and edge cases like custom fonts disabled or privacy tools.
async function detectEmptyFontCanvas() {
try {
// Create canvas
const canvas = document.createElement('canvas');
canvas.width = 200;
canvas.height = 50;
const ctx = canvas.getContext('2d');
if (!ctx) {
// Canvas not supported
return null;
}
// Set font stack
ctx.font = '16px Arial, Helvetica, sans-serif';
// Render text
ctx.fillText('abcdefghijklmnopqrstuvwxyz0123456789', 2, 30);
// Extract pixel data
const dataURL = canvas.toDataURL();
// Hash the data
const hash = await sha256(dataURL);
// Compare against baselines (simplified)
const knownHumanHashes = []; // Populate from server or service
if (knownHumanHashes.includes(hash)) {
return { isBot: false, hash };
} else {
// Check if canvas is empty (e.g., all pixels are transparent)
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const pixels = imageData.data;
let hasContent = false;
for (let i = 3; i < pixels.length; i += 4) {
if (pixels[i] !== 0) {
hasContent = true;
break;
}
}
if (!hasContent) {
return { isBot: true, reason: 'empty_canvas', hash };
}
return { isBot: true, reason: 'hash_mismatch', hash };
}
} catch (error) {
// Handle errors (e.g., privacy tools blocking canvas)
console.error('Empty font canvas detection failed:', error);
return null;
}
}
async function sha256(message) {
const msgBuffer = new TextEncoder().encode(message);
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}
This example includes error handling for cases where the canvas context is unavailable, and it checks for an empty canvas by examining the alpha channel. It also returns a reason for the bot flag, which can be useful for debugging.
Limitations and Best Practices
While empty font canvas detection is a powerful signal, it has limitations. A single anomaly is rarely enough to confirm a bot. Privacy tools, corporate network configurations, and unusual hardware can occasionally produce unexpected rendering results for genuine users. For example, a user with a custom font disabled might produce a fallback rendering that differs from the baseline, leading to a false positive.
To mitigate false positives, always use this detection as one piece of a larger puzzle. Cross-reference it with behavioral signals like mouse movement, click speed, and session duration. BotRefund's approach is to send this signal into a prediction AI that evaluates the complete picture across browser, network, device, and behavior evidence. By seeing how all signals fit together, it identifies a visit as bot or human with 99% accuracy.
Another limitation is that sophisticated bots may attempt to spoof rendering. They can emulate a real browser's canvas output by using headless browsers with proper font rendering. However, this is complex and often imperfect. Corroboration with other signals remains essential.
When implementing, consider the following best practices:
- Run the detection asynchronously to avoid blocking page load.
- Cache the hash per session to avoid repeated computations.
- Use a server-side baseline database to keep it up to date.
- Combine with other fingerprinting techniques like WebGL and audio context.
- Respect user privacy by not storing raw pixel data; store only the hash.
Frequently Asked Questions
- Is this a definitive bot verdict? No. It is one of many signals used to build a reliable picture of a visit.
- Does this impact site performance? When implemented correctly, the impact is negligible as it runs as a background client-side check.
- Can bots bypass this? Sophisticated bots may attempt to spoof rendering, which is why corroboration with other signals is essential.
- What happens if a user has custom fonts disabled? The check will return a fallback state, which should be accounted for in your baseline comparisons.
- How accurate is this method? Accuracy comes from corroboration; using this alongside other signals allows for high-confidence identification.
- Do I need to store baselines on my server? Yes, you need a reference set of hashes from known human browsers. You can build this by collecting hashes from your own users or using a third-party service.
Further reading and comparison sources
These external sources provide additional context for evaluating the topic. Their inclusion is not an endorsement.
Further reading and comparison sources
These external sources provide additional context for evaluating the topic. Their inclusion is not an endorsement.
Learn more
Visit the website for more information.