Skip to content

PerimeterX · HUMAN Security

Keep clearance and browser identity in one session. Start with the PX-protected page and your session proxy. Supply captured visitor and block context when you already have it.

POST /createTask · example
{
  "clientKey": "capzy_YOUR_API_KEY",
  "task": {
    "type": "AntiPerimeterXTask",
    "websiteURL": "https://target.com/protected-page",
    "blockMode": "html",
    "blockData": "<!DOCTYPE html> … the raw 403 Press-and-Hold block page (embeds window._pxAppId / _pxUuid / _pxJsClientSrc) …",
    "initCookies": {
      "_pxhd": "<_pxhd from your 403 response>",
      "_pxvid": "<your uuid>"
    },
    "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
    "proxyType": "http",
    "proxyAddress": "123.45.67.89",
    "proxyPort": "8080",
    "proxyLogin": "user",
    "proxyPassword": "pass"
  }
}

Illustrative values. Replace the inputs with values from your current integration.

Protected URL + PX context + proxyYour session proxy requiredPX cookies + solver user-agent

Collect the PX application context.

Start with the PX-protected page and your session proxy. Supply captured visitor and block context when you already have it.

Your API key authenticates Capzy.
Provider keys, challenge IDs, images, and session values belong inside task.

Match the page to its application configuration.

  1. Copy the protected document's full URL into websiteURL. In Network/Sources, inspect the PX client script or block configuration for the PX-prefixed application ID.
  2. If supplying pxAppId, copy the complete value. Read _pxvid and _pxhd from the current cookie jar for uuid and _pxhd when relevant.
  3. Preserve vid separately when the block configuration supplies a different visitor ID.
Input collection example - replace illustrative values
{
  "websiteURL": "https://example.com/account",
  "pxAppId": "PX_YOUR_APP_ID",
  "uuid": "YOUR_CURRENT_VISITOR_ID",
  "_pxhd": "YOUR_CURRENT_PXHD"
}

These visitor fields are optional context. Keep them from this page's session rather than reusing a sample visitor.

How PerimeterX · HUMAN Security works

PerimeterX (now HUMAN Security) is a bot protection platform that scores requests on IP trust, browser fingerprint, TLS/JA3, and on-page behavior. The visible CAPTCHA is a press-and-hold button; the cookie hierarchy is _px3 (newer/strict, 60s expiry) > _px2 (older sites) > _pxhd (fallback).

Capzy's proprietary solver returns the PerimeterX clearance cookies (_px3 / _px2 / _pxhd) needed to access the protected page. PerimeterX clearance cookies are bound to the solving IP — your client must replay requests through the same proxy you supplied at solve time, so the `AntiPerimeterXTask` (proxy-required) variant is the only supported task type. The ProxyLess variant is intentionally NOT offered for PerimeterX because tokens issued from our pool IPs would be rejected by your downstream calls.

  • Solves Hold Captcha (HUMAN Challenge) — 8.5–11s hold
  • Bring your own proxy on the Task variant — tokens are IP-bound
  • Returns _px3 (priority) → _px2 → _pxhd with full _px* cookie set
  • Ready-to-paste `cookie` header string + structured `cookies` array
  • `challengePresented` field tells you whether the Hold widget actually fired
  • User-Agent capture for session continuity

Know what goes in.

Choose a field to see its purpose, where to get it, and its place in the request. Optional values omitted from the example can be added when your flow uses them.

Your request returned a full HTML block page ("Access to this page has been denied" / Press & Hold). Send its raw body as blockData. The block page embeds your session identity (appId, uuid, vid, and the first-party captcha.js path), so when it is present we rebuild the Press & Hold widget DIRECTLY from it, with no round-trip to the protected page, and bind the clearance to YOUR _pxvid. Pass your session cookies (especially _pxhd) so the result validates when you replay. If you cannot capture the block body, omit it and send websiteURL + proxy only; we navigate the page ourselves and render the widget live.

Request authentication

Required task inputs

Optional context

Proxy connection

websiteURL

stringRequired

