SurSolver

BlogPerimeterX Bypass: Solve for _px3 Cookies via API

PerimeterX Bypass: Solve for _px3 Cookies via API

· Updated · 9 min read

By the end of this guide you will be able to confirm that a site runs PerimeterX, find the values the task needs, get the _px3 cookies from AntiPerimeterXTask, and replay them from the connection that earned them. It also covers how to tell which layer of the stack is blocking you when a 403 keeps coming back.

What PerimeterX checks

PerimeterX is now part of HUMAN Security, but the cookies, endpoints and most documentation still say PerimeterX or PX. The product in front of scrapers is Bot Defender: a JavaScript sensor plus a cloud risk score per request, with models trained for each customer. Enforcement is a block page, or a 403 that carries a PX reference id.

Reports on how PX scores a request list these layers: TLS, HTTP/2 and header fingerprints, IP reputation, the JavaScript sensor, behaviour and the per-customer model. The fingerprints are scored before any challenge is shown. That is why a correct solve can still fail when it is replayed from the wrong client or the wrong address. The PerimeterX solver page has a shorter overview.

Invisible, press-and-hold or mobile

Surface What the visitor sees What a solve produces
Invisible sensor Nothing; silent scoring _px3 cookie
Press and hold A "Press & Hold to confirm you are human" page, sometimes a slide or image-match variant _px3 cookie
Mobile app Nothing in a browser Reportedly an x-px-authorization header set, not cookies

SurSolver has one task type for PerimeterX, AntiPerimeterXTask, and its result holds cookies, token and userAgent. No header output is documented, so do not plan around mobile app traffic.

The task accepts blockData, blockMode and initCookies as optional fields. Other providers' documentation uses the same names for their press-and-hold flow: blockData is the raw body of the 403 you fetched through your proxy, initCookies the cookies of that response, and blockMode the shape of the block (for example xhr). SurSolver's reference lists the names but does not say what they change, so test them on your target before relying on them.

Confirm that a site runs PerimeterX

Page source and cookies take a minute to check; the Network panel takes a few more.

Marker Where you see it
_px3 or _px2 cookie on first load Application → Cookies
_pxvid, sometimes _pxhd Application → Cookies
_pxAppId: PX followed by eight characters Page source, window._pxAppId
//client.perimeterx.net/<id>/main.min.js Script tag in the page source
Requests to *.px-cloud.net, *.px-cdn.net, *.perimeterx.net Network panel
An unexplained POST on every page, or an init.js on a random path of the site's own domain such as /px/PXxxxxxxxx/init.js Network panel; first-party deployments hide the PX hosts
px-captcha, captcha.px-cdn.net, "Please verify you are a human", "Access to this page has been denied" Body of a 403 or 429
An iframe on iframe.hsprotect.net or *.px-cdn.net The challenge itself; never use its URL as websiteURL

This function checks a response against the markers. Feed it the status, the Set-Cookie headers and the body of a request you made through your proxy. Page source varies by site, so confirm any app id it prints in DevTools.

solve.js
const PX_BODY_MARKERS = [
  '_pxAppId',
  'px-captcha',
  'captcha.px-cdn.net',
  'perimeterx',
  'Please verify you are a human',
  'Access to this page has been denied',
];
const PX_COOKIE_NAMES = ['_px3', '_px2', '_pxhd', '_pxvid'];
const BLOCK_STATUSES = [403, 429];
const APP_ID_PATTERNS = [
  /_pxAppId['"]?\s*[:=]\s*['"](PX[0-9A-Za-z]{8})['"]/,
  /client\.perimeterx\.net\/(PX[0-9A-Za-z]{8})\/main\.min\.js/,
  /\/(PX[0-9A-Za-z]{8})\/init\.js/,
];

export function inspectPerimeterX(status, setCookieHeaders, body) {
  const lowerBody = body.toLowerCase();
  const markers = PX_BODY_MARKERS.filter((m) => lowerBody.includes(m.toLowerCase()));
  const cookies = PX_COOKIE_NAMES.filter((name) =>
    setCookieHeaders.some((header) => header.startsWith(`${name}=`)),
  );
  const appId = APP_ID_PATTERNS.map((pattern) => body.match(pattern)?.[1]).find(Boolean) ?? null;
  return {
    blocked: BLOCK_STATUSES.includes(status) && markers.length > 0,
    markers,
    cookies,
    appId,
  };
}

The cookies you get back

Cookie Role
_px3 The main clearance cookie; newest and highest priority
_px2 The older equivalent, used when there is no _px3
_pxhd A hardened-device cookie; one provider reports it lasting up to 24 hours
_pxvid The visitor id; send it as uuid

The exact names vary by target. Replay everything in solution.cookies, not only _px3. When token is present it carries the main cookie value, but cookies is the full set.

Lifetimes are reported inconsistently: about 60 seconds for _px3 on newer high-security deployments, up to an hour on others. Neither figure is guaranteed, so cache a working set and re-solve when you are blocked again.

Find the parameters

Field Required Where to find it
websiteURL Yes The parent page URL in the address bar, never the iframe URL
pxAppId Yes _pxAppId in page source, or the id in the main.min.js URL
proxy fields Yes Your sticky proxy; there is no proxyless form
uuid No The _pxvid cookie value
vid No window._pxhc.vid in the console; it often equals uuid
userAgent No The user agent of the session you will replay with

