> For the complete documentation index, see [llms.txt](https://developers.oxylabs.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developers.oxylabs.io/products/agent-browser/page-handler.md).

# Page Handler

Let Agent Browser identify each page, handle access challenges automatically, and tell your script when the page is ready.

{% hint style="warning" %}
**Account access required:** accounts without the feature enabled, receive `400 page handler is not enabled for this account on the handshake` (see [Response Codes](/products/agent-browser/response-codes.md)).
{% endhint %}

Oxylabs Agent Browser uses a built-in page handler for website access challenges. The `page_handler` parameter identifies every page it loads, completes the supported challenges it finds, and reports the page state to your script as a window message.

```bash
wss://USER:PASS@hb.oxylabs.io?p_cc=US&page_handler=true
```

**Page handler is disabled by default** and must be passed for every connection and kept identical for all connections to the same target, including every run that shares a [persistent profile](/products/agent-browser/persistent-sessions-and-profiles.md#persistent-profiles) (profile `o_profile` or a sticky proxy session `proxy_resi_ses_id`).

<table><thead><tr><th width="133">Parameter</th><th width="405">Description</th><th width="93.5">Type</th></tr></thead><tbody><tr><td><code>page_handler</code></td><td>Enables page identification and automatic challenge handling. Default: <code>false</code>.</td><td>boolean</td></tr></tbody></table>

{% hint style="info" %}
Page Handler adds time to page loads, so enable it only for targets that show challenges. If the feature is not enabled for your account, contact [Oxylabs support.](mailto:support@oxylabs.io)
{% endhint %}

## Page status

With the page handler enabled, the browser reports the status of every page as a `window` message:

```javascript
{ source: "oxylabs-runtime", type: "oxylabs-page-status", status: "open" }
```

<table><thead><tr><th width="130.5">Status</th><th>Description</th></tr></thead><tbody><tr><td><code>identifying</code></td><td>The page is being checked.</td></tr><tr><td><code>closed</code></td><td>A challenge was found and is being handled. The page is not usable yet.</td></tr><tr><td><code>open</code></td><td>The page is ready to use.</td></tr><tr><td><code>failed</code></td><td>The challenge could not be completed. The message includes an <code>error</code> field.</td></tr></tbody></table>

`identifying` and `closed` are temporary, so wait for `open` or `failed`. A page without a challenge reports `open` about 8 seconds after loading, and handling a challenge typically takes 30–45 seconds. If the status is `failed`, close the session and retry with a new connection.

## Tracking page status

Browser console output is not delivered to CDP clients, so listen for the window message with a script that is added before the page loads, then wait for the final status:

{% tabs %}
{% tab title="Python (Playwright)" %}

```python
from playwright.sync_api import sync_playwright

username = "USERNAME"
password = "PASSWORD"
endpoint = "hb.oxylabs.io"
browser_url = f"wss://{username}:{password}@{endpoint}?p_cc=US&page_handler=true"

# Keep the latest page status in the page
status_listener = """
window.addEventListener("message", (e) => {
  const d = e.data;
  if (d?.source === "oxylabs-runtime" && d.type === "oxylabs-page-status") {
    window.__pageStatus = d.status;
  }
});
"""

with sync_playwright() as p:
    browser = p.chromium.connect_over_cdp(browser_url)
    context = browser.contexts[0] if browser.contexts else browser.new_context()
    context.add_init_script(status_listener)  # add before opening the page
    page = context.new_page()
    page.goto("https://example.com")

    # Wait until the page is ready ("open") or the handler gives up ("failed")
    page.wait_for_function("['open', 'failed'].includes(window.__pageStatus)", timeout=90000)
    print(page.evaluate("window.__pageStatus"), page.title())
    browser.close()
```

{% endtab %}

{% tab title="JavaScript (Playwright)" %}

```javascript
import { chromium } from "playwright";

const username = "USERNAME";
const password = "PASSWORD";
const endpoint = "hb.oxylabs.io";
const browserUrl = `wss://${username}:${password}@${endpoint}?p_cc=US&page_handler=true`;

// Keep the latest page status in the page
const statusListener = () => {
    window.addEventListener("message", (e) => {
        const d = e.data;
        if (d?.source === "oxylabs-runtime" && d.type === "oxylabs-page-status") {
            window.__pageStatus = d.status;
        }
    });
};

(async () => {
    const browser = await chromium.connectOverCDP(browserUrl);
    const context = browser.contexts()[0] || (await browser.newContext());
    await context.addInitScript(statusListener); // add before opening the page
    const page = await context.newPage();
    await page.goto("https://example.com");

    // Wait until the page is ready ("open") or the handler gives up ("failed")
    await page.waitForFunction(
        () => ["open", "failed"].includes(window.__pageStatus),
        null,
        { timeout: 90000 }
    );
    console.log(await page.evaluate(() => window.__pageStatus), await page.title());
    await browser.close();
})();
```

{% endtab %}

{% tab title="JavaScript (Puppeteer)" %}

```javascript
import puppeteer from "puppeteer";

const username = "USERNAME";
const password = "PASSWORD";
const endpoint = "hb.oxylabs.io";
const browserUrl = `wss://${username}:${password}@${endpoint}?p_cc=US&page_handler=true`;

// Keep the latest page status in the page
const statusListener = () => {
    window.addEventListener("message", (e) => {
        const d = e.data;
        if (d?.source === "oxylabs-runtime" && d.type === "oxylabs-page-status") {
            window.__pageStatus = d.status;
        }
    });
};

(async () => {
    const browser = await puppeteer.connect({ browserWSEndpoint: browserUrl });
    const page = await browser.newPage();
    await page.evaluateOnNewDocument(statusListener); // add before opening the page
    await page.goto("https://example.com");

    // Wait until the page is ready ("open") or the handler gives up ("failed")
    await page.waitForFunction(
        () => ["open", "failed"].includes(window.__pageStatus),
        { timeout: 90000 }
    );
    console.log(await page.evaluate(() => window.__pageStatus), await page.title());
    await browser.close();
})();
```

{% endtab %}
{% endtabs %}

### Handling challenges after page load

The page handler deals with challenges that are present when a page loads. A challenge that appears later, for example after a click or a form submit, is not handled automatically. To handle it, send an `oxylabs-page-handle` message to the page. The browser checks the page again, handles any challenge it finds, and reports the status as before.

### MCP Server and Agent Skills

If you're using an AI agent or a coding assistant, we highly recommend taking advantage our easy integration methods for Oxylabs Agent Browser:

<table data-header-hidden><thead><tr><th width="152"></th><th></th></tr></thead><tbody><tr><td><a href="/ai-workflows/agent-skills.md"><strong>Agent Skills</strong></a></td><td>Set up Agent Browser in Claude Code, Cursor and other AI coding assistants.</td></tr><tr><td><a href="/ai-workflows/mcp.md"><strong>MCP Server</strong></a></td><td>Give MCP-compatible AI assistants a remote Agent Browser.</td></tr></tbody></table>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://developers.oxylabs.io/products/agent-browser/page-handler.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