Full URL of the PROTECTED parent page (the one the user is trying to access). NOT the PerimeterX challenge iframe URL — hitting `iframe.hsprotect.net` or `*.px-cdn.net` directly returns an empty stub and fails. Capzy rejects iframe URLs with `ERROR_INVALID_PARAMS` so you catch the wrong input on the first try.

Where it comes from

Copy the full page URL from the address bar or the document request in Network, including the path where this flow runs.

Open the input guide
Example valuehttps://target.com/protected-page
POST /createTask · selected configuration
{
  "clientKey": "capzy_YOUR_API_KEY",
  "task": {
    "type": "AntiPerimeterXTask",
    "websiteURL": "https://target.com/protected-page",
    "blockMode": "html",
    "blockData": "<!DOCTYPE html> … the raw 403 Press-and-Hold block page (embeds window._pxAppId / _pxUuid / _pxJsClientSrc) …",
    "initCookies": {
      "_pxhd": "<_pxhd from your 403 response>",
      "_pxvid": "<your uuid>"
    },
    "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
    "proxyType": "http",
    "proxyAddress": "123.45.67.89",
    "proxyPort": "8080",
    "proxyLogin": "user",
    "proxyPassword": "pass"
  }
}

The selected field is highlighted when present. Preserve the declared value type and current challenge context.

Make your first request.

Create the task, keep its taskId, and poll until status is ready. Then read the selected variant's solution.

More examples
  1. Create the task

    Send clientKey and the selected task to /createTask from your server. Keep the returned taskId.

  2. Wait for the solution

    Send that same key and ID to /getTaskResult. While processing, wait two seconds before checking again. Stop on Ready or an error.

  3. Apply the result

    Merge the complete returned cookies with your existing jar, pin solution.userAgent, and retry through the same proxy. Preserve returned visitor IDs and inspect challengePresented/ipBound metadata when continuing the flow.

solve_perimeterx.py
import json, os, time
import requests

API = "https://api.capzy.ai"
KEY = os.environ["CAPZY_API_KEY"]
task = json.loads("{\"type\":\"AntiPerimeterXTask\",\"websiteURL\":\"https://target.com/protected-page\",\"blockMode\":\"html\",\"blockData\":\"<!DOCTYPE html> … the raw 403 Press-and-Hold block page (embeds window._pxAppId / _pxUuid / _pxJsClientSrc) …\",\"initCookies\":{\"_pxhd\":\"<_pxhd from your 403 response>\",\"_pxvid\":\"<your uuid>\"},\"userAgent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36\",\"proxyType\":\"http\",\"proxyAddress\":\"123.45.67.89\",\"proxyPort\":\"8080\",\"proxyLogin\":\"user\",\"proxyPassword\":\"pass\"}")

def call(path, payload):
    response = requests.post(f"{API}/{path}", json={"clientKey": KEY, **payload}, timeout=30)
    response.raise_for_status()
    data = response.json()
    if data.get("errorId", 0) or data.get("status") == "failed":
        raise RuntimeError(data.get("errorDescription") or data.get("errorCode") or "Task failed")
    return data

created = call("createTask", {"task": task})
for attempt in range(60):
    result = call("getTaskResult", {"taskId": created["taskId"]})
    if result.get("status") == "ready":
        solution = result["solution"]
        print(solution)
        break
    time.sleep(2)
else:
    raise TimeoutError("Task is still processing; keep the taskId to check it later.")

Before runningReplace all illustrative inputs with your captured values. Set CAPZY_API_KEY in your environment. Install requests with pip install requests.

Polling and failuresThe example checks HTTP/API failures, uses 30-second request timeouts, and stops after 60 result checks. Retain taskId if your task is still processing.

Understand what comes back.

Follow this task from creation to completion. Response fields and usage follow your selected request variant.

PX cookies + solver user-agent

Merge the complete returned cookies with your existing jar, pin solution.userAgent, and retry through the same proxy. Preserve returned visitor IDs and inspect challengePresented/ipBound metadata when continuing the flow.

