NuData Security's behavioral biometrics can block even the most sophisticated automation attempts on major platforms like Ticketmaster, Kohl's, and other enterprise websites. This technical guide breaks down exactly how NuData tracks users and provides battle-tested methods to bypass their detection.
NuData's Detection Architecture {#nudata-detection-architecture}
NuData, now a Mastercard company, monitors over 650 billion behavioral events annually and uses machine learning models to detect bot-like patterns across multiple use cases including login monitoring, transaction verification, and account hijacking. The system tracks three main data layers:
Behavioral Biometrics: Mouse movements, typing patterns, scrolling behavior, and device angle information Device Fingerprinting: Canvas fingerprints, WebGL parameters, screen resolution, installed fonts Network Intelligence: IP reputation, geolocation consistency, Trust Consortium data
The most critical component for bypassing NuData is the nds-pmd
parameter - a session token that validates your interaction as legitimate. Without this token properly generated and submitted, your requests will be flagged immediately.
Step 1: Extract the NuData Script ID {#step-1-extract-nudata-script-id}
First, you need to locate NuData's JavaScript on the target website. The script is typically hosted at *.nudatasecurity.com
and contains the configuration needed to generate valid tokens.
Request-Based Extraction
For lightweight operations, extract the script ID directly from the page HTML:
import re
import requests
from bs4 import BeautifulSoup
def extract_nudata_script(url):
"""
Extracts NuData script ID from target webpage
Returns tuple of (script_id, session_params)
"""
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5',
'Accept-Encoding': 'gzip, deflate, br',
'DNT': '1',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1'
}
response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.text, 'html.parser')
# Pattern 1: Direct script tag
script_pattern = re.compile(
r'<script[^>]*\s+src=["\'].*?nudatasecurity\.com/([^"\']+)["\'][^>]*>',
re.IGNORECASE
)
# Pattern 2: Dynamic script loading
dynamic_pattern = re.compile(
r'loadScript\(["\'].*?nudatasecurity\.com/([^"\']+)["\']',
re.IGNORECASE
)
script_match = script_pattern.search(response.text)
if not script_match:
script_match = dynamic_pattern.search(response.text)
if script_match:
script_id = script_match.group(1)
# Extract session parameters if available
session_params = {}
session_pattern = re.compile(r'nds[_-]?session[_-]?id["\']?\s*[:=]\s*["\']([^"\']+)')
session_match = session_pattern.search(response.text)
if session_match:
session_params['session_id'] = session_match.group(1)
return script_id, session_params
return None, {}
# Usage
script_id, params = extract_nudata_script('https://www.ticketmaster.com')
print(f"Script ID: {script_id}")
print(f"Session params: {params}")
Browser-Based Extraction with Playwright
For sites with heavy JavaScript obfuscation, use a real browser:
from playwright.sync_api import sync_playwright
import json
def extract_nudata_browser(url):
"""
Uses Playwright to extract NuData configuration from runtime
"""
with sync_playwright() as p:
# Launch with stealth settings
browser = p.chromium.launch(
headless=False, # Run headed for better evasion
args=[
'--disable-blink-features=AutomationControlled',
'--disable-dev-shm-usage',
'--no-sandbox',
'--disable-web-security',
'--disable-features=IsolateOrigins,site-per-process'
]
)
context = browser.new_context(
viewport={'width': 1920, 'height': 1080},
user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
locale='en-US',
timezone_id='America/New_York'
)
# Inject evasion scripts before page loads
context.add_init_script("""
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
// Override chrome detection
window.chrome = {
runtime: {},
loadTimes: function() {},
csi: function() {}
};
// Spoof plugins
Object.defineProperty(navigator, 'plugins', {
get: () => [1, 2, 3, 4, 5]
});
""")
page = context.new_page()
# Intercept NuData script requests
nudata_config = {}
def handle_response(response):
if 'nudatasecurity.com' in response.url:
nudata_config['script_url'] = response.url
# Extract script ID from URL
import re
match = re.search(r'nudatasecurity\.com/([^?]+)', response.url)
if match:
nudata_config['script_id'] = match.group(1)
page.on('response', handle_response)
# Navigate and wait for NuData to load
page.goto(url, wait_until='networkidle')
page.wait_for_timeout(3000) # Give NuData time to initialize
# Extract runtime configuration
try:
runtime_config = page.evaluate("""
() => {
// Look for NuData objects in window
const config = {};
if (window.ndsObj) config.ndsObj = window.ndsObj;
if (window.nds) config.nds = window.nds;
if (window.__NDS_CONFIG__) config.__NDS_CONFIG__ = window.__NDS_CONFIG__;
return config;
}
""")
nudata_config['runtime'] = runtime_config
except:
pass
browser.close()
return nudata_config
# Usage
config = extract_nudata_browser('https://www.ticketmaster.com')
print(json.dumps(config, indent=2))
Step 2: Generate Valid NDS-PMD Tokens {#step-2-generate-nds-pmd-tokens}
The nds-pmd
parameter contains encoded behavioral data that NuData analyzes. Here are three methods to generate valid tokens:
Method A: API-Based Token Generation
The fastest approach uses a dedicated API service:
import requests
import time
from typing import Dict, Optional
class NuDataTokenGenerator:
def __init__(self, api_key: str, api_url: str = "https://nudata.yourapi.tech"):
self.api_key = api_key
self.api_url = api_url
self.session = requests.Session()
self.session.headers.update({
'x-api-key': api_key,
'Content-Type': 'application/json'
})
def generate_token(
self,
script_id: str,
session_id: Optional[str] = None,
user_agent: Optional[str] = None,
additional_headers: Optional[Dict] = None
) -> Dict:
"""
Generates NuData token with proper behavioral simulation
"""
headers = {
'User-Agent': user_agent or 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Accept-Language': 'en-US,en;q=0.9',
'Sec-Ch-Ua': '"Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"',
'Sec-Ch-Ua-Mobile': '?0',
'Sec-Ch-Ua-Platform': '"Windows"'
}
if additional_headers:
headers.update(additional_headers)
payload = {
'scriptID': script_id,
'session_id': session_id,
'behavioral_data': {
'mouse_movements': self._generate_mouse_pattern(),
'typing_pattern': self._generate_typing_pattern(),
'scroll_behavior': self._generate_scroll_pattern()
}
}
response = self.session.post(
f"{self.api_url}/generate",
json=payload,
headers=headers
)
if response.status_code == 200:
return response.json()
else:
raise Exception(f"Token generation failed: {response.text}")
def _generate_mouse_pattern(self) -> list:
"""Generates realistic mouse movement data"""
import random
movements = []
x, y = 500, 300
for _ in range(random.randint(20, 40)):
# Human-like curved movements
x += random.gauss(0, 15)
y += random.gauss(0, 15)
movements.append({
'x': max(0, min(1920, int(x))),
'y': max(0, min(1080, int(y))),
'timestamp': int(time.time() * 1000) + random.randint(50, 200)
})
return movements
def _generate_typing_pattern(self) -> dict:
"""Generates realistic typing cadence data"""
import random
return {
'avg_key_delay': random.gauss(120, 30), # ms between keystrokes
'typing_speed_variance': random.uniform(0.15, 0.35),
'backspace_count': random.randint(0, 3),
'pause_count': random.randint(1, 4)
}
def _generate_scroll_pattern(self) -> list:
"""Generates human-like scrolling data"""
import random
scrolls = []
current_position = 0
for _ in range(random.randint(3, 8)):
delta = random.gauss(300, 100)
current_position += delta
scrolls.append({
'position': max(0, int(current_position)),
'velocity': abs(random.gauss(2.5, 0.8)),
'timestamp': int(time.time() * 1000) + random.randint(500, 2000)
})
return scrolls
# Usage
generator = NuDataTokenGenerator(api_key="YOUR_API_KEY")
token_data = generator.generate_token(
script_id="abc123.js",
session_id="session_xyz",
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
)
print(f"Generated token: {token_data['nds-pmd']}")
Method B: Local Token Generation
Generate tokens locally by reverse-engineering NuData's algorithm:
import hashlib
import base64
import json
import time
import random
from typing import Dict
class LocalNuDataGenerator:
def __init__(self):
self.behavioral_buffer = []
self.start_time = int(time.time() * 1000)
def generate_local_token(self, config: Dict) -> str:
"""
Generates NuData token locally using behavioral simulation
"""
# Build behavioral profile
profile = {
'jvqtrgQngn': { # Obfuscated field names
'oq': self._get_screen_info(),
'wfi': f"flap-{random.randint(100000, 999999)}",
'oc': self._generate_session_hash(),
'fe': f"{1920}k{1080} 24",
'qvqgm': str(random.randint(250, 350)),
'jxe': int(time.time() * 1000) - self.start_time,
'syi': 'snyfr', # ROT13 encoded 'false'
'si': 'si,btt,zc4,jroz',
'sn': 'sn,zcrt,btt,jni',
'us': self._generate_device_hash(),
'cy': base64.b64encode('MacIntel'.encode()).decode(),
'sg': json.dumps({'zgc': 0, 'gf': False, 'gr': False}),
'sp': json.dumps({'gp': True, 'ap': True}),
'sf': 'gehr', # ROT13 encoded 'true'
'jt': self._generate_session_hash(),
'sz': self._generate_device_hash(),
'vce': self._encode_interactions()
}
}
# Encode the profile
encoded = base64.b64encode(
json.dumps(profile).encode()
).decode()
return encoded
def _get_screen_info(self) -> str:
"""Generate screen dimension string"""
screens = [
"1920:1080:1920:1040:1920:1080",
"1440:900:1440:860:1440:900",
"2560:1440:2560:1400:2560:1440",
"1366:768:1366:728:1366:768"
]
return random.choice(screens)
def _generate_session_hash(self) -> str:
"""Generate consistent session hash"""
data = f"{time.time()}{random.random()}"
return hashlib.md5(data.encode()).hexdigest()[:16]
def _generate_device_hash(self) -> str:
"""Generate device fingerprint hash"""
device_data = {
'platform': 'Win32',
'vendor': 'Google Inc.',
'renderer': 'ANGLE (Intel, Intel(R) UHD Graphics Direct3D11)',
'memory': 8
}
return hashlib.sha256(
json.dumps(device_data).encode()
).hexdigest()[:16]
def _encode_interactions(self) -> str:
"""Encode behavioral interaction data"""
interactions = []
# Mouse movements
for i in range(random.randint(10, 30)):
interactions.append(
f"mm,{random.randint(100, 1900)},{random.randint(100, 1000)}"
)
# Key presses
for i in range(random.randint(5, 15)):
interactions.append(
f"kp,{random.randint(65, 90)},{random.randint(80, 200)}"
)
# Scrolls
for i in range(random.randint(2, 8)):
interactions.append(
f"sc,{random.randint(0, 3000)},{random.randint(1, 5)}"
)
return ';'.join(interactions)
def add_interaction(self, action_type: str, data: Dict):
"""Add behavioral interaction to buffer"""
self.behavioral_buffer.append({
'type': action_type,
'data': data,
'timestamp': int(time.time() * 1000)
})
# Usage
generator = LocalNuDataGenerator()
token = generator.generate_local_token({'site': 'ticketmaster'})
print(f"Local token: {token}")
Step 3: Choose Your Bypass Strategy {#step-3-bypass-strategy}
Strategy 1: Pure Requests Approach (Lightweight)
Best for high-volume operations where speed matters:
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
import random
class NuDataRequester:
def __init__(self, proxies: list = None):
self.session = requests.Session()
self.proxies = proxies or []
# Configure retry strategy
retry_strategy = Retry(
total=3,
backoff_factor=1,
status_forcelist=[429, 500, 502, 503, 504]
)
adapter = HTTPAdapter(max_retries=retry_strategy)
self.session.mount("http://", adapter)
self.session.mount("https://", adapter)
# TLS fingerprint spoofing
self.session.headers.update(self._get_browser_headers())
def _get_browser_headers(self) -> dict:
"""Returns browser-like headers with proper ordering"""
return {
'sec-ch-ua': '"Not A(Brand";v="99", "Google Chrome";v="121", "Chromium";v="121"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'Upgrade-Insecure-Requests': '1',
'User-Agent': self._get_random_ua(),
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
'Sec-Fetch-Site': 'none',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-User': '?1',
'Sec-Fetch-Dest': 'document',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'en-US,en;q=0.9'
}
def _get_random_ua(self) -> str:
"""Returns random realistic user agent"""
user_agents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36'
]
return random.choice(user_agents)
def make_request(self, url: str, nds_token: str, method: str = 'GET', data: dict = None):
"""Makes request with NuData token"""
proxy = random.choice(self.proxies) if self.proxies else None
# Add NuData token to request
if method == 'POST':
if data is None:
data = {}
data['nds-pmd'] = nds_token
response = self.session.post(
url,
data=data,
proxies={'http': proxy, 'https': proxy} if proxy else None,
timeout=30
)
else:
params = {'nds-pmd': nds_token}
response = self.session.get(
url,
params=params,
proxies={'http': proxy, 'https': proxy} if proxy else None,
timeout=30
)
return response
# Usage with proxy rotation
proxies = [
'http://user:pass@proxy1.com:8080',
'http://user:pass@proxy2.com:8080',
'http://user:pass@proxy3.com:8080'
]
requester = NuDataRequester(proxies=proxies)
response = requester.make_request(
'https://www.ticketmaster.com/api/search',
nds_token='generated_token_here',
method='POST',
data={'query': 'concert'}
)
Strategy 2: Selenium with Undetected ChromeDriver
For sites requiring full browser execution:
import undetected_chromedriver as uc
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import random
import time
class StealthSeleniumDriver:
def __init__(self, proxy: str = None):
options = uc.ChromeOptions()
# Stealth options
options.add_argument('--disable-blink-features=AutomationControlled')
options.add_argument('--disable-dev-shm-usage')
options.add_argument('--no-sandbox')
options.add_argument('--disable-gpu')
options.add_argument(f'--user-agent={self._get_user_agent()}')
# Window size for consistent fingerprint
options.add_argument('--window-size=1920,1080')
options.add_argument('--start-maximized')
# Disable automation flags
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option('useAutomationExtension', False)
# Add proxy if provided
if proxy:
options.add_argument(f'--proxy-server={proxy}')
# Initialize driver
self.driver = uc.Chrome(options=options, version_main=121)
# Execute stealth scripts
self._apply_stealth_scripts()
def _get_user_agent(self) -> str:
agents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36'
]
return random.choice(agents)
def _apply_stealth_scripts(self):
"""Apply additional stealth JavaScript"""
stealth_js = """
// Override webdriver detection
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
// Override plugins to look real
Object.defineProperty(navigator, 'plugins', {
get: () => [
{name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer'},
{name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai'},
{name: 'Native Client', filename: 'internal-nacl-plugin'}
]
});
// Override permissions
const originalQuery = window.navigator.permissions.query;
window.navigator.permissions.query = (parameters) => (
parameters.name === 'notifications' ?
Promise.resolve({ state: Notification.permission }) :
originalQuery(parameters)
);
// Spoof languages
Object.defineProperty(navigator, 'languages', {
get: () => ['en-US', 'en']
});
// Override hardwareConcurrency
Object.defineProperty(navigator, 'hardwareConcurrency', {
get: () => 8
});
// Override deviceMemory
Object.defineProperty(navigator, 'deviceMemory', {
get: () => 8
});
"""
self.driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
'source': stealth_js
})
def human_like_interaction(self, element):
"""Perform human-like interaction with element"""
# Random delay before action
time.sleep(random.uniform(0.5, 2.0))
# Move to element with curve
self._curved_mouse_movement(element)
# Random micro-movements
for _ in range(random.randint(1, 3)):
x_offset = random.randint(-5, 5)
y_offset = random.randint(-5, 5)
self.driver.execute_script(
f"arguments[0].dispatchEvent(new MouseEvent('mousemove', {{bubbles: true, clientX: {x_offset}, clientY: {y_offset}}}))",
element
)
time.sleep(random.uniform(0.05, 0.15))
# Click with realistic timing
element.click()
def _curved_mouse_movement(self, element):
"""Simulate curved mouse movement to element"""
# Get element position
location = element.location
size = element.size
# Calculate target position (center of element)
target_x = location['x'] + size['width'] / 2
target_y = location['y'] + size['height'] / 2
# Generate bezier curve points
steps = random.randint(10, 20)
for i in range(steps):
t = i / steps
# Bezier curve calculation
x = (1-t)**2 * 0 + 2*(1-t)*t * random.randint(100, 1800) + t**2 * target_x
y = (1-t)**2 * 0 + 2*(1-t)*t * random.randint(100, 900) + t**2 * target_y
self.driver.execute_script(
f"document.dispatchEvent(new MouseEvent('mousemove', {{bubbles: true, clientX: {x}, clientY: {y}}}))"
)
time.sleep(random.uniform(0.01, 0.03))
def type_like_human(self, element, text):
"""Type text with human-like cadence"""
element.click()
for char in text:
element.send_keys(char)
# Variable typing speed
if char == ' ':
time.sleep(random.uniform(0.1, 0.3))
elif char in '.,!?':
time.sleep(random.uniform(0.2, 0.5))
else:
time.sleep(random.gauss(0.1, 0.03))
# Occasional typos and corrections
if random.random() < 0.02:
wrong_char = random.choice('abcdefghijklmnopqrstuvwxyz')
element.send_keys(wrong_char)
time.sleep(random.uniform(0.2, 0.4))
element.send_keys('\b')
time.sleep(random.uniform(0.1, 0.2))
# Usage
driver = StealthSeleniumDriver(proxy='http://proxy.com:8080')
driver.driver.get('https://www.ticketmaster.com')
# Wait for page load
wait = WebDriverWait(driver.driver, 10)
search_box = wait.until(EC.presence_of_element_located((By.ID, 'search-input')))
# Type with human-like behavior
driver.type_like_human(search_box, 'Taylor Swift concert')
Strategy 3: Playwright with Fingerprint Spoofing
The most advanced approach using Playwright:
from playwright.sync_api import sync_playwright
import random
import json
class PlaywrightNuDataBypass:
def __init__(self):
self.playwright = sync_playwright().start()
self.fingerprints = self._load_fingerprints()
def _load_fingerprints(self):
"""Load real browser fingerprints"""
return [
{
'userAgent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'viewport': {'width': 1920, 'height': 1080},
'screen': {'width': 1920, 'height': 1080},
'platform': 'Win32',
'memory': 8,
'cpus': 8,
'canvas_hash': self._generate_canvas_hash()
}
# Add more fingerprints
]
def _generate_canvas_hash(self):
"""Generate unique canvas fingerprint"""
import hashlib
data = f"{random.random()}{random.randint(1000000, 9999999)}"
return hashlib.md5(data.encode()).hexdigest()
def create_context(self, proxy=None):
"""Create stealth browser context"""
fingerprint = random.choice(self.fingerprints)
browser = self.playwright.chromium.launch(
headless=False, # Use headed mode for better evasion
args=[
'--disable-blink-features=AutomationControlled',
f'--user-agent={fingerprint["userAgent"]}',
'--disable-web-security',
'--disable-features=IsolateOrigins,site-per-process',
'--allow-running-insecure-content',
'--disable-features=CrossSiteDocumentBlockingIfIsolating',
'--disable-site-isolation-trials'
],
proxy={'server': proxy} if proxy else None
)
context = browser.new_context(
viewport=fingerprint['viewport'],
user_agent=fingerprint['userAgent'],
locale='en-US',
timezone_id='America/New_York',
permissions=['geolocation', 'notifications'],
geolocation={'latitude': 40.7128, 'longitude': -74.0060},
color_scheme='light',
device_scale_factor=1,
has_touch=False,
is_mobile=False
)
# Inject fingerprint spoofing
context.add_init_script(self._get_spoofing_script(fingerprint))
return context
def _get_spoofing_script(self, fingerprint):
"""Generate JavaScript for fingerprint spoofing"""
return f"""
// Canvas fingerprint spoofing
const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
HTMLCanvasElement.prototype.toDataURL = function(type) {{
if (type === 'image/png') {{
// Add noise to canvas
const context = this.getContext('2d');
const imageData = context.getImageData(0, 0, this.width, this.height);
for (let i = 0; i < imageData.data.length; i += 4) {{
imageData.data[i] += Math.random() * 0.1;
}}
context.putImageData(imageData, 0, 0);
}}
return originalToDataURL.apply(this, arguments);
}};
// WebGL spoofing
const getParameter = WebGLRenderingContext.prototype.getParameter;
WebGLRenderingContext.prototype.getParameter = function(parameter) {{
if (parameter === 37445) {{
return 'Intel Inc.';
}}
if (parameter === 37446) {{
return 'Intel Iris OpenGL Engine';
}}
return getParameter.apply(this, arguments);
}};
// Audio context spoofing
const AudioContext = window.AudioContext || window.webkitAudioContext;
const audioContext = new AudioContext();
const oscillator = audioContext.createOscillator();
const gain = audioContext.createGain();
oscillator.connect(gain);
gain.connect(audioContext.destination);
oscillator.frequency.value = 1000 + Math.random() * 100;
// Battery API spoofing
if ('getBattery' in navigator) {{
navigator.getBattery = async () => ({{
charging: Math.random() > 0.5,
chargingTime: Math.random() * 3600,
dischargingTime: Infinity,
level: Math.random()
}});
}}
// Hardware concurrency
Object.defineProperty(navigator, 'hardwareConcurrency', {{
get: () => {fingerprint['cpus']}
}});
// Device memory
Object.defineProperty(navigator, 'deviceMemory', {{
get: () => {fingerprint['memory']}
}});
// Platform
Object.defineProperty(navigator, 'platform', {{
get: () => '{fingerprint['platform']}'
}});
"""
async def navigate_with_behavior(self, page, url):
"""Navigate to URL with human-like behavior"""
# Random initial wait
await page.wait_for_timeout(random.randint(1000, 3000))
# Navigate
await page.goto(url, wait_until='networkidle')
# Simulate reading behavior
for _ in range(random.randint(2, 5)):
await page.mouse.wheel(0, random.randint(100, 300))
await page.wait_for_timeout(random.randint(500, 2000))
# Random mouse movements
for _ in range(random.randint(5, 10)):
x = random.randint(100, 1800)
y = random.randint(100, 900)
await page.mouse.move(x, y)
await page.wait_for_timeout(random.randint(100, 500))
return page
# Usage
bypass = PlaywrightNuDataBypass()
context = bypass.create_context(proxy='http://proxy.com:8080')
page = context.new_page()
# Navigate with behavioral simulation
bypass.navigate_with_behavior(page, 'https://www.ticketmaster.com')
Step 4: Implement Human-Like Behavior {#step-4-human-behavior}
NuData detects bots by analyzing parameters such as typing patterns, device information, and device angle information. Here's how to simulate realistic human behavior:
Advanced Behavioral Simulation
import numpy as np
from scipy import interpolate
import time
import random
class BehavioralSimulator:
def __init__(self):
self.typing_profile = self._generate_typing_profile()
self.mouse_profile = self._generate_mouse_profile()
def _generate_typing_profile(self):
"""Generate unique typing profile based on real human data"""
return {
'avg_dwell_time': np.random.normal(120, 30), # How long keys are pressed
'avg_flight_time': np.random.normal(150, 40), # Time between keys
'backspace_probability': 0.02,
'pause_probability': 0.1,
'burst_typing_probability': 0.15,
'common_bigrams': { # Faster typing for common patterns
'th': 0.8, 'he': 0.8, 'in': 0.85, 'er': 0.82,
'an': 0.83, 'ed': 0.81, 'nd': 0.84, 'to': 0.86
}
}
def _generate_mouse_profile(self):
"""Generate mouse movement profile"""
return {
'avg_speed': np.random.normal(400, 100), # pixels per second
'acceleration': np.random.normal(1.5, 0.3),
'jitter_amplitude': np.random.uniform(1, 3),
'curve_tendency': np.random.uniform(0.1, 0.3)
}
def generate_mouse_path(self, start_x, start_y, end_x, end_y):
"""Generate realistic mouse movement path using Bezier curves"""
distance = np.sqrt((end_x - start_x)**2 + (end_y - start_y)**2)
num_points = max(10, int(distance / 50))
# Generate control points for Bezier curve
ctrl_x1 = start_x + (end_x - start_x) * 0.25 + random.uniform(-50, 50)
ctrl_y1 = start_y + (end_y - start_y) * 0.25 + random.uniform(-50, 50)
ctrl_x2 = start_x + (end_x - start_x) * 0.75 + random.uniform(-50, 50)
ctrl_y2 = start_y + (end_y - start_y) * 0.75 + random.uniform(-50, 50)
# Generate Bezier curve
t = np.linspace(0, 1, num_points)
x = (1-t)**3 * start_x + 3*(1-t)**2*t * ctrl_x1 + 3*(1-t)*t**2 * ctrl_x2 + t**3 * end_x
y = (1-t)**3 * start_y + 3*(1-t)**2*t * ctrl_y1 + 3*(1-t)*t**2 * ctrl_y2 + t**3 * end_y
# Add micro jitter
x += np.random.normal(0, self.mouse_profile['jitter_amplitude'], num_points)
y += np.random.normal(0, self.mouse_profile['jitter_amplitude'], num_points)
# Generate timestamps with variable speed
timestamps = []
current_time = 0
for i in range(1, num_points):
segment_distance = np.sqrt((x[i] - x[i-1])**2 + (y[i] - y[i-1])**2)
segment_time = segment_distance / self.mouse_profile['avg_speed']
# Add acceleration/deceleration
if i < num_points * 0.2: # Acceleration phase
segment_time *= (1 + self.mouse_profile['acceleration'])
elif i > num_points * 0.8: # Deceleration phase
segment_time *= (1 + self.mouse_profile['acceleration'] * 1.5)
current_time += segment_time * 1000 # Convert to milliseconds
timestamps.append(int(current_time))
return list(zip(x.astype(int), y.astype(int), timestamps))
def generate_typing_events(self, text):
"""Generate realistic typing events with timing"""
events = []
current_time = 0
for i, char in enumerate(text):
# Check for typo
if random.random() < self.typing_profile['backspace_probability']:
# Make typo
wrong_char = random.choice('abcdefghijklmnopqrstuvwxyz')
events.append({
'type': 'keydown',
'key': wrong_char,
'timestamp': current_time
})
current_time += np.random.normal(
self.typing_profile['avg_dwell_time'], 20
)
# Realize mistake and backspace
current_time += np.random.normal(300, 50) # Reaction time
events.append({
'type': 'keydown',
'key': 'Backspace',
'timestamp': current_time
})
current_time += np.random.normal(100, 20)
# Check for pause
if random.random() < self.typing_profile['pause_probability']:
current_time += np.random.normal(800, 200)
# Check for bigram optimization
if i > 0:
bigram = text[i-1:i+1].lower()
if bigram in self.typing_profile['common_bigrams']:
speed_multiplier = self.typing_profile['common_bigrams'][bigram]
else:
speed_multiplier = 1.0
else:
speed_multiplier = 1.0
# Add key event
events.append({
'type': 'keydown',
'key': char,
'timestamp': current_time
})
# Dwell time (how long key is pressed)
dwell = np.random.normal(
self.typing_profile['avg_dwell_time'] * speed_multiplier,
20
)
current_time += max(50, dwell)
events.append({
'type': 'keyup',
'key': char,
'timestamp': current_time
})
# Flight time (time to next key)
if i < len(text) - 1:
flight = np.random.normal(
self.typing_profile['avg_flight_time'] * speed_multiplier,
30
)
current_time += max(30, flight)
return events
def generate_scroll_events(self, page_height):
"""Generate realistic scrolling pattern"""
events = []
current_position = 0
current_time = 0
# Reading pattern: fast scan, slow read, fast scan
while current_position < page_height * 0.8:
# Determine scroll type
scroll_type = random.choice(['fast_scan', 'slow_read', 'back_check'])
if scroll_type == 'fast_scan':
# Quick scroll down
delta = random.uniform(300, 600)
duration = random.uniform(200, 400)
elif scroll_type == 'slow_read':
# Slow reading scroll
delta = random.uniform(50, 150)
duration = random.uniform(1000, 3000)
else: # back_check
# Scroll back up to reread
delta = random.uniform(-200, -50)
duration = random.uniform(500, 1000)
new_position = max(0, min(page_height, current_position + delta))
events.append({
'type': 'scroll',
'position': new_position,
'delta': delta,
'timestamp': current_time,
'duration': duration
})
current_position = new_position
current_time += duration + random.uniform(100, 500) # Pause between scrolls
return events
# Usage example
simulator = BehavioralSimulator()
# Generate mouse path
mouse_path = simulator.generate_mouse_path(100, 100, 800, 600)
for x, y, timestamp in mouse_path:
print(f"Move to ({x}, {y}) at {timestamp}ms")
# Generate typing events
typing_events = simulator.generate_typing_events("Hello World")
for event in typing_events:
print(f"{event['type']} key '{event['key']}' at {event['timestamp']}ms")
# Generate scroll events
scroll_events = simulator.generate_scroll_events(3000)
for event in scroll_events:
print(f"Scroll to {event['position']} at {event['timestamp']}ms")
Step 5: Handle Session Management {#step-5-session-management}
NuData tracks sessions across requests. Proper session management is crucial:
import pickle
import json
from datetime import datetime, timedelta
class NuDataSessionManager:
def __init__(self, session_file='nudata_sessions.pkl'):
self.session_file = session_file
self.sessions = self._load_sessions()
self.current_session = None
def _load_sessions(self):
"""Load existing sessions from file"""
try:
with open(self.session_file, 'rb') as f:
return pickle.load(f)
except FileNotFoundError:
return {}
def _save_sessions(self):
"""Save sessions to file"""
with open(self.session_file, 'wb') as f:
pickle.dump(self.sessions, f)
def create_session(self, site_url):
"""Create new NuData session"""
session_id = self._generate_session_id()
self.current_session = {
'id': session_id,
'site': site_url,
'created': datetime.now(),
'last_used': datetime.now(),
'cookies': {},
'tokens': [],
'behavioral_profile': self._init_behavioral_profile(),
'request_count': 0
}
self.sessions[session_id] = self.current_session
self._save_sessions()
return session_id
def _generate_session_id(self):
"""Generate unique session ID"""
import uuid
return str(uuid.uuid4())
def _init_behavioral_profile(self):
"""Initialize behavioral profile for consistency"""
return {
'typing_speed': random.gauss(120, 20),
'mouse_speed': random.gauss(400, 80),
'scroll_pattern': random.choice(['smooth', 'jumpy', 'mixed']),
'interaction_delays': {
'min': random.uniform(0.5, 1.0),
'max': random.uniform(2.0, 4.0),
'avg': random.uniform(1.2, 2.0)
}
}
def update_session(self, session_id, token=None, cookies=None):
"""Update session with new token or cookies"""
if session_id in self.sessions:
session = self.sessions[session_id]
session['last_used'] = datetime.now()
session['request_count'] += 1
if token:
session['tokens'].append({
'token': token,
'timestamp': datetime.now()
})
# Keep only last 10 tokens
session['tokens'] = session['tokens'][-10:]
if cookies:
session['cookies'].update(cookies)
self._save_sessions()
def get_valid_session(self, site_url):
"""Get valid session for site or create new one"""
# Look for recent valid session
for session_id, session in self.sessions.items():
if (session['site'] == site_url and
session['last_used'] > datetime.now() - timedelta(hours=1)):
self.current_session = session
return session_id
# No valid session found, create new one
return self.create_session(site_url)
def get_session_context(self, session_id):
"""Get full session context for requests"""
if session_id not in self.sessions:
return None
session = self.sessions[session_id]
# Build context with consistent behavior
context = {
'session_id': session_id,
'cookies': session['cookies'],
'behavioral_profile': session['behavioral_profile'],
'request_count': session['request_count'],
'session_age': (datetime.now() - session['created']).total_seconds()
}
# Get most recent token if available
if session['tokens']:
context['latest_token'] = session['tokens'][-1]['token']
return context
def cleanup_old_sessions(self, max_age_hours=24):
"""Remove old sessions"""
cutoff = datetime.now() - timedelta(hours=max_age_hours)
old_sessions = [
sid for sid, session in self.sessions.items()
if session['last_used'] < cutoff
]
for sid in old_sessions:
del self.sessions[sid]
self._save_sessions()
print(f"Cleaned up {len(old_sessions)} old sessions")
# Usage
session_manager = NuDataSessionManager()
# Create or get session
session_id = session_manager.get_valid_session('https://www.ticketmaster.com')
print(f"Using session: {session_id}")
# Get session context for request
context = session_manager.get_session_context(session_id)
print(f"Session context: {json.dumps(context, default=str, indent=2)}")
# Update session after successful request
session_manager.update_session(
session_id,
token='new_nds_token_here',
cookies={'session': 'abc123', 'user': 'xyz789'}
)
Common Pitfalls to Avoid {#common-pitfalls}
1. Inconsistent Fingerprints
Never mix fingerprint components from different browsers or devices. If you claim to be Chrome on Windows, all your properties must match:
# BAD: Inconsistent fingerprint
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0...) Chrome/121.0', # Windows Chrome
'Sec-CH-UA-Platform': '"macOS"' # But claims macOS!
}
# GOOD: Consistent fingerprint
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0...) Chrome/121.0',
'Sec-CH-UA-Platform': '"Windows"'
}
2. Unrealistic Timing Patterns
Avoid perfectly consistent timing - humans are naturally variable:
# BAD: Robot-like timing
for i in range(10):
click_element()
time.sleep(1.0) # Exactly 1 second every time
# GOOD: Human-like variation
for i in range(10):
click_element()
time.sleep(random.gauss(1.0, 0.2)) # Variable timing around 1 second
3. Missing Behavioral Noise
Real humans make mistakes and have quirks:
# Add realistic behaviors
def human_typing(text):
result = ""
for char in text:
if random.random() < 0.02: # 2% typo rate
result += random.choice('aeiou') # Wrong character
time.sleep(0.3)
result = result[:-1] # Backspace
result += char
time.sleep(random.gauss(0.1, 0.03))
return result
4. Ignoring Rate Limits
Even with perfect bypassing, respect rate limits to avoid detection:
from time import sleep
from random import uniform
class RateLimiter:
def __init__(self, requests_per_minute=30):
self.min_delay = 60.0 / requests_per_minute
self.last_request = 0
def wait(self):
elapsed = time.time() - self.last_request
if elapsed < self.min_delay:
sleep_time = self.min_delay - elapsed + uniform(0, 2)
sleep(sleep_time)
self.last_request = time.time()
Final Thoughts
Successfully bypassing NuData in 2025 requires a multi-layered approach combining proper token generation, realistic behavioral simulation, and consistent session management. As NuData continues to evolve with its Trust Consortium and machine learning models, staying ahead means constantly adapting your techniques.
Remember that while these techniques are powerful, they should be used responsibly and in compliance with website terms of service. The goal is to automate legitimate interactions, not to violate security measures for malicious purposes.
The key to success is thinking like NuData: every interaction must appear natural, every fingerprint must be consistent, and every session must follow realistic human patterns. With the code and techniques provided in this guide, you now have the tools to interact with NuData-protected sites effectively.