Seatext library / BotRefund evidence
How to Validate WebGL Detection Rules Without Exposing Them to Bot Operators
Validate WebGL detection rules safely by running shadow-mode logging in staging, generating synthetic traffic with known automation tools, and running A/B tests against live traffic without blocking. This approach keeps your detection logic hidden...
✓ Built for advertisers who need clear, refund-ready traffic evidence.
Start with a staging environment that mirrors production traffic patterns. Enable shadow-mode logging so every WebGL texture constraint check records its verdict without acting on it. Feed the staging endpoint synthetic visits from Puppeteer, Playwright, and Selenium so you see how headless browsers and spoofed profiles behave. Finally, run an A/B test where a small slice of real traffic passes through the new rule in monitor-only mode; compare its signals against the other 105 independent checks BotRefund uses before you ever flip a blocking switch.
Why Secure Validation Matters
Bot operators study detection code the moment it ships. If you test a new WebGL rule in production with blocking turned on, you hand them a live probe: they can iterate spoofing techniques until the rule stops firing, then deploy the bypass at scale. Shadow-mode logging and synthetic traffic keep the rule invisible while you gather evidence.
BotRefund treats each WebGL texture constraint mismatch as evidence, not a verdict. The system cross-checks that signal against independent browser, network, device, and behavior data before the prediction AI weighs the complete pattern. Your validation workflow should mirror that philosophy: collect the signal in isolation, then verify it correlates with other anomalies before you trust it.
How the WebGL Texture Constraint Check Works
The WebGL Texture Constraint is one of 106 independent checks BotRefund runs. It looks for a mismatch between the device a browser claims to be and the graphics, font, audio, or processor behavior that device actually exhibits. Virtual machines and spoofed profiles often claim one hardware profile while their WebGL rendering reveals another.
A normal browser reports hardware, graphics, fonts, and operating-system details that naturally fit together for that device. When the texture constraint check flags a mismatch, BotRefund keeps the signal as evidence and cross-checks it against other signals. Privacy tools, travel, corporate networks, and unusual devices can produce unexpected behavior for genuine people, so a single anomaly never triggers a block on its own.
Prerequisites for Safe Testing
- A staging environment that receives a representative sample of production traffic (same CDN, same headers, same TLS termination).
- Access to the detection rule configuration so you can toggle shadow-mode logging without code deploys.
- Automation tooling: Puppeteer, Playwright, Selenium, and at least one residential proxy pool for synthetic traffic generation.
- A logging pipeline that captures every WebGL check result alongside the other 105 signals, session IDs, and timestamps.
- An A/B testing framework that can route a configurable percentage of live traffic to the new rule in monitor-only mode.
Step-by-Step Validation Workflow
- Deploy the rule in shadow mode on staging. Configure the WebGL texture constraint check to log its verdict (match/mismatch) for every session but take no enforcement action. Verify logs show the expected fields: session ID, user agent, WebGL renderer string, texture constraint result, and the other 105 signal values.
- Generate synthetic bot traffic. Script visits using Puppeteer, Playwright, and Selenium with default and hardened configurations. Include headless Chrome, headless Firefox, and common anti-detect browser profiles. Route a subset through residential proxies to mimic the residential proxy expansion trend fraud networks use. Record how each tool triggers the texture constraint check.
- Generate synthetic human traffic. Use the same automation tools but add human-like behavior: randomized mouse curvature, click intervals, scroll patterns, and think times. This helps you measure false-positive rates when real users exhibit unusual but legitimate device configurations.
- Analyze signal correlation. For every session where the texture constraint flags a mismatch, check whether other signals (ghost click detection, honeypot trap interactions, robotic linear mouse movements, absence of humanlike mouse tremor, superhuman input speed, grid-aligned movement patterns, absence of clicks or scrolling, unnatural session durations) also fire. BotRefund's AI prediction weighs the complete pattern; your validation should do the same.
- Run an A/B test on production traffic. Route 1–5% of live traffic through the new rule in monitor-only mode. Compare the mismatch rate, correlation with other signals, and downstream conversion metrics between the test and control groups. Do not block; only log.
- Set a promotion threshold. Define a minimum correlation coefficient (e.g., texture constraint mismatch + ≥2 other behavioral anomalies in >90% of confirmed bot sessions) and a maximum false-positive rate (e.g., <0.1% of converting human sessions). Only promote the rule to blocking when both thresholds hold for at least two full traffic cycles (weekday/weekend).
Synthetic Traffic Generation Code Example
The following Playwright script demonstrates synthetic traffic generation for WebGL texture constraint validation. It launches a headless browser, navigates to your staging endpoint, executes the WebGL texture constraint check, and logs the WebGL renderer string and texture constraint result.
const { chromium } = require('playwright');
const fs = require('fs');
const path = require('path');
async function runWebGLValidation({
stagingUrl = 'https://staging.example.com',
headless = true,
proxy = null, // e.g., 'http://user:pass@residential-proxy:8080'
userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
outputDir = './webgl-validation-logs'
} = {}) {
// Ensure output directory exists
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
const browser = await chromium.launch({
headless,
args: [
'--disable-blink-features=AutomationControlled',
'--disable-webgl', // Test with WebGL disabled
'--disable-webgl2'
],
proxy: proxy ? { server: proxy } : undefined
});
const context = await browser.newContext({
userAgent,
viewport: { width: 1366, height: 768 },
// Emulate a real device profile
deviceScaleFactor: 1,
isMobile: false,
hasTouch: false
});
// Enable console logging from the page
const page = await context.newPage();
page.on('console', msg => {
if (msg.type() === 'log' && msg.text().includes('[WEBGL_VALIDATION]')) {
console.log(`[PAGE LOG] ${msg.text()}`);
}
});
// Inject validation script before page load
await page.addInitScript(() => {
// Override WebGL context creation to capture renderer string
const originalGetContext = HTMLCanvasElement.prototype.getContext;
HTMLCanvasElement.prototype.getContext = function (type, attrs) {
const ctx = originalGetContext.call(this, type, attrs);
if (type === 'webgl' || type === 'webgl2' || type === 'experimental-webgl') {
const originalGetParameter = ctx.getParameter;
ctx.getParameter = function (pname) {
if (pname === this.RENDERER || pname === this.VENDOR || pname === this.VERSION) {
const value = originalGetParameter.call(this, pname);
console.log(`[WEBGL_VALIDATION] WebGL ${type.toUpperCase()} ${pname === this.RENDERER ? 'RENDERER' : pname === this.VENDOR ? 'VENDOR' : 'VERSION'}: ${value}`);
return value;
}
return originalGetParameter.call(this, pname);
};
}
return ctx;
};
});
try {
console.log(`[WEBGL_VALIDATION] Navigating to ${stagingUrl}`);
await page.goto(stagingUrl, { waitUntil: 'networkidle', timeout: 30000 });
// Wait for BotRefund detection script to load and execute
await page.waitForTimeout(2000);
// Execute WebGL texture constraint check manually
const webglResult = await page.evaluate(() => {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
if (!gl) {
return { supported: false, error: 'WebGL not supported' };
}
// Get renderer info
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
const renderer = debugInfo ? gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) : gl.getParameter(gl.RENDERER);
const vendor = debugInfo ? gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) : gl.getParameter(gl.VENDOR);
// Texture constraint test: max texture size vs reported device class
const maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);
const maxCubeMapTextureSize = gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE);
const maxRenderbufferSize = gl.getParameter(gl.MAX_RENDERBUFFER_SIZE);
// Simple heuristic: mobile devices typically have lower limits
const isMobileUA = /Mobile|Android|iPhone|iPad/.test(navigator.userAgent);
const expectedMaxTexture = isMobileUA ? 4096 : 8192; // rough baseline
const textureMismatch = maxTextureSize < expectedMaxTexture * 0.5; // significant deviation
return {
supported: true,
renderer,
vendor,
maxTextureSize,
maxCubeMapTextureSize,
maxRenderbufferSize,
textureMismatch,
userAgent: navigator.userAgent,
timestamp: new Date().toISOString()
};
});
// Log results
const logEntry = {
testConfig: { stagingUrl, headless, proxy, userAgent },
webglResult,
timestamp: new Date().toISOString()
};
const logFile = path.join(outputDir, `webgl-validation-${Date.now()}.json`);
fs.writeFileSync(logFile, JSON.stringify(logEntry, null, 2));
console.log(`[WEBGL_VALIDATION] Results written to ${logFile}`);
console.log(`[WEBGL_VALIDATION] Renderer: ${webglResult.renderer}`);
console.log(`[WEBGL_VALIDATION] Texture mismatch: ${webglResult.textureMismatch}`);
console.log(`[WEBGL_VALIDATION] Max texture size: ${webglResult.maxTextureSize}`);
return logEntry;
} catch (error) {
console.error(`[WEBGL_VALIDATION] Error: ${error.message}`);
const errorLog = {
testConfig: { stagingUrl, headless, proxy, userAgent },
error: error.message,
timestamp: new Date().toISOString()
};
const errorFile = path.join(outputDir, `webgl-validation-error-${Date.now()}.json`);
fs.writeFileSync(errorFile, JSON.stringify(errorLog, null, 2));
throw error;
} finally {
await browser.close();
}
}
// Example usage for different bot profiles
async function runValidationSuite() {
const profiles = [
{ name: 'headless-chrome-default', headless: true },
{ name: 'headless-chrome-no-webgl', headless: true, args: ['--disable-webgl', '--disable-webgl2'] },
{ name: 'headed-chrome', headless: false },
{ name: 'mobile-emulation', headless: true, userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15', viewport: { width: 390, height: 844 }, isMobile: true, hasTouch: true }
];
for (const profile of profiles) {
console.log(`\n=== Running profile: ${profile.name} ===`);
try {
await runWebGLValidation({
stagingUrl: 'https://staging.example.com',
headless: profile.headless,
userAgent: profile.userAgent,
outputDir: `./webgl-validation-logs/${profile.name}`
});
} catch (e) {
console.error(`Profile ${profile.name} failed:`, e.message);
}
}
}
// Run if executed directly
if (require.main === module) {
runValidationSuite().catch(console.error);
}
module.exports = { runWebGLValidation, runValidationSuite };
This script tests multiple browser profiles (headless Chrome, headed Chrome, mobile emulation) and captures the WebGL renderer string, vendor, and texture constraint results. Run it against your staging endpoint with shadow-mode logging enabled. The JSON output files can be ingested by your logging pipeline for correlation analysis alongside the other 105 signals.
Common Mistakes to Avoid
- Testing only in staging with synthetic traffic. Staging never replicates the full diversity of real devices, corporate proxies, privacy extensions, and network conditions. Always validate with a live A/B slice.
- Treating a single WebGL anomaly as a block decision. BotRefund's architecture explicitly avoids this: "A single anomaly is not a bot verdict." Your validation must confirm the signal adds predictive value when combined with other evidence.
- Exposing the rule logic in client-side code during testing. Keep the WebGL check server-side or in an obfuscated module that only loads after feature detection. Bot operators scrape staging endpoints for new detection scripts.
- Skipping the correlation analysis. A rule that fires on 30% of bots but also 5% of humans is worse than useless if you don't know which 5% and why. Map every mismatch to the full 106-signal vector.
Verification Checklist Before Promotion
- Shadow-mode logs show the texture constraint check executes on 100% of staged sessions without errors.
- Synthetic bot traffic from at least three automation frameworks triggers the mismatch in >80% of runs.
- Synthetic human traffic with behavioral emulation triggers the mismatch in <0.5% of runs.
- Live A/B test shows mismatch correlation with ≥2 other behavioral signals in >90% of sessions that your existing model already classifies as bots.
- False-positive rate on converting human sessions (completed purchase, form submit, or qualified lead) stays below your defined threshold for two full traffic cycles.
- Rollback plan documented: a single config flag disables the rule without redeploy.
Limitations and When This Advice Does Not Apply
This workflow assumes you control the detection rule deployment and can run shadow-mode logging. If your WebGL check lives in a third-party script you cannot modify, you are limited to observing its output in production and cannot safely iterate. The approach also assumes your traffic volume supports a statistically meaningful A/B slice; sites with under 10,000 daily sessions may need longer test windows or higher traffic allocation.
The correlation thresholds (80% bot detection, 0.5% human false positive, 90% multi-signal correlation) are starting heuristics. Adjust them based on your risk tolerance: brand-protection campaigns may accept higher false positives; performance-marketing campaigns may require stricter thresholds.
Key Facts
| Fact | Detail |
|---|---|
| WebGL Texture Constraint role | One of 106 independent checks BotRefund uses to build a reliable picture of whether a visit is human or automated |
| Signal treatment | Kept as evidence—not a verdict—and cross-checked against independent browser, network, device, and behavior data |
| Accuracy claim | BotRefund identifies a visit as bot or human with 99% accuracy by evaluating the complete pattern across all signals |
| Common bot automation tools | Puppeteer, Selenium, Playwright (headless browsers) |
| Behavioral signals correlated | Ghost click detection, honeypot trap interactions, robotic linear mouse movements, absence of humanlike mouse tremor, superhuman input speed (<1ms), grid-aligned movement patterns, absence of clicks or scrolling, unnatural session durations |
| Fraud trend relevance | AI-powered bot telemetry now simulates human mouse curvature, click intervals, and page scrolling to bypass simple pattern-detection rules |
FAQ
How long should the A/B test run before I trust the results?
Run until you have at least 1,000 sessions in the test bucket that your existing model classifies as bots, and at least 10,000 human sessions with conversions. For most sites this means 7–14 days covering weekday and weekend cycles.
Can I validate a WebGL rule without a staging environment?
Not safely. Without staging you cannot run synthetic traffic or shadow-mode logging without exposing the rule to live bot operators. If you lack staging, deploy the rule in monitor-only mode on a low-traffic subdomain or a separate test property first.
What if my synthetic traffic doesn't trigger the mismatch but real bots do?
Your synthetic tooling is missing the evasion techniques real fraud networks use. Add residential proxy routing, anti-detect browser profiles, and AI-generated behavioral noise (mouse curvature, scroll jitter) to your test harness. The fraud trend toward AI-powered bot telemetry means static automation frameworks quickly become obsolete.
Should I block on WebGL mismatch alone for high-risk endpoints like login or checkout?
No. BotRefund's architecture explicitly avoids single-signal verdicts even for high-risk flows. Instead, use the mismatch to step up authentication (challenge, MFA, rate limit) while you continue logging. Blocking on one signal creates a bypass target.
How do I know the 105 other signals are firing correctly during validation?
Your logging pipeline must capture the full 106-signal vector for every session. Build a dashboard that shows, for each signal, the fire rate on confirmed bots, confirmed humans, and unknown traffic. A signal that never fires or fires on everyone is broken—fix it before you trust any correlation analysis.
What is the cost of running this validation workflow?
Infrastructure cost is minimal: a staging environment you already maintain, synthetic traffic scripts (engineering time), and an A/B framework (often built into your CDN or experimentation platform). The real cost is engineering time to instrument full-signal logging and correlation analysis. Budget 1–2 sprints for a team that owns the detection pipeline.
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.