string
Clearance cookie value alone (priority: _px3 > _px2 > _pxhd) — use when you only need the proof string.
string
Ready-to-paste `Cookie:` header value (`_px3=...; _pxhd=...; _pxvid=...`). Domain is intentionally omitted — it's a header value, not a jar entry, so it ships to whichever URL you're calling.
array
All _px* cookies as `{name, value, domain, path}` for customers building their own cookie jar.
string
User-Agent used during solve — must match on all replay requests (clearance is UA-bound).
string
**Only returned when `challengePresented` is `true`.** Visitor identifier (the `_pxvid` value) PerimeterX bound `_px3` to during the Hold Captcha. PerimeterX validates `_px3` + `_pxvid` + `_pxhd` as a triple — pin this `uuid` on your downstream session for the replay to validate. Omitted on silent passes because no fresh `_pxvid` is minted.
string
**Only returned when `challengePresented` is `true`.** PerimeterX's `vid` parameter (`window._pxhc.vid` on the parent page). Usually equal to `uuid`; differs only when the target exposes them separately.
boolean
`true` if PerimeterX actually rendered the Hold Captcha (press-and-hold) widget on this solve and we held it; `false` if our fingerprint passed silently. Both produce valid cookies — this just tells you which path minted them.
number
Seconds we held the press-and-hold button (`8.5`–`11.0`). `0` when the Hold Captcha was not presented.
boolean
Always `true` for PerimeterX — flags that the proof is tied to the solving IP, so replays must come through the same proxy.
POST /getTaskResult · example response
{
  "errorId": 0,
  "status": "ready",
  "solution": {
    "token": "<_px3 cookie value>",
    "cookie": "_pxvid=<uuid>; _px3=<value>; _pxhd=<value>",
    "cookies": [
      {
        "name": "_pxvid",
        "value": "<uuid>",
        "domain": ".target.example.com",
        "path": "/"
      },
      {
        "name": "_px3",
        "value": "<value>",
        "domain": ".target.example.com",
        "path": "/"
      },
      {
        "name": "_pxhd",
        "value": "<value>",
        "domain": ".target.example.com",
        "path": "/"
      }
    ],
    "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
    "challengePresented": true,
    "holdDurationSec": 9.4,
    "uuid": "<the _pxvid value PX bound _px3 to>",
    "vid": "<usually equal to uuid>",
    "ipBound": true
  }
}

Illustrative values. Additional metadata may be returned; only Ready contains a completed solution.

Use the result

Merge the complete returned cookies with your existing jar, pin solution.userAgent, and retry through the same proxy. Preserve returned visitor IDs and inspect challengePresented/ipBound metadata when continuing the flow.

Execution context result is the parsed Ready response from the polling example. session, when referenced, is your existing requests.Session (or browser-compatible session) with the original cookie jar, headers, and solving proxy configured in session.proxies for both http and https. Keep this client for the complete flow. task is the task object you submitted. For Shape, action_body is the original protected request body as bytes or a string, with its original Content-Type in session.headers. Install curl_cffi when the fragment imports it; match the target's required browser/TLS identity.

Use the result · Python

from http.cookies import SimpleCookie
sol = result["solution"]
cookie_values = SimpleCookie()
cookie_values.load(sol["cookie"])
for name, cookie in cookie_values.items():
    session.cookies.set(name, cookie.value, domain="target.example.com", path="/")
session.headers["User-Agent"] = sol["userAgent"]
# session retains the original cookie jar and the solve's proxy.
resp = session.get("https://target.example.com/", timeout=30)
print(resp.status_code)

When something doesn't work.

Check your captured values, the selected variant, and the response before starting another task.

My API call returns a 403 with a JSON body, not a CAPTCHA page. How do I solve it?

