How to Bypass PerimeterX in 2025: 5 Simple Steps

If you’ve ever tried to automate data scraping from a mobile app, chances are you’ve run into PerimeterX (now HUMAN). And if you have, then you know just how tough it can be to get around. Especially in its mobile form, this protection system has become one of the most advanced and frustrating obstacles developers face today.

That was exactly my situation—until I spent several months reverse-engineering how PerimeterX Mobile works. With a lot of testing, iteration, and more dead ends than I can count, I finally figured out a method that bumped my success rate from 20% to consistently over 90%. Apps like StockX, TextNow, and GrubHub? I can now extract data from them automatically without getting flagged.

In this guide, I’ll walk you through everything I’ve learned: how to identify PerimeterX, understand its token structure, generate valid tokens, and implement them in your requests. Plus, I’ll share some practical tips for keeping your sessions alive and handling common roadblocks along the way.

What is PerimeterX Mobile?

PerimeterX, recently rebranded as HUMAN, is a powerful bot mitigation solution designed specifically to protect mobile apps and APIs from automated abuse. Unlike its web counterpart, PerimeterX Mobile is tailored for mobile platforms like iOS and Android.

You’ll usually encounter one of two versions—px2 or px3. Px3 is the newer, more robust version and, not surprisingly, the harder one to crack. The protection relies on a mix of device fingerprinting, behavioral data, and token validation to determine whether a request is legitimate.

The short version? If you’re not sending the right tokens in your API calls, your requests are going to get blocked—fast.

Step 1: Identify PerimeterX Implementation

Before you even think about bypassing PerimeterX, you first need to confirm that it’s actually in play—and figure out which version you’re dealing with. That means digging into your network traffic and identifying key indicators in the headers and URLs.

import requests
import re

def detect_perimeterx(request_headers, response_headers, url):
    # Check request headers for PX tokens
    px_headers = [
        'x-px-authorization',
        'x-px-original-token',
        'x-px-context'
    ]
    
    for header in px_headers:
        if header in request_headers:
            # Check for PX3 format
            if re.match(r'3:[a-f0-9]+:[A-Za-z0-9+/]+={0,2}:\d+:[^:]+', request_headers[header]):
                print("PerimeterX px3 detected in request headers")
                return "px3"
                
            # Check for PX2 format
            if re.match(r'2:[A-Za-z0-9+/]+={0,2}', request_headers[header]):
                print("PerimeterX px2 detected in request headers")
                return "px2"
    
    # Check for collector endpoints in the URL
    px_endpoints = [
        'collector-[a-zA-Z0-9]+\.perimeterx\.net',
        'collector-[a-zA-Z0-9]+\.px-cloud\.net'
    ]
    
    for pattern in px_endpoints:
        if re.search(pattern, url):
            print("PerimeterX collector endpoint detected in URL")
            return "unknown_version"
    
    return None

# Example usage with mitmproxy or other traffic capture
headers = {
    "x-px-authorization": "3:5b0de8f7a1d99c67add:MOXOASOAP5VNA==:1000:6xXzN9Q="
}
url = "https://api.stockx.com/v2/products"

px_version = detect_perimeterx(headers, {}, url)
print(f"PerimeterX version: {px_version}")

This detection script gives you a quick read on whether PerimeterX is active, and if so, whether it’s using px2 or px3. You’ll often find telltale headers like x-px-authorization or URL patterns tied to collector domains. Knowing the version you’re dealing with will help determine your approach later on.

Step 2: Understand Token Structure

Understanding how PerimeterX tokens are structured is key to bypassing the system. Let’s break it down:

PX3 tokens are formatted like this:
3:hash:payload:appId:additional

def parse_px3_token(token):
    parts = token.split(':')
    if len(parts) < 5 or parts[0] != '3':
        return None
    
    return {
        'version': parts[0],
        'hash': parts[1],
        'payload': parts[2],  # Base64 encoded
        'appId': parts[3],
        'additional': parts[4]
    }

# Example
token = "3:5b0de8f7a1d99c67add:MOXOASOAP5VNA==:1000:6xXzN9Q="
parsed = parse_px3_token(token)
print(parsed)

PX2 tokens, on the other hand, are simpler and usually look like this:
2:base64_payload

import base64
import json