Pick the proxy now. Residential, mobile or static ISP addresses pass the IP layer; datacenter addresses fail it. Keep the session sticky for roughly 5 to 10 minutes so fetch, solve and replay share one IP. The proxy guide lists the accepted formats, and proxies for captcha solving covers choosing a class.

Solve it and replay the cookies

This script creates the task, polls every 3 seconds and loads the page in Playwright with the returned cookies, the returned user agent and the same proxy. Replace the placeholders with your own values; delete the uuid line if you have no _pxvid.

solve.js
import { chromium } from 'playwright';

const API = 'https://api.sursolver.com';
const CLIENT_KEY = 'YOUR_API_KEY';
const PAGE_URL = 'https://www.example.com/products';
const PX_APP_ID = 'YOUR_PX_APP_ID';
const PXVID = 'YOUR_PXVID_COOKIE_VALUE';
const USER_AGENT = 'YOUR_BROWSER_USER_AGENT';
const PROXY = {
  host: 'proxy.example.com',
  port: 8000,
  login: 'YOUR_PROXY_LOGIN',
  password: 'YOUR_PROXY_PASSWORD',
};
const POLL_MS = 3000;
const POLL_ATTEMPTS = 60;

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function call(path, body) {
  const res = await fetch(`${API}${path}`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  });
  // API errors are HTTP 200 with errorId 1; anything else is a transport failure.
  if (!res.ok) throw new Error(`HTTP ${res.status} from ${path}`);
  const data = await res.json();
  if (data.errorId !== 0) throw new Error(`${data.errorCode}: ${data.errorDescription}`);
  return data;
}

async function solvePerimeterX() {
  const created = await call('/createTask', {
    clientKey: CLIENT_KEY,
    task: {
      type: 'AntiPerimeterXTask',
      websiteURL: PAGE_URL,
      pxAppId: PX_APP_ID,
      uuid: PXVID,
      userAgent: USER_AGENT,
      proxyType: 'http',
      proxyAddress: PROXY.host,
      proxyPort: PROXY.port,
      proxyLogin: PROXY.login,
      proxyPassword: PROXY.password,
    },
  });
  for (let attempt = 0; attempt < POLL_ATTEMPTS; attempt += 1) {
    const result = await call('/getTaskResult', {
      clientKey: CLIENT_KEY,
      taskId: created.taskId,
    });
    if (result.status === 'ready') return result.solution;
    await sleep(POLL_MS);
  }
  throw new Error(`task ${created.taskId} was not ready in ${POLL_ATTEMPTS} polls`);
}

async function main() {
  const solution = await solvePerimeterX();
  const browser = await chromium.launch({
    proxy: {
      server: `http://${PROXY.host}:${PROXY.port}`,
      username: PROXY.login,
      password: PROXY.password,
    },
  });
  try {
    // solution.userAgent is present only when the solver reports one.
    const context = await browser.newContext({ userAgent: solution.userAgent ?? USER_AGENT });
    const { hostname } = new URL(PAGE_URL);
    await context.addCookies(
      solution.cookies.map((cookie) => ({
        name: cookie.name,
        value: cookie.value,
        domain: cookie.domain || hostname,
        path: cookie.path || '/',
      })),
    );
    const page = await context.newPage();
    const response = await page.goto(PAGE_URL);
    console.log('status:', response.status());
  } finally {
    await browser.close();
  }
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Check the content, not only the status: a hold that the challenge UI accepted does not mean the site's risk check passed, so wait for the protected content before you treat the session as cleared. With Selenium instead of Playwright the same cookies and user agent apply; see Selenium captcha solving.

Stay cleared

  • IP binding is strict. Fetch, solve and replay go through the same proxy; a different IP, user agent or proxy gives a 403 even with a valid cookie.
  • Send the user agent from the solution exactly when one is returned. Otherwise reuse the one you triggered the block with. See browser headers and user agents.
  • PX trains per-site models. A result that works on one app id may fail on another, so test on the real deployment.
  • Each accepted task is charged and a task that finishes as failed automatically returns its original charge once, so re-solving in a loop on a bad proxy wastes time without clearing anything (billing).

Which layer is blocking you

What you observe Layer Does a new solve help? What to do
403 block page and no _px3 yet Challenge Yes Send AntiPerimeterXTask. Akamai block pages look similar but return cookies instead — Akamai solver API
"Press & Hold" page in your own automation Behaviour No Do not click or navigate during the hold; it reads mouse movement and hold timing. The button often sits in a closed Shadow DOM, so Selenium locators miss it. Send the task to the API instead
403 even though the cookie is valid IP or user agent consistency Only from the same IP Replay through the proxy and user agent of the solve
The captcha keeps coming back IP reputation No Move to a cleaner IP range; this is a proxy problem
429 The site's own rate limit No Slow down or widen the pool of addresses
requests-style client is blocked before any challenge TLS, HTTP/2, headers No Use a client whose fingerprint and header order match a real browser
ERROR_FAILED from the API Solve failed Submit a new task The task was charged; check proxy quality and websiteURL
ERROR_INVALID_REQUEST Request shape After correcting it The description names the field; nothing was charged

Next steps

Open the target in your own proxy, confirm the markers and copy the app id and the parent page URL. Then send them with AntiPerimeterXTask in one createTask call. The accepted parameters are in the public catalog (GET /v1/task-types), and the statuses and failure handling are in the task lifecycle guide. For DataDome, which returns a datadome cookie instead, see DataDome solver API.

More articles