🎉 Premium Proxies · 3-Day Free TrialClaim Now →
Guides

How to Bypass PerimeterX (HUMAN Security) When Scraping

Bypass PerimeterX (HUMAN) when scraping public data: how _px cookies, the sensor JS, and Press and Hold work, plus real browsers, residential IPs, and API code.

S SparkProxy 17 21 min read
Share
How to Bypass PerimeterX (HUMAN Security) When Scraping

A plain requests.get() against a PerimeterX-protected site comes back 403 before your parser ever runs, and no amount of header tweaking fixes it. To bypass PerimeterX (now HUMAN Security's Bot Defender) you first have to accept that it scored your request as a bot before the HTML existed, using a sensor script, a stack of _px cookies, and behavioral biometrics a bare HTTP client can't produce. This guide explains how HUMAN Security bot detection actually works, how to read its 403 and the "Press and Hold" challenge, and the ethical ways to collect public data without pretending your traffic is something it isn't. There's no permanent bypass and no magic flag. What follows are the signals that decide the outcome, and how to make legitimate automation look like the real browser sessions it already is.

Scrape responsibly: what "bypass" really means

"Bypass" here means one thing: making legitimate, automated access to public data look like the ordinary browser traffic it already is, so a heuristic doesn't wrongly flag it. It does not mean breaking into anything protected. PerimeterX sits in front of login pages, checkout flows, account creation, and gift-card redemption for a reason, and those are exactly the flows to leave alone. Set the ground rules before you write a line of code.

  • Public data only. Anything behind a login, a paywall, or that exposes someone's personal information is off-limits without explicit permission. HUMAN protects fraud-sensitive paths; don't touch them.
  • Read robots.txt and the Terms of Service. If a path is disallowed or the ToS prohibits automated collection, respect it. A Crawl-delay is a rate the site is asking you to honor, not a suggestion.
  • Rate-limit yourself. Bot defenses exist in part because scrapers hammer origins. Slow, considerate crawling is both more ethical and, conveniently, far less detectable.
  • Prefer the official API. If the site publishes a feed or API, use it. It's faster, cleaner, and it's the access path the site actually sanctions.
  • No guarantees. HUMAN ships detection updates continuously. A technique that works today can stop working next week. Anyone selling a "permanent PerimeterX bypass" is selling snake oil.

The goal is not to defeat security. It's to stop a bot filter from misclassifying a well-behaved, public-data crawler as an attacker. If a site clearly does not want to be scraped, the correct answer is to stop, not to escalate.

Everything below is about fingerprint realism and good manners, not about defeating authentication or collecting data that was never meant to be public.


What PerimeterX and HUMAN Bot Defender actually are

PerimeterX was acquired by HUMAN Security in 2022 and its bot-mitigation product is now branded HUMAN Bot Defender. Most engineers still call it PerimeterX, and the technical footprint on a protected page still carries the _px prefix, so both names point at the same thing. It runs as an enforcer at the edge or origin plus a client-side sensor that scores every request through HUMAN's detection engine.

Two design choices make PerimeterX scraping harder than a generic firewall. First, it leans heavily on behavioral biometrics: mouse movement, scroll dynamics, touch pressure, and keystroke cadence, not just a static fingerprint. Second, it increasingly runs in first-party mode, where the sensor and its telemetry collector are served from the customer's own domain under a randomized path prefix instead of perimeterx.net. You can't defeat PX bot detection by blocking a third-party host, because on a first-party site there isn't one to block. The sensor is stitched into the page you want.

Each protected app has an appId shaped like PXxxxxxxxx. You'll see it in the sensor URL and in every block response, and it's the anchor for reading what PerimeterX decided about your request.


Free trial

Scraping at scale? Skip the blocks.

Fast, unblockable datacentre proxies with unlimited bandwidth.

How PerimeterX detects bots

PerimeterX scores several independent layers on every request and combines them into a risk score. A single strong mismatch can trip a block, which is why swapping in a "better proxy" rarely fixes anything by itself. The IP is one row in the table.

Detection layerWhat PerimeterX inspectsWhat gives a bot away
IP reputation and ASNAddress history, whether the ASN is a known datacenter (AWS, GCP, Azure, OVH, Hetzner)A clean-looking request from a flagged datacenter range
Sensor JS device fingerprintCanvas, WebGL, audio stack, fonts, screen geometry, `navigator.webdriver`, automation propertiesHeadless Chromium tells, or no sensor telemetry submitted at all
Behavioral biometricsMouse paths, scroll cadence, touch events, keystroke timing, the Press and Hold gestureZero mouse movement, a robotic hold, instant form fills
`_px` token cookiesValidity of `_px3`, the `_pxhd` human-detection hash, `_pxvid` continuityMissing, stale, or replayed token; no `_pxhd` from a prior visit
TLS fingerprint (JA3/JA4)Cipher suites, extensions, and curves in the Client HelloPython `urllib3` or Go defaults that match no real browser
HTTP/2 fingerprint and headersFrame settings, header and pseudo-header order, `sec-ch-ua` client hintsA Chrome User-Agent over an HTTP/2 profile no Chrome would send

The mental model that matters: PerimeterX cross-checks these layers against each other. A Chrome User-Agent paired with a Python TLS handshake and no behavioral data is far more suspicious than an honest Python client, because the inconsistency is the tell. To pass, every layer has to agree on the same story. That's the same principle behind the broader proxy-block avoidance playbook, and it applies double when behavioral scoring is in the mix.


The _px cookies and the sensor script

PerimeterX tracks visitors with a family of first-party cookies, all sharing the _px prefix. Reading them is how you tell a healthy session from a doomed one. The important members:

CookieRoleLifetimeNotes
`_pxhd`Human-detection cookie, set on the first responseLong (about a year)Format is `hash:timestamp`. A returning visitor should carry one; a fresh scraper never does.
`_px3`Current-generation risk tokenShort (minutes)Minted by the sensor after it submits clean telemetry. Older sites still use `_px`/`_px2`.
`_pxvid`Visitor ID (a UUID)LongTies activity together across the HUMAN network.
`_pxde`Data-enrichment blobSessionBase64 JSON of collected signals.

The _px cookie you actually earn is _px3. Here's how: the sensor script (served from a first-party path like //init.js in first-party mode, or historically from client.perimeterx.net//main.min.js) runs in the browser, collects device and behavioral signals, and POSTs them to a collector endpoint such as //xhr/api/v1/collector. If the risk score is low enough, the collector responds with a fresh _px3. Subsequent requests carrying a valid _px3 sail through.

Three facts about that flow decide whether your scraper survives:

  1. You earn _px3 in a browser context. A valid token comes from the sensor executing and reporting a clean fingerprint plus real interaction. You cannot mint one with a bare HTTP client.
  2. _pxhd is your "returning human" credential. Requesting a deep page with no _pxhd from a prior visit is a first-contact pattern that raises the score. Warm up on a lighter page first so the cookie gets set.
  3. Tokens are bound to a session and loosely to an IP. Replaying one "golden" _px3 from fifty exit IPs is itself a signal. Same token, many addresses, is a pattern PerimeterX watches for.

Decode the 403 and the Press and Hold challenge

Most scrapers treat "not 200" as one failure. PerimeterX gives you a diagnostic if you read the status code together with the block body. When the enforcer blocks an XHR or API call, the 403 body is JSON carrying fields like appId, uuid, vid, firstPartyEnabled, and blockScript. When it blocks a top-level navigation, you get an HTML block page that loads the "Press and Hold to confirm you are a human" widget. Different outcomes need different fixes, and retrying the wrong way just burns IPs.

ResponseWhat you'll seeWhat PerimeterX decidedCorrect action
**200 (real content)**Your HTML/JSON, plus a set or refreshed `_px3` cookieRisk score passedPersist `_px3` and `_pxhd`, keep the same exit IP for this session
**403 (challenge)**Block page with a Press and Hold widget, or JSON with `appId`, `uuid`, `vid`, `blockScript`Risk too high: prove a real deviceRender in a real browser, complete the hold, let the sensor refresh `_px3`
**403 (hard block)**"Access to this page has been denied", repeated on retry, no solvable widgetReputation plus fingerprint failed outrightRotate IP and fix TLS/JS fingerprint; do not replay the identical request
**429**Rate-limit response, often with `Retry-After`Too many requests from this IP or tokenBack off, drop concurrency, slow the crawl
**Sensor never fires**403 loop even inside a browser, no `_px3` ever mintedThe first-party sensor or collector was blocked or didn't executeAllow the `//` sensor and collector to load and run; don't block their requests
**5xx from a scraping API**Returned by the API when upstream attempts failThe proxy or render layer exhausted its retriesRetry with backoff; escalate to premium proxy plus stealth

The Press and Hold challenge deserves its own note. It is not a puzzle you "solve" with pixel matching. It is a behavioral test: HUMAN measures how you press, the micro-movements during the hold, and the release, then scores whether that gesture looks human. A headless browser that programmatically dispatches a mousedown, waits, and dispatches a mouseup produces a suspiciously perfect signal and fails. This is the difference between PerimeterX and a challenge that only checks a token, and it's why the behavior section below matters more here than it does for other vendors.


Run a real browser that executes the sensor

The sensor is the layer a plain HTTP library cannot satisfy, because there's no JS runtime to run it and no telemetry to submit. Without the sensor's POST to the collector, _px3 is never minted, so you're blocked no matter how clean your headers look. Satisfying this reliably means running an actual browser.

Launch Chromium through Playwright or Puppeteer, patch the obvious automation leaks with a stealth layer, and warm up so the sensor sets _pxhd and _px3 before you hit the page you want. Here's Playwright in Python routing through a residential proxy:

from playwright.sync_api import sync_playwright
from playwright_stealth import stealth_sync

with sync_playwright() as p:
    browser = p.chromium.launch(
        headless=True,
        proxy={"server": "http://gate.sparkproxy.io:10000",
               "username": "user", "password": "pass"},
    )
    context = browser.new_context(
        viewport={"width": 1366, "height": 900},
        locale="en-US",
        timezone_id="America/New_York",   # match the proxy's country
    )
    page = context.new_page()
    stealth_sync(page)                    # hides navigator.webdriver and headless tells

    page.goto("https://example.com/", wait_until="networkidle")   # sensor sets _pxhd + _px3
    page.mouse.move(240, 300)             # give the behavioral layer real motion
    page.mouse.move(480, 520, steps=12)
    page.goto("https://example.com/catalog", wait_until="networkidle")
    html = page.content()
    browser.close()

Two details matter. The first goto hits a light page so the sensor runs and bakes _pxhd and _px3 before you request the target, exactly as a returning human's browser would. The mouse moves are not decoration: PerimeterX scores behavioral signals continuously, and a session with literally zero cursor movement is a robot. Set the viewport, locale, and timezone to consistent, human values rather than the headless defaults (800x600, UTC) that push the score up. If you're on Puppeteer, the same proxy-plus-stealth-plus-warm-up pattern applies.

A real browser is heavier than an HTTP client. Run one only for targets that genuinely challenge you, and reserve lightweight clients for the easy pages.


Match your TLS and HTTP/2 fingerprint

Some PerimeterX endpoints (JSON APIs behind the same enforcer) only score the network and header layers, and there you can skip the browser. The request still has to survive TLS fingerprinting. Python's requests rides on urllib3, whose Client Hello matches no browser on earth. PerimeterX computes a JA3/JA4 hash from that handshake and reads "Python," no matter what User-Agent you set.

The fix is curl_cffi, which impersonates a real browser's TLS stack and HTTP/2 profile:

from curl_cffi import requests as cffi

session = cffi.Session(impersonate="chrome124")   # real Chrome TLS + HTTP/2
session.proxies = {
    "http":  "http://user:pass@gate.sparkproxy.io:10000",
    "https": "http://user:pass@gate.sparkproxy.io:10000",
}

resp = session.get("https://example.com/api/products")
print(resp.status_code)          # 200 if the fingerprint, IP, and any cached _px3 hold up

impersonate="chrome124" sends Chrome 124's exact cipher suites, extensions, and HTTP/2 settings, so the JA3/JA4 hash and the User-Agent finally tell the same story. That consistency is the whole point. For TLS realism layer by layer see the proxy-block avoidance guide, and for doing this at volume the async scraping with requests and aiohttp walkthrough covers concurrency without tripping rate limits.

Be honest about which path a target needs. If PerimeterX serves the Press and Hold challenge, no TLS trick alone clears it. That's a job for a real browser or a rendering API that runs one.


Use residential IPs with clean reputation

IP reputation is the fastest way to earn an instant 403. Requests from well-known datacenter ASNs (AWS, GCP, Azure, OVH, Hetzner) start with a heavy suspicion penalty, because almost no ordinary shopper browses from an AWS address. You can have a perfect sensor payload and still get blocked purely on the exit IP.

Residential and mobile proxies route through real consumer ISPs and carrier networks, so the exit IP carries the reputation of an ordinary home or phone connection. That single change often turns a reliable 403 into a 200. If you're new to the differences between IP families, the residential proxy explainer breaks down when each type fits.

Two rules keep a good IP good:

  • One session, one IP. Bind the _px3 and _pxhd cookies to the exit IP that earned them. Don't replay a token across a rotating pool; that token-to-IP mismatch is exactly what PerimeterX flags.
  • Geo-match the audience. Scraping a US retailer? Exit from a US residential IP. A German IP hitting a US-only storefront is an easy anomaly.

Residential IPs are not a standalone bypass. They clear the reputation layer, but the sensor and behavioral layers still have to pass. Pair a clean IP with a real browser or a matched TLS fingerprint, never one alone.


Behavior is the point: pacing and the hold gesture

Behavioral scoring is where PerimeterX earns its reputation, and it's the layer most scrapers ignore. Even with a flawless fingerprint and a residential IP, ten requests per second from one session reads as a machine, and a Press and Hold with a perfectly flat pressure curve fails on its own.

  • Warm up. Load a light page first and let the sensor set _pxhd and _px3 before you request deep pages. Jumping straight to a product URL with no prior cookie is a classic bot pattern.
  • Generate real interaction. Move the cursor, scroll a little, pause. A browser session with human-shaped motion scores far lower than a static one.
  • Add jittered delays. Random pauses between requests beat a fixed interval. A perfectly regular heartbeat is itself robotic.
  • Cap concurrency per IP at a handful of parallel requests, not hundreds, and spread load across the pool.
  • Follow a plausible path. Category, then listing, then detail. Real users don't teleport across a hundred unrelated URLs in a minute.
import random, time

def polite_delay(lo=1.5, hi=5.0):
    """Human-like jittered pause between requests."""
    time.sleep(random.uniform(lo, hi))

If the site publishes a Crawl-delay in robots.txt, treat it as a hard floor. Slower crawling costs you throughput today and saves you from a blanket ban tomorrow. It's the cheapest anti-detection technique there is, and the most ethical.


Bypass PerimeterX with the SparkProxy Scraping API

Maintaining a real browser farm, a residential pool, current TLS fingerprints, and behavioral pacing is a standing engineering job, because PerimeterX keeps moving. A scraping API collapses those layers into request parameters and keeps them current on the provider's side. The scraping API vs self-managed proxies comparison covers when that trade is worth it; for a behavioral target like PerimeterX, it usually is.

The SparkProxy Scraping API takes a target URL and handles the proxy, the real browser render, the sensor execution, and human-like interaction for you. Each parameter maps to one of the detection layers above:

curl -G "https://scrape.sparkproxy.io/api/v1" \
  -H "X-API-Key: YOUR_API_KEY" \
  --data-urlencode "url=https://example.com/catalog" \
  --data-urlencode "render_js=true" \
  --data-urlencode "premium_proxy=true" \
  --data-urlencode "country_code=US" \
  --data-urlencode "stealth=true" \
  --data-urlencode "human=true" \
  --data-urlencode "wait_for=.product-grid" \
  --data-urlencode "json_response=true"

The same request in Python, with each flag annotated by the layer it satisfies:

import requests

def fetch(url):
    r = requests.get(
        "https://scrape.sparkproxy.io/api/v1",
        headers={"X-API-Key": "YOUR_API_KEY"},
        params={
            "url": url,
            "render_js": "true",       # real Chromium runs the PX sensor and mints _px3
            "premium_proxy": "true",   # residential exit, not a flagged datacenter ASN
            "country_code": "US",       # geo-match the site's expected audience
            "stealth": "true",          # homepage pre-warm, forced referrer, idle delays
            "human": "true",            # simulated mouse movement for the behavioral layer
            "wait_for": ".product-grid",# wait past the block/challenge until content renders
            "json_response": "true",    # envelope with status_code + body + credits_used
        },
        timeout=120,
    )
    return r.json()

render_js=true runs a genuine browser, so the sensor executes and a valid _px3 gets minted. premium_proxy=true routes through a residential IP, clearing the reputation layer. stealth=true adds a homepage pre-warm, a forced Google referrer, and extended idle delays. human=true simulates mouse movement and random interaction delays, which is the behavioral layer that the Press and Hold challenge scores. wait_for holds until your real content appears, so you don't capture a block page by mistake. With json_response=true, the reply is an envelope carrying status_code, body, and credits_used. On the SparkProxy price sheet a residential request with JS rendering costs 25 credits, and stealth and country_code add 5 credits each, so turn them on for hard targets like PerimeterX and leave them off for easy ones.

PerimeterX is not the only vendor you'll meet. SparkProxy keeps companion guides for the others, and the differences are real: see how to bypass Cloudflare, which leans on its JS challenge and Turnstile, and how to bypass DataDome, which decodes on a rt response flag. PerimeterX stands out for its behavioral biometrics and the Press and Hold gesture, so the human=true flag carries more weight here than it does against a pure token check.


A retry loop that reads PerimeterX signals

Tie it together with a loop that branches on the decoded status instead of blindly retrying. With json_response=true, the envelope's status_code mirrors what the target returned, so you can act on a 403, a 429, or a 5xx differently.

import time
import requests

API = "https://scrape.sparkproxy.io/api/v1"
KEY = "YOUR_API_KEY"

def scrape(url, max_tries=4):
    params = {
        "url": url,
        "render_js": "true",
        "premium_proxy": "true",
        "country_code": "US",
        "stealth": "true",
        "human": "true",
        "wait_for": ".product-grid",
        "json_response": "true",
    }
    for attempt in range(1, max_tries + 1):
        env = requests.get(API, headers={"X-API-Key": KEY},
                           params=params, timeout=120).json()
        code = env.get("status_code")

        if code == 200:
            return env["body"]               # rendered HTML, past PerimeterX

        if code == 429:                      # rate limited: exponential backoff
            time.sleep(2 ** attempt)
            continue

        if code == 403 or code >= 500:       # blocked or render failed: retry, already max-stealth
            time.sleep(1.5 * attempt)
            continue

        raise RuntimeError(f"Unexpected PerimeterX status {code} for {url}")

    raise RuntimeError(f"Gave up on {url} after {max_tries} tries")

The loop already runs with premium proxy, stealth, and human interaction on, so a 403 retry gets a fresh residential IP and a new browser session rather than the same doomed request. A 429 backs off exponentially instead of hammering the origin. A 5xx (the API's "all upstream attempts failed" signal) gets a short, growing pause. When nothing clears after a few tries, the honest move is to stop, respect the site's signal, and revisit whether the data is worth pursuing at all.


Frequently asked questions

FAQ

Scraping public data is broadly permitted in many jurisdictions, but bypassing PerimeterX does not grant a legal exemption. Legality depends on what you collect and how: stay on public pages, honor robots.txt and the site's Terms of Service, avoid personal data, and never touch content behind a login. When in doubt, get written permission or use the site's official API.

Because several layers give it away at once. A bare HTTP client sends a TLS fingerprint that matches no real browser, runs no sensor script (so no _px3 token is ever minted), submits no behavioral data, and usually exits from a datacenter IP that HUMAN already distrusts. Any one of those raises the risk score; together they produce an immediate 403.

They are PerimeterX's first-party tracking cookies. _px3 is the current-generation risk token minted by the sensor after it submits a clean payload; _pxhd is the long-lived human-detection cookie set on your first visit (hash:timestamp); _pxvid is your visitor ID. A real returning browser carries _pxhd and a fresh _px3, while a first-contact scraper carries neither, which is itself a signal.

Not reliably by faking events. The Press and Hold widget is a behavioral test: HUMAN measures the press, the micro-movements during the hold, and the release, then scores whether the gesture looks human. Dispatching a synthetic mousedown and mouseup produces a suspiciously perfect signal that fails. The workable path is a genuine browser session with real, human-shaped interaction, or a rendering API that runs one for you.

No. A residential IP clears the reputation layer, which is often the fastest block, but PerimeterX still scores your sensor fingerprint, the _px cookies, your TLS handshake, and your behavior. A residential IP behind a Python client with no sensor telemetry still gets blocked. Combine a clean IP with a real browser and human-like pacing.

All three score IP reputation, TLS, and a JavaScript fingerprint, so the fundamentals carry over. PerimeterX weights behavioral biometrics more heavily and ships the Press and Hold gesture challenge, so real interaction (not just a passed token) matters more. It also runs first-party more often, serving the sensor from the customer's own domain, so there's no third-party host to allowlist or block.


Limited-time · 50% off

Get 50% off your first purchase

Premium datacentre proxies with unlimited bandwidth. Use the code at checkout.

Offer ends soon — claim it before it's gone

Claim Discount

About the Author

This guide was written by the SparkProxy Technical Team. SparkProxy builds and operates global datacenter and residential proxy networks plus a managed Scraping API, so we watch how anti-bot systems like PerimeterX and HUMAN Bot Defender score traffic from both sides of the request every day. The patterns here reflect current behavior validated against Playwright, playwright-stealth, curl_cffi 0.7+, and the SparkProxy Scraping API as of July 2026. Our aim is a straight, ethical explanation of how PerimeterX works and how to collect public data without abusing anyone's infrastructure, not a promise of a permanent bypass, because no such thing exists.

Citations: curl_cffi, browser TLS impersonation for Python · SparkProxy Scraping API documentation

Keep reading

Related articles