_px3 cookie?Short answer: _px3 is the cookie PerimeterX (now HUMAN Security) issues to a browser whose sensor check passed. It carries a payload encrypted against a key the site holds, a lifetime enforced inside that payload, and an HMAC signature over the payload. It is not a session token you can copy to another client: the signature covers what the payload says about your IP and user agent, and the edge re-signs on a schedule of its own.
This page is organised around the three things that actually decide whether your requests pass: what the fields mean, which clock is running, and which identity the cookie is pinned to. If the cookie is in your jar and the page is still a challenge, the answer is almost always one of those three.
A _px3 value is colon-separated: the encrypted payload, a timestamp, a hash and a signature. Practically, what matters is:
| Part | What it decides |
|---|---|
| Encrypted payload | Contains the score PerimeterX calculated for the session, the IP and user agent it was issued to, and a UUID that ties it to _pxvid. |
| Timestamp | When it was issued. Site policy decides how long it is considered fresh. |
| Signature | HMAC over the payload with the site's own key. Altering the payload breaks it, so you cannot edit a low score into a high one. |
The field count has changed across deployments — older values carry three colon-separated fields, newer ones four, with the extra field holding version metadata. Parse by splitting on : and taking the signature from the last field, not from a fixed index, or your code will break quietly the day the format moves.
This is where most implementations break. There is the Expires the server sets on the cookie, and there is a lifetime baked into the payload that the server enforces on its own. They do not have to agree. A cookie can be sent back to you with an Expires two hours out while the payload was already close to expiring, because the server's own TTL is measured from issuance, not from the last time you used it.
| Clock | Where it lives | Measured from | What it causes |
|---|---|---|---|
Cookie Expires / Max-Age | The Set-Cookie header | The last write | When the browser drops the value. Usually later than the payload dies, which is why this attribute is a trap. |
| Payload TTL | Inside the signed payload | Issuance | The edge starts challenging while your jar still looks healthy. |
| Re-sign | Any response that carries Set-Cookie | Whenever the edge decides | The value you hold is superseded. Sending a stale copy is itself a signal. |
If you reuse a cookie until the browser would drop it, you spend the last part of that window getting challenge responses on a cookie that still looks valid in your jar. Reading Set-Cookie on every response and replacing the jar entry unconditionally is the fix — not parsing the Expires attribute.
_pxvid and _pxhd travel with it. _px3 without _pxvid is a signature over a session the edge cannot look up._pxvid the edge has no session history at all and runs the full sensor check again.Two things are worth writing down: the shape that earns the cookie, and the way you decide it is still fresh. Neither involves decoding the payload.
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
ctx = browser.new_context(proxy={"server": PROXY}, locale="en-US")
page = ctx.new_page()
page.goto(url, wait_until="networkidle") # the sensor runs here
jar = {c["name"]: c["value"] for c in ctx.cookies()}
print(sorted(jar)) # expect _px3, _pxvid, _pxhd
for attempt in range(3):
r = page.request.get(url) # same context: cookies, UA and TLS
if r.status == 200:
break
# Never lift jar out of this context. The exit IP, user agent and TLS
# stack are part of the pass, not decoration around it.
import time
def px3_age(value: str) -> float:
parts = value.split(":")
# 3 fields on older deployments, 4 on newer; the timestamp sits at index 1 either way.
return time.time() - int(parts[1])
age = px3_age(jar["_px3"])
print("signed", round(age / 60, 1), "minutes ago")
if age > REFRESH_BUDGET: # your own budget, not the cookie's Expires
refresh_session() # new browser context, same exit IP
Most "the cookie is there but the page is blocked" reports collapse into five causes. Check them in this order, because the first two account for most of them:
| What you see | What it usually means | First thing to change |
|---|---|---|
| 200 once, then a challenge on the next request from the same jar | The payload TTL is measured from issuance, not from last use | Replace the jar entry on every Set-Cookie; stop waiting for Expires |
| Challenge immediately after a proxy rotation | The payload records the IP it was issued to | Pin one exit IP per session; rotate sessions, not requests |
Challenge after replaying a browser jar from requests | The TLS fingerprint no longer agrees with the user agent in the payload | Keep the client stack that earned the cookie, or re-earn it in the new stack |
_px3 present, _pxvid missing | The edge cannot resolve the session the signature refers to | Restore the full jar rather than the one cookie you care about |
| Fine for minutes, then challenges under load | The session is re-scored on machine-speed traffic at the next re-sign | Lower the rate, or take a fresh value per session |
PerimeterX has a server-to-server token flow used by some integrations. That path returns a token verified directly with HUMAN's API and does not require a browser-side cookie at all. If a site you are authorised to test supports it, it is far more stable than a cookie jar — see the PerimeterX endpoint for how the SolveHub API exposes both modes.
Where s2s is not offered, the honest options are: run the sensor in a real browser and keep the whole jar plus network identity, or obtain a valid _px3 for a given URL from a solving API. Regenerating the payload yourself means reimplementing an obfuscated, rotating script and the site's key — not a maintenance burden worth taking on.
_pxvid — visitor ID. The _px3 payload is bound to this value; on many sites it is a prerequisite rather than a companion._pxhd — HTTP-only, tied to _pxvid; required alongside _px3 on many properties.pxcts — short-lived challenge token, rotated constantly._pxde — deferred-cookie placeholder while the sensor is still running.Can I decode the _px3 payload? Not usefully. It is encrypted with a key the site controls, so you can see its length, not its contents. There is no client-side key.
Why does my _px3 stop working before it expires? Because the server enforces its own lifetime from issuance, which can be shorter than the cookie's Expires attribute. Replace the jar entry on every Set-Cookie response.
Does _px3 work from a different IP? Usually not for long. The payload records the IP and user agent of the issuing session, and a session evaluated under machine-speed traffic can be re-scored at the next re-sign.
Do I need _pxvid as well, or is _px3 enough? On most properties you need both. _px3 alone is a signature over a session the edge has no record of.
Can I raise the score by editing the cookie? No. The signature covers the payload, so any edit invalidates it. The score is computed from session behaviour, which is why the practical answer is a real browser or a fresh value from a solving API.