VexSolver

PerimeterX / HUMAN ACTIVE

PX2/PX3/PX23/PX203/PX182 protocol + WASM captcha automation.

Overview

PerimeterX (now HUMAN Security) uses a multi-event fingerprint protocol:

  • PX2 — init event
  • PX3 — canvas, audio, WebGL fingerprint
  • PX23 / PX203 — high-security hashes
  • PX182 — activity heartbeat

VexSolver sends all events natively in Go, decodes the XOR-encrypted OB responses, and bakes the _px3, _pxhd, _pxde (and _px2 where applicable) cookies. If the site triggers a WASM captcha challenge, VexSolver automatically solves the proof-of-work and simulates the Press & Hold interaction.

Supports first-party, third-party, and hsprotect.net PX deployments (Microsoft / Outlook signup). Auto-detects the AppId and collector URL from the target page; custom deployments can override via the app_id request field.

Cookies returned

  • _px3 — main session token, RSA-signed by PerimeterX
  • _pxhd — hardened device ID (UUID)
  • _pxde — device enrichment payload (timestamp + incident id)
  • _px2 — init token (non-hsprotect deployments)

Step 1 — Submit

curl -X POST https://api.vexsolver.com/api/solve \
  -H "X-API-Key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://www.zillow.com/",
    "proxy": "http://user:pass@your-proxy:8080"
  }'

# → { "taskId": "abc123xyz" }

Step 2 — Poll

curl -G "https://api.vexsolver.com/api/getTaskResult" \
  -H "X-API-Key: sk_live_..." \
  --data-urlencode "taskId=abc123xyz"

# Retry every 1-2 seconds until status != "pending".

Microsoft / Outlook signup (signup.live.com)

signup.live.com uses a custom hsprotect PX deployment. Pass app_id: "PXzC5j78di" to bypass AppId auto-detection and hit it reliably.

curl -X POST https://api.vexsolver.com/api/solve \
  -H "X-API-Key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://signup.live.com/signup",
    "proxy": "http://user:pass@your-proxy:8080",
    "app_id": "PXzC5j78di"
  }'

Force fresh solve

Pass "fresh": true to bypass our 5-min cache and get a new PerimeterX-signed token on every call.

curl -X POST https://api.vexsolver.com/api/solve \
  -H "X-API-Key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://signup.live.com/signup",
    "proxy": "http://user:pass@your-proxy:8080",
    "app_id": "PXzC5j78di",
    "fresh": true
  }'

Pin your own User-Agent (TLS replay)

PerimeterX cross-checks the cookie's UA against the TLS handshake fingerprint on every request. If your TLS client is fixed at a specific Chrome version (e.g. Chrome 146), pass that exact UA in the user_agent field and VexSolver will mint the cookie with EXACTLY that UA — so your replay TLS + cookie UA stay aligned.

curl -X POST https://api.vexsolver.com/api/solve \
  -H "X-API-Key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://identity.walmart.com/account/login",
    "app_id": "PXu6b0qd2S",
    "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
    "fresh": true
  }'

When omitted, VexSolver picks from a randomized desktop pool (Chrome 131–147 / macOS / Android / iPad). The poll result's user_agent field always tells you which UA was actually used — replay with that exact value.

Poll result shape

{
  "success": true,
  "vendor": "perimeterx",
  "source": "px_solver",
  "target": "https://signup.live.com/signup",
  "attempts": 1,
  "cached": false,
  "elapsed_ms": 596,
  "solve_ms": 596,
  "solved_at": 1776997536,
  "expires_at": 1776998736,
  "ttl_sec": 1200,
  "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
  "proxy_fp": "f2e8631d8304",
  "cookies": "_px3=181114e3...; _pxde=e855ddba...; _pxhd=1160a60f-...",
  "cookie_map": {
    "_px3": "181114e3045c626fbb8076478ac086...",
    "_pxde": "e855ddbab4b77a76cc714f537a478a31...",
    "_pxhd": "1160a60f-3f81-11f1-911b-9812291b09e9"
  }
}
Replay rules for PerimeterX cookies
PerimeterX tokens are bound to both the egress IP and the User-Agent used during the solve. Always:
  • Replay through the same proxy you passed to /api/solve
  • Replay with the same User-Agent — either pin it via the request user_agent field, or read it back from the poll result and replay with that exact value
  • Keep the full cookie set together (_px3 + _pxde + _pxhd)