That is PerimeterX's Advanced Blocking Response (ABR): high-security tenants block your API/XHR call with a JSON 403 instead of an HTML page, so nothing renders a press-and-hold. Solve it with the same `AntiPerimeterXTask` plus a few fields: - `blockMode`: `"xhr"` - `blockData`: the raw JSON body of the 403 you got (it carries `appId` / `uuid` / `blockScript`) - `initCookies`: your flagged session cookies, including the `_px3` that is now getting 403s - `blockUrl` / `blockMethod`: the exact request that got blocked We rebuild the challenge from `blockData`, fire `blockUrl` so PerimeterX renders the press-and-hold, hold it, and return a fresh `_px3` that clears your endpoint. Replay through the SAME proxy + User-Agent. ```json { "type": "AntiPerimeterXTask", "websiteURL": "https://site.com/account/summary", "blockMode": "xhr", "blockData": "{\"appId\":\"PX...\",\"uuid\":\"...\",\"blockScript\":\"/.../captcha.js?...\"}", "initCookies": { "_px3": "<your flagged token>", "_pxhd": "..." }, "blockUrl": "https://site.com/api/search", "blockMethod": "POST", "proxyType": "http", "proxyAddress": "...", "proxyPort": 8000, "userAgent": "Mozilla/5.0 ..." } ``` Firing the blocked request is what makes the challenge appear: PerimeterX's client renders the press-and-hold only when it sees a monitored request return the ABR block, so `blockUrl` (with the correct `blockMethod`) is required for XHR/ABR sites. For a simple GET endpoint set `blockMethod: "GET"`.

My request returns an HTML "Press & Hold" block page, not JSON. What do I send?

Send the raw HTML block body as `blockData` with `blockMode: "html"`. Unlike the XHR/ABR case, you do NOT need `blockUrl`: the HTML block page already embeds your session globals (`window._pxAppId`, `_pxUuid`, `_pxVid`, and the first-party `captcha.js` path), so we parse them straight out of your HTML and rebuild the Press & Hold widget directly from it. That skips loading the protected page entirely (faster, and it cannot silent-pass), and binds the clearance to YOUR `_pxvid` / `_pxhd` so it validates on replay. Pass your session cookies (especially `_pxhd`) from the blocked response, and replay through the SAME proxy + User-Agent. ```json { "type": "AntiPerimeterXTask", "websiteURL": "https://site.com/protected-page", "blockMode": "html", "blockData": "<!DOCTYPE html> ... the raw 403 Press-and-Hold block page ...", "initCookies": { "_pxhd": "<your _pxhd>", "_pxvid": "<your uuid>" }, "proxyType": "http", "proxyAddress": "...", "proxyPort": 8000, "userAgent": "Mozilla/5.0 ..." } ``` If your HTML block does not embed those globals, or you cannot capture the block body, just send `websiteURL` + proxy: we navigate the page ourselves, read the widget config live, and render an isolated Press & Hold to solve. Supplying the block is faster and more reliable, but it is optional for HTML.

Do I still just send websiteURL for normal sites?

Yes. For a standard PerimeterX page, send `websiteURL` plus your proxy and nothing changes — the solver navigates the page, trips the press-and-hold if it appears, and returns the clearance cookies. The `blockMode` / `blockData` / `blockUrl` fields are OPTIONAL and only needed for XHR/ABR endpoints where your API call returns a JSON 403 and no page renders a widget. If you do not pass `blockData`, behavior is exactly as before.

Why isn't there a ProxyLess variant?

PerimeterX clearance cookies (_px3 / _px2 / _pxhd) are cryptographically tied to the IP they were issued to. A token we solved on our pool IP would be rejected the moment your downstream HTTP client called the protected endpoint from your own IP. Supplying your own proxy is the only way the solve is reusable — so we don't ship a ProxyLess variant for PerimeterX. Other captchas (Turnstile, hCaptcha base, GeeTest) aren't IP-bound and DO have ProxyLess variants.

Which cookie do I need?

Three flavours of the same proof are returned: `token` (just the highest-priority cookie value), `cookie` (a ready-to-paste `Cookie:` header string with every _px* cookie joined), and `cookies` (the structured array). Most customers using `requests` / `httpx` just want `cookie` — drop it straight into the header.

What does the solution response look like?