def parse_px2_token(token):
    parts = token.split(':')
    if len(parts) < 2 or parts[0] != '2':
        return None
    
    try:
        # Decode base64 payload
        payload = base64.b64decode(parts[1]).decode('utf-8')
        # Parse JSON
        json_data = json.loads(payload)
        return {
            'version': parts[0],
            'decoded_payload': json_data
        }
    except Exception as e:
        print(f"Error decoding token: {e}")
        return None

# Example
token = "2:eyJ1Ijoi7hTyuIdsHHQ2cGxhdGZvcm0iOiJpT1MiLCJ0diI6IjMuMy4wIiwidGFnIjoidjMuMy4wIiwidXVpZCI6ImFiYzEyMzQ1Njc4OSIsImNzcyI6e30sInNjIjpbXSwiY3VzdG9tIjp7fSwiY3RzIjoxNjE5NjM0OTA2Njk3LCJ2aWQiOiJhMTIzNDU2NyIsInNpZCI6ImJkZWYyIn0="
parsed = parse_px2_token(token)
print(parsed)

Grasping the components of these tokens—especially the base64 payloads—is critical. It’ll allow you to decode them, troubleshoot when something goes wrong, and ultimately build your own.

Step 3: Generate Valid PX Tokens

Now comes the meat of the process: generating valid tokens that fool PerimeterX into thinking your requests are legitimate.

Method 1: Use an API-Based Generator

The most reliable route, especially in 2025, is to use a dedicated token-generation API like TakionAPI. These services simulate real devices and environments, producing valid tokens for use in apps like StockX.

import requests

def generate_px_token(app_id, device_info, px_version="px3"):
    api_url = "https://px.takionapi.tech/generate"
    
    payload = {
        "appId": app_id,
        "version": px_version,
        "device": {
            "os": device_info.get("os", "android"),
            "osVersion": device_info.get("osVersion", "13"),
            "deviceId": device_info.get("deviceId", ""),
            "userAgent": device_info.get("userAgent", "")
        }
    }
    
    headers = {
        "x-api-key": "YOUR_API_KEY",
        "Content-Type": "application/json"
    }
    
    response = requests.post(api_url, json=payload, headers=headers)
    
    if response.status_code == 200:
        return response.json()
    else:
        print(f"Error generating token: {response.status_code}")
        return None

# Example usage for StockX
device_info = {
    "os": "android",
    "osVersion": "13",
    "deviceId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "userAgent": "StockX/24.02.02 (Android 13; SM-G998B)"
}

token_data = generate_px_token("PX1000", device_info)
print(token_data)

Method 2: Build a Basic Token Generator

If you’re working on a smaller project or just experimenting, you can also roll your own simple PX3 token generator. Keep in mind, this approach won’t get you past heavily protected apps—but it’s a great way to learn how the system works under the hood.

import time
import json
import random
import hashlib
import base64

def generate_basic_px3_token(app_id, device_id):
    # This is a simplified version - a real implementation would be more complex
    
    # Create a basic payload
    timestamp = int(time.time() * 1000)
    payload_data = {
        "t": timestamp,
        "u": device_id,
        "v": "3.3.0",
        "os": "android",
        "osv": "13",
        "ts": {
            "tst": timestamp - random.randint(100, 500),
            "mst": random.randint(10, 50)
        }
    }
    
    # Convert to JSON and encode to base64
    payload_json = json.dumps(payload_data)
    payload_b64 = base64.b64encode(payload_json.encode()).decode()
    
    # Generate a fake hash - in a real scenario, this would use PerimeterX's algorithm
    hash_value = hashlib.md5((payload_b64 + app_id).encode()).hexdigest()[:16]
    
    # Additional data - normally contains signature information
    additional = base64.b64encode(str(random.random()).encode()).decode()[:8] + "="
    
    # Construct the token
    token = f"3:{hash_value}:{payload_b64}:{app_id}:{additional}"
    
    return token

# Example usage
app_id = "1000"
device_id = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
token = generate_basic_px3_token(app_id, device_id)
print(token)

The takeaway here is this: while custom solutions might work for less secure implementations, most real-world scenarios will require using or mimicking a legitimate mobile environment.

Step 4: Implement Tokens in Your Requests