Mismatched IPs or UAs will cause PerimeterX to flag the session instantly regardless of how valid the token is.

Go end-to-end example

// 1. Submit
body, _ := json.Marshal(map[string]string{
    "url":    "https://signup.live.com/signup",
    "proxy":  "http://user:pass@your-proxy:8080",
    "app_id": "PXzC5j78di",
})
req, _ := http.NewRequest("POST", "https://api.vexsolver.com/api/solve", bytes.NewReader(body))
req.Header.Set("X-API-Key", "sk_live_...")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
var sub struct{ TaskID string `json:"taskId"` }
json.NewDecoder(resp.Body).Decode(&sub)
resp.Body.Close()

// 2. Poll
var out struct {
    Status    string `json:"status"`
    Success   bool   `json:"success"`
    Cookies   string `json:"cookies"`
    UserAgent string `json:"user_agent"`
}
poll := "https://api.vexsolver.com/api/getTaskResult?taskId=" + sub.TaskID
for i := 0; i < 60 && out.Status != ""; i++ {
    time.Sleep(2 * time.Second)
    r, _ := http.NewRequest("GET", poll, nil)
    r.Header.Set("X-API-Key", "sk_live_...")
    rr, _ := http.DefaultClient.Do(r)
    json.NewDecoder(rr.Body).Decode(&out)
    rr.Body.Close()
    if out.Status != "pending" { break }
}

// 3. Replay
req2, _ := http.NewRequest("GET", "https://signup.live.com/signup", nil)
req2.Header.Set("Cookie", out.Cookies)
req2.Header.Set("User-Agent", out.UserAgent)   // MUST match our solve UA
// ...and send it through the SAME proxy you passed above.
Tested on
Walmart (incl. identity.walmart.com), Wayfair, Zillow, signup.live.com, login.live.com, skyscanner (all TLDs), crunchbase, indeed, ticketmaster, stockx, seatgeek, fubo.tv, spothero, belk, westmarine, and dozens more.

Mobile SDK (Android / iOS) — /api/solve-mobile

Native iOS and Android apps that ship with PerimeterX's mobile SDK (com.perimeterx.mobile_sdk) speak a different protocol than the web flow: a single /api/v1/collector/mobile POST mints session identifiers, then X-PX-* headers (not cookies) carry the session on every outgoing request.

VexSolver's /api/solve-mobile endpoint follows the same async submit + poll pattern as /api/solve:

curl -X POST https://api.vexsolver.com/api/solve-mobile \
  -H "X-API-Key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "app_id":      "PXu6b0qd2S",
    "app_package": "com.walmart.android",
    "app_name":    "Walmart",
    "app_version": "26.17",
    "user_agent":  "okhttp/4.12.0",
    "proxy":       "http://USER:PASS@your-proxy:PORT"
  }'

# → { "taskId": "..." }, poll /api/getTaskResult as usual.

Final poll result shape:

{
  "success": true,
  "vendor": "perimeterx-mobile",
  "vid": "9c333cf0-8099-400f-b9bb-e58c7115a401",
  "uuid": "5b8a9197-4b93-4314-b310-e4ce02b40881",
  "user_agent": "okhttp/4.12.0",
  "headers": {
    "X-PX-AUTHORIZATION":      "PerimeterX Android SDK/3.3.0",
    "X-PX-OS":                 "Android",
    "X-PX-OS-VERSION":         "14",
    "X-PX-MOBILE-SDK-VERSION": "3.3.0",
    "X-PX-VID":                "9c333cf0-...",
    "X-PX-UUID":               "5b8a9197-...",
    "X-PX-DEVICE-FP":          "...",
    "X-PX-DEVICE-MODEL":       "Pixel 7"
  },
  "captcha_solved": false,
  "elapsed_ms": 317
}

Attach the headers map to every outgoing request your bot sends to the protected backend. Pass the same user_agent as your TLS client. The same proxy you supplied here must be used on replay.

Typical performance

  • Cache hit: 1-30 ms (cached: true)
  • Replay template: 500 ms - 2 s
  • Cold capture: 3-8 seconds
  • With WASM captcha: 10-12 seconds
  • Mobile (Init only): ~300 ms via /api/solve-mobile
  • Mobile (HoldCaptcha solve): 8-12 seconds