```json { "cookie": "_px3=<value>; _pxhd=<value>; _pxvid=<uuid>", "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...", "challengePresented": true, "holdDurationSec": 9.4, "uuid": "<the _pxvid value PX bound _px3 to>", "vid": "<usually equal to uuid>", "ipBound": true } ``` For most integrations you just need `cookie` (drop into the `Cookie:` header) and `userAgent` (drop into the `User-Agent:` header). `uuid` + `vid` are returned when the Hold Captcha fires — useful if you need to pin the visitor identity in a downstream session that doesn't use a cookie jar. `challengePresented` / `holdDurationSec` are diagnostic — `true` means the Hold Captcha widget rendered and we held it, `false` means PerimeterX silent-passed our session (cookies valid either way). Make the replay request through the SAME proxy you supplied at solve time.

Do you support PerimeterX Hold Captcha (the press-and-hold widget)?

Yes — that's the primary challenge type our solver handles. PerimeterX's Hold Captcha (officially "HUMAN Challenge", branded since HUMAN Security acquired PerimeterX in 2022) is the press-and-hold button that fires when PerimeterX's risk score crosses a threshold. Under the hood it runs a proof-of-work computation while streaming cursor-variance telemetry during the 8–10s hold. Our solver detects the widget (`#px-captcha`, `[class*="press-and-hold"]`, etc.), holds the button for 8.5–11.0s, and captures the resulting _px3 cookie. The `challengePresented` field in the solution tells you which path minted the cookies: `true` if we exercised the press-and-hold, `false` if our fingerprint passed silently before PerimeterX escalated to the widget.

Why does _px3 expire so fast?

Newer high-security PerimeterX deployments rotate _px3 every ~60 seconds as part of session continuity scoring. Re-solve when it expires, or use a sticky proxy + a custom HTTP client that mimics Chrome's behavior to refresh _px3 inline.

What kind of proxy do I need?

Use a proxy with good IP reputation; low-trust IPs fail PerimeterX's validation because PerimeterX scores IP trust as a primary signal. Use a sticky session (5-10 min stickiness) so the same IP can be reused for both the solve and your downstream calls.

What are `uuid` / `vid` and do I need them?

Strongly recommended for any site that enforces session continuity. `uuid` is the `_pxvid` cookie value on the target page — find it in DevTools → Application → Cookies → `_pxvid`. Pass it on createTask and we pre-seed it on the solver's browser context so PerimeterX issues clearance tied to YOUR visitor. Without it, the cookie belongs to a session PerimeterX won't recognize when your client replays. `vid` is sometimes distinct from `uuid` (read from `window._pxhc.vid`); if you only have `uuid` we use it for both.

I keep getting re-challenged when I browse more pages — how do I keep a session alive?

PerimeterX re-scores every request, so a valid _px3/_px2 alone won't carry a session that still looks automated. Three things keep a long session clean: (1) Replay from a normal client — an automation-flagged browser (`navigator.webdriver=true`, CDP/Playwright artifacts) gets re-challenged on nearly every page load even with a valid clearance cookie, because PerimeterX re-runs its client fingerprint each load; a plain HTTP client (`requests`/`httpx`) or a non-instrumented browser does not. (2) Pin ONE visitor — pass the SAME `uuid` (`_pxvid`) on every solve and keep that _pxvid in your downstream cookie jar, reusing the `uuid` the solution returns on the next createTask. If each solve mints a fresh _pxvid, PerimeterX sees one IP cycling through many new visitors in seconds, which is itself a bot signal that accelerates blocking. (3) Stay on the same sticky IP and reuse the returned `userAgent` — both are part of what the clearance is bound to. With these in place a continuous session is challenged only occasionally, and a single re-solve with the same `uuid` clears it.

I tried passing the iframe URL (iframe.hsprotect.net) and it fails — why?

That's the PerimeterX challenge-iframe URL, not the protected page. Hitting it directly returns a ~2 KB empty stub because the press-and-hold widget only renders inside the parent site's context. Pass the URL of the parent page (the actual page the user is trying to access) as `websiteURL`. Capzy now rejects iframe URLs with `ERROR_INVALID_PARAMS` so the misconfig is obvious on the first try.

Connect PerimeterX · HUMAN Security to your workflow.

Get your Capzy API key and use this guide's integration.

$3.00Per 1,000 solves
8.0sAverage solve time

AntiPerimeterXTask · Live configuration