# How to Intercept and Modify Network Requests and Responses in Zendriver

> Learn to intercept and modify network requests and responses using Zendriver's Tab.intercept() context manager. Pause, inspect, and alter HTTP traffic effortlessly.

- Repository: [CDP Driver/zendriver](https://github.com/cdpdriver/zendriver)
- Tags: how-to-guide
- Published: 2026-02-27

---

**Zendriver provides an async context manager `Tab.intercept()` that wraps the Chrome DevTools Protocol Fetch domain, allowing you to pause, inspect, and modify HTTP traffic without handling raw CDP commands.**

Intercepting and modifying network requests and responses is essential for mocking APIs, blocking trackers, or debugging frontend applications. Zendriver, a Python async driver for Chrome DevTools Protocol, exposes this capability through a high-level API built on top of the CDP Fetch and Network domains. This guide explains how to use `Tab.intercept()` to capture and manipulate network traffic using the actual implementation from the `cdpdriver/zendriver` repository.

## Understanding the Interception Architecture

Zendriver's interception system operates through three distinct layers that abstract the complexity of Chrome DevTools Protocol.

### User-Level API in Tab

The entry point for all interception functionality resides in [`zendriver/core/tab.py`](https://github.com/cdpdriver/zendriver/blob/main/zendriver/core/tab.py). The `Tab.intercept()` method creates an async context manager that returns a `BaseFetchInterception` object:

```python

# zendriver/core/tab.py (lines 1216-1230)

async def intercept(self, url_pattern: str, request_stage: RequestStage, 
                   resource_type: Optional[ResourceType] = None):
    """Create an interception context for the given URL pattern."""
    interception = BaseFetchInterception(self, url_pattern, request_stage, resource_type)
    await interception._setup()
    return interception

```

### Interception Helper Implementation

The `BaseFetchInterception` class in [`zendriver/core/intercept.py`](https://github.com/cdpdriver/zendriver/blob/main/zendriver/core/intercept.py) manages the CDP session lifecycle. It registers the `Fetch.enable` command with a specific `RequestPattern`, listens for `Fetch.RequestPaused` events, and provides high-level helper methods like `continue_request()`, `fulfill_request()`, and `fail_request()`.

### CDP Bindings Layer

Low-level CDP commands are defined in [`zendriver/cdp/fetch.py`](https://github.com/cdpdriver/zendriver/blob/main/zendriver/cdp/fetch.py) and [`zendriver/cdp/network.py`](https://github.com/cdpdriver/zendriver/blob/main/zendriver/cdp/network.py). These generated modules provide Python dataclasses for `RequestPattern`, `RequestStage`, `ResourceType`, `HeaderEntry`, and command methods like `Fetch.continueRequest` and `Fetch.fulfillRequest`.

## Setting Up Request Interception

To begin intercepting traffic, import the required CDP enums and create an interception context using the `async with` statement. The context manager automatically handles `Fetch.enable` on entry and `Fetch.disable` on exit.

```python
import zendriver as zd
from zendriver.cdp.fetch import RequestStage
from zendriver.cdp.network import ResourceType

async def intercept_api_calls(tab: zd.Tab):
    # Intercept XHR responses matching the pattern

    async with tab.intercept(
        url_pattern="*/api/*", 
        request_stage=RequestStage.RESPONSE,
        resource_type=ResourceType.XHR
    ) as interception:
        await tab.get("https://example.com/dashboard")
        # Interception logic here

```

The `url_pattern` supports wildcards (`*`). The `request_stage` parameter accepts `RequestStage.REQUEST` (pause before sending to server) or `RequestStage.RESPONSE` (pause after server reply).

## Inspecting and Modifying Requests

Once the `Fetch.RequestPaused` event fires, the interception object captures the event and exposes methods to inspect or alter the network exchange.

### Reading Request Data

Access the original request details through the `request` property:

```python
request = await interception.request
print(f"Method: {request.method}")
print(f"URL: {request.url}")
print(f"Headers: {request.headers}")

```

### Modifying Request Parameters

To alter the request before it reaches the server, use `continue_request()` with override parameters:

```python
await interception.continue_request(
    url="https://api.example.com/v2/data",  # Redirect to different endpoint

    method="POST",                          # Change method

    headers=[{"name": "Authorization", "value": "Bearer token123"}],
    post_data='{"modified": true}'
)

```

If you do not wish to modify the request, call `continue_request()` without arguments to proceed normally.

## Handling Responses

Intercepting responses allows you to read body content, substitute mock data, or block unwanted resources entirely.

### Reading Response Bodies

When intercepting at the `RESPONSE` stage, fetch the body using the `response_body` property. This internally calls `Fetch.getResponseBody` and handles base64 decoding:

```python
body, is_base64 = await interception.response_body
if is_base64:
    import base64
    body = base64.b64decode(body).decode('utf-8')
print("Response content:", body)

```

### Fulfilling with Custom Data

To provide a synthetic response without hitting the server, use `fulfill_request()`. This is useful for mocking APIs during testing:

```python
import json
from zendriver.cdp.fetch import HeaderEntry

mock_data = json.dumps({"user": "test", "id": 123})
headers = [
    HeaderEntry(name="Content-Type", value="application/json"),
    HeaderEntry(name="Content-Length", value=str(len(mock_data)))
]

await interception.fulfill_request(
    response_code=200,
    response_headers=headers,
    body=mock_data
)

```

### Blocking Requests

To abort a request entirely, intercept at the `REQUEST` stage and call `fail_request()`:

```python
from zendriver.cdp.network import ErrorReason

async with tab.intercept("*tracker.js", RequestStage.REQUEST, ResourceType.SCRIPT) as inter:
    await tab.get("https://example.com")
    # Immediately abort the request

    await inter.fail_request(ErrorReason.BLOCKED_BY_CLIENT)

```

## Advanced Patterns

### Reusing Interceptions Across Page Reloads

By default, an interception context captures a single `RequestPaused` event. To intercept multiple requests or survive page reloads, use the `reset()` method:

```python
async with tab.intercept("*api/data", RequestStage.RESPONSE) as inter:
    await tab.get("https://example.com/page1")
    body1, _ = await inter.response_body
    await inter.continue_request()
    
    # Reset to capture the next occurrence

    await inter.reset()
    
    await tab.reload()
    body2, _ = await inter.response_body
    await inter.continue_request()

```

The `reset()` method clears the internal future, disables the fetch pattern, and re-enables it, allowing the same context to handle subsequent requests.

## Summary

- **Zendriver** exposes network interception through `Tab.intercept()`, an async context manager that wraps CDP Fetch domain commands.
- The implementation resides in [`zendriver/core/intercept.py`](https://github.com/cdpdriver/zendriver/blob/main/zendriver/core/intercept.py) (`BaseFetchInterception`) and integrates with [`zendriver/cdp/fetch.py`](https://github.com/cdpdriver/zendriver/blob/main/zendriver/cdp/fetch.py) for low-level protocol bindings.
- Use `RequestStage.REQUEST` to modify or block requests before they reach the server, and `RequestStage.RESPONSE` to inspect or substitute response bodies.
- Key methods include `continue_request()` (modify and forward), `fulfill_request()` (mock responses), `fail_request()` (block requests), and `response_body` (read captured content).
- The `reset()` method allows reusing the same interception context across multiple page loads or requests.

## Frequently Asked Questions

### How do I intercept only specific resource types like images or scripts?

Pass the `resource_type` parameter to `tab.intercept()` using values from `zendriver.cdp.network.ResourceType`. For example, use `ResourceType.IMAGE` to intercept only image requests or `ResourceType.SCRIPT` for JavaScript files. This filtering happens at the CDP level, so only matching requests trigger the `RequestPaused` event.

### Can I modify both the request and response in a single interception?

No, you must choose the interception stage when setting up the context. Use `RequestStage.REQUEST` to modify headers, method, URL, or POST data before the request reaches the server. Use `RequestStage.RESPONSE` to read or substitute the response body after the server replies. If you need to handle both stages for the same URL, create two separate interception contexts.

### What happens if I don't call continue_request or fulfill_request?

If you intercept a request and do not explicitly call `continue_request()`, `fulfill_request()`, or `fail_request()`, the browser tab will hang indefinitely waiting for the Fetch domain to resolve the paused request. Always ensure your interception logic reaches one of these resolution methods, ideally using `try/finally` blocks or context managers to guarantee cleanup.

### How do I inspect response headers, not just the body?

The `Fetch.RequestPaused` event includes a `response_headers` field when intercepting at the `RESPONSE` stage. Access these through the interception object's internal event data. However, if you need to modify response headers specifically, use `continue_response()` (available when intercepting responses) to override headers while keeping the original body, or use `fulfill_request()` to provide completely custom headers and body content.