Once you’ve got a valid token, you need to make sure it’s integrated properly in your requests. That typically means adding it to your headers—often under x-px-authorization, though it can vary by app.

def make_px_request(url, px_token, cookies=None):
    headers = {
        "User-Agent": "Mozilla/5.0 (Linux; Android 13; SM-G998B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Mobile Safari/537.36",
        "Accept-Language": "en-US,en;q=0.9",
        "x-px-authorization": px_token
    }
    
    session = requests.Session()
    if cookies:
        for key, value in cookies.items():
            session.cookies.set(key, value)
    
    response = session.get(url, headers=headers)
    
    if response.status_code == 200:
        print("Request successful!")
        return response
    else:
        print(f"Request failed with status code: {response.status_code}")
        print(response.text[:200])  # Print first 200 chars for debugging
        return None

# Example usage
url = "https://api.stockx.com/v2/products/search?query=jordan"
response = make_px_request(url, token)

if response:
    data = response.json()
    print(json.dumps(data, indent=2)[:500])  # Print first 500 chars

Keep your headers consistent with what the app expects. That includes the correct User-Agent string, language preferences, and of course, the token itself. If your request gets blocked, check your headers first—tiny mismatches can trigger PerimeterX defenses.

Step 5: Maintain Session Validity

Here’s something a lot of developers overlook: PX tokens aren’t meant to last. Many expire within 60 seconds, so unless you’re updating them regularly, you’ll find yourself blocked again before long.

To keep things running smoothly, I built a session class that handles token refresh and retry logic automatically.

class PerimeterXSession:
    def __init__(self, app_id, device_info):
        self.app_id = app_id
        self.device_info = device_info
        self.session = requests.Session()
        self.px_token = None
        self.token_generation_time = 0
        self.token_expiry = 60  # Tokens typically expire after 60 seconds
        
        # Set up session headers
        self.session.headers.update({
            "User-Agent": device_info.get("userAgent", "Mozilla/5.0 (Linux; Android 13; SM-G998B)"),
            "Accept-Language": "en-US,en;q=0.9"
        })
        
        # Generate initial token
        self._refresh_token()
    
    def _refresh_token(self):
        token_data = generate_px_token(self.app_id, self.device_info)
        if token_data and "token" in token_data:
            self.px_token = token_data["token"]
            self.token_generation_time = time.time()
            return True
        return False
    
    def _is_token_expired(self):
        return (time.time() - self.token_generation_time) > self.token_expiry
    
    def request(self, method, url, **kwargs):
        # Check if token needs refreshing
        if self._is_token_expired():
            self._refresh_token()
        
        # Add token to request headers
        headers = kwargs.pop("headers", {})
        headers["x-px-authorization"] = self.px_token
        
        # Make the request
        response = self.session.request(method, url, headers=headers, **kwargs)
        
        # If we get a 403, try refreshing the token and retry
        if response.status_code == 403:
            if self._refresh_token():
                headers["x-px-authorization"] = self.px_token
                response = self.session.request(method, url, headers=headers, **kwargs)
        
        return response
    
    def get(self, url, **kwargs):
        return self.request("GET", url, **kwargs)
    
    def post(self, url, **kwargs):
        return self.request("POST", url, **kwargs)

# Example usage
device_info = {
    "os": "android",
    "osVersion": "13",
    "deviceId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "userAgent": "StockX/24.02.02 (Android 13; SM-G998B)"
}

px_session = PerimeterXSession("PX1000", device_info)
response = px_session.get("https://api.stockx.com/v2/products/search?query=jordan")

print(f"Status code: {response.status_code}")
if response.status_code == 200:
    data = response.json()
    print(f"Found {len(data.get('products', []))} products")

This setup checks whether your token has expired, refreshes it when needed, and retries failed requests after getting a fresh one. It’s a massive time-saver and helps prevent unnecessary blocks.

Common Challenges and Solutions

Even with the right token, PerimeterX can still block you if other parts of your setup don’t look right. Here’s how to deal with the most common pitfalls:

1. Device Fingerprinting

PerimeterX looks closely at your device's fingerprint. To keep your session believable, you need consistent and realistic device info.

import uuid

def generate_consistent_device_info(app_name, seed=None):
    # Generate deterministic device ID based on seed
    if seed:
        random.seed(seed)
    
    # Popular Android devices in 2025
    devices = [
        {"model": "SM-S9300", "manufacturer": "Samsung", "os_version": "14"},
        {"model": "Pixel 9", "manufacturer": "Google", "os_version": "15"},
        {"model": "M3 Note", "manufacturer": "Xiaomi", "os_version": "14.5"}
    ]
    
    selected_device = random.choice(devices)
    
    # Generate a stable device ID based on seed
    device_id = str(uuid.uuid5(uuid.NAMESPACE_DNS, f"{app_name}:{seed}")) if seed else str(uuid.uuid4())
    
    user_agents = {
        "StockX": f"StockX/24.05.01 (Android {selected_device['os_version']}; {selected_device['model']})",
        "TextNow": f"TextNow/24.5.1 (Android {selected_device['os_version']}; {selected_device['model']})",
        "GrubHub": f"GrubHub/2025.5.1 (Android {selected_device['os_version']}; {selected_device['model']})"
    }
    
    return {
        "os": "android",
        "osVersion": selected_device["os_version"],
        "deviceId": device_id,
        "model": selected_device["model"],
        "manufacturer": selected_device["manufacturer"],
        "userAgent": user_agents.get(app_name, f"{app_name}/1.0 (Android {selected_device['os_version']}; {selected_device['model']})")
    }

# Usage example - providing a seed ensures the same device info each time
device_info = generate_consistent_device_info("StockX", seed="your-stable-seed")
print(device_info)

Use a consistent seed when generating device info so your fingerprints remain stable across requests. Don’t skip this step—it plays a big role in passing fingerprint checks.

2. IP Rotation

Sticking with one IP is a sure way to get flagged. Rotate proxies often, and make sure they’re residential or mobile if possible.

def create_proxy_session(proxy_list):
    # Create a session with proxy rotation
    session = requests.Session()
    
    # Select a random proxy
    proxy = random.choice(proxy_list)
    
    session.proxies = {
        "http": proxy,
        "https": proxy
    }
    
    return session

# Example
proxies = [
    "http://user:pass@proxy1.example.com:8080",
    "http://user:pass@proxy2.example.com:8080"
]

session = create_proxy_session(proxies)

High-quality proxies are essential for staying under the radar. Avoid datacenter IPs—they tend to get blacklisted quickly.

3. App Version Changes

Apps update frequently, and so do their anti-bot defenses. Keeping tabs on version changes can help you stay ahead of new challenges.

from bs4 import BeautifulSoup
import requests

def check_app_version(package_name):
    url = f"https://play.google.com/store/apps/details?id={package_name}"
    
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
    }
    
    response = requests.get(url, headers=headers)
    
    if response.status_code == 200:
        soup = BeautifulSoup(response.text, 'html.parser')
        # Look for version information in the Play Store page
        version_element = soup.select_one('div[itemprop="softwareVersion"]')
        
        if version_element:
            return version_element.text.strip()
        else:
            # Alternative method if the first one fails
            version_pattern = r'Current Version</div><span class="htlgb">([^<]+)<'
            match = re.search(version_pattern, response.text)
            if match:
                return match.group(1)
    
    return None

# Example usage
package_name = "com.stockx.stockx"
latest_version = check_app_version(package_name)
print(f"Latest version: {latest_version}")

If your token suddenly stops working, the app may have changed how it implements PerimeterX. Track app versions so you’re never caught off guard.

Final Thoughts

Getting around PerimeterX Mobile in 2025 isn’t easy—but it’s far from impossible. With the right tools, a structured approach, and a little patience, you can successfully automate even the most locked-down mobile apps.

Keep in mind that these techniques require upkeep. Monitor your success rates, test new devices and proxies, and tweak your tokens as needed. And always be mindful of the legal and ethical boundaries—you’re responsible for how you use this information.

This guide is designed to help developers understand and navigate PerimeterX protection in a responsible way. Use it to level up your automation workflows—and if you’ve hit other snags or found workarounds not mentioned here, feel free to share them.

Marius Bernard

Marius Bernard

Marius Bernard is a Product Advisor, Technical SEO, & Brand Ambassador at Roundproxies. He was the lead author for the SEO chapter of the 2024 Web and a reviewer for the 2023 SEO chapter.