Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Use Chrome DevTools Protocol (CDP) Network events to observe AJAX traffic inside headless Chromium. Enable the Network domain before navigation or the interaction that triggers the call, record requestWillBeSent and responseReceived events, filter resource types to XHR and Fetch, then call Network.getResponseBody after loadingFinished. This gives you request metadata and response bodies without pausing page execution.
What you are capturing
In Chrome terminology, AJAX-style calls normally appear as resource type XHR or Fetch. CDP’s Network domain tracks the browser’s network activity: each request has a request ID, lifecycle events describe its progress, and the same ID lets you retrieve the response body. The result is a record of what Chromium observed during one run—not a guaranteed inventory of every backend endpoint or proof that the request can be replayed outside the browser.
Attach listeners before the page loads or before the click, form submission, timer, or JavaScript action of interest. If you attach after the activity, the earlier requests are already gone. Redirects can create related request records, and cache or service-worker behavior can change what reaches the network.
Free tools Windows power users keep installed
One-click scans. No signup required.
Passive logging versus interception
Use Network for ordinary capture
The CDP Network domain is the right choice when you want to watch traffic, save metadata, and read completed responses. It does not pause the page. Enable it once, collect events, and handle success and failure paths.
#1 Best Overall
Use Fetch only when you must intervene
The CDP Fetch domain is for interception: matching requests are paused at a request or response stage. Your client must continue, fail, or fulfill every paused request. Forgetting to resolve one can make the page appear frozen. Do not turn on interception merely to log traffic.
Prerequisites and a minimal capture design
- A Chromium or Chrome installation that your automation library can launch in headless mode.
- A current automation wrapper with CDP-session support. CDP tip-of-tree documentation changes and does not promise backwards compatibility, so pin and verify the wrapper version you deploy.
- An authorized target and a policy for redacting credentials, tokens, cookies, and personal data before writing logs.
The implementation has six parts:
- Launch Chromium and create a page.
- Open a CDP session for that page.
- Send
Network.enablebefore navigation. - Store request metadata by request ID and keep only
XHRandFetch. - On
loadingFinished, request the body withNetwork.getResponseBody; onloadingFailed, preserve the failure. - Serialize the selected records after the action is complete, then redact sensitive values.
Node.js example with Puppeteer and CDP
The following example uses Puppeteer’s CDP session. Check the current Puppeteer API for your installed version before production use; the protocol commands and event names are the important parts.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
const client = await page.target().createCDPSession();
const calls = new Map();
await client.send('Network.enable');
client.on('Network.requestWillBeSent', event => {
const type = event.type;
if (type !== 'XHR' && type !== 'Fetch') return;
calls.set(event.requestId, {
requestId: event.requestId,
url: event.request.url,
method: event.request.method,
requestHeaders: event.request.headers,
resourceType: type,
initiator: event.initiator,
timestamp: event.timestamp,
redirectResponse: event.redirectResponse || null
});
});
client.on('Network.responseReceived', event => {
const record = calls.get(event.requestId);
if (!record) return;
record.status = event.response.status;
record.statusText = event.response.statusText;
record.mimeType = event.response.mimeType;
record.responseHeaders = event.response.headers;
record.fromDiskCache = event.response.fromDiskCache;
record.fromServiceWorker = event.response.fromServiceWorker;
});
client.on('Network.loadingFinished', async event => {
const record = calls.get(event.requestId);
if (!record) return;
record.encodedDataLength = event.encodedDataLength;
try {
const body = await client.send('Network.getResponseBody', {
requestId: event.requestId
});
record.body = body.body;
record.base64Encoded = body.base64Encoded;
} catch (error) {
record.bodyError = error.message;
}
});
client.on('Network.loadingFailed', event => {
const record = calls.get(event.requestId);
if (!record) return;
record.failed = true;
record.errorText = event.errorText;
record.canceled = event.canceled || false;
});
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
// Replace this with the action that triggers your AJAX call.
// await page.click('#load-data');
await page.waitForTimeout(3000);
console.log(JSON.stringify([...calls.values()], null, 2));
await browser.close();
})();
Replace the URL and trigger with your authorized test case. For a deterministic capture, wait for a selector, a known response, or an application-specific completion signal instead of relying only on a fixed delay. The listener is installed before page.goto, so navigation-time requests are eligible for capture.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Associating requests, responses, redirects and failures
Request IDs are the join key
Use the request ID as the map key. Add request URL, method, headers, initiator and timing fields from requestWillBeSent. Add status, MIME type, response headers and cache indicators from responseReceived.
Read bodies after completion
Call Network.getResponseBody after the corresponding response has completed, normally from loadingFinished. The returned body may be plain text or marked base64 encoded. A body can be unavailable for a failed, canceled, or otherwise unsupported request; record that condition instead of treating it as an empty response.
Do not flatten redirects
A navigation or API call that redirects can produce multiple records. Preserve the redirect response and each request ID so you can reconstruct the chain. Do not assume one logical URL equals one network event.
Record failures separately
loadingFailed supplies an error and may indicate cancellation. Keep failed records in your output; dropping them makes intermittent problems look like missing traffic.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Filtering and interpreting the output
Filtering by XHR and Fetch removes images, stylesheets and most document noise, but it also narrows the evidence. WebSockets expose messages through a different event family, and EventSource and other streaming responses have different semantics. If your application uses those transports, capture their dedicated events rather than expecting one completed response body.
Inspect initiators to learn which script or action caused a call. Keep status and timing data, and note whether a response came from disk cache or a service worker. Cached requests may not expose the original request headers. Security controls can also produce provisional or incomplete header information, so do not present partial metadata as the complete wire exchange.
When a HAR file is enough—and when it is not
A HAR-style log is useful for request metadata and timing, but it is not automatically the response content. Chrome’s network extension interfaces expose a separate body-content operation. If your goal is to analyze JSON returned by an endpoint, explicitly retrieve and store the body with CDP rather than assuming a HAR contains it.
Rank #3
Reliable capture workflow
Capture the complete trigger
- Open the CDP session and enable Network before navigation.
- If the call occurs after a click, install listeners before the click.
- Wait for the application state that proves the operation finished.
- Save both successful and failed records.
Control noise and storage
Keep only the fields needed for your test. Bodies can be large or binary; enforce size limits, store base64 data deliberately, and redact authorization headers, cookies, API keys, email addresses and other personal data. Never publish an unredacted capture.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Clear out junk files and repair common Windows errors3Scan for outdated or missing drivers - takes under a minuteMake runs comparable
Record browser version, viewport, user agent, locale, timezone, network conditions and whether cache or service workers were enabled. Differences in those settings can change which calls occur and whether a response is served locally.
Troubleshooting common failures
No AJAX requests appear
Most often the listener was attached too late, the action was never triggered, or the request is a WebSocket, EventSource, document, or another resource type. Enable Network first, reload, perform the action again, and inspect all resource types temporarily to confirm the transport.
getResponseBody fails
Request the body only after loadingFinished. Check that the request ID is the same one received in the lifecycle event, and retain a failure record when loadingFailed occurred. Some responses are unavailable because they were canceled, streamed, served through a path the protocol cannot expose, or discarded after the target changed.
The page hangs after adding interception
A Fetch interception handler paused a request without continuing, failing, or fulfilling it. Remove interception for passive logging, or resolve every paused event on every branch, including errors.
Recommended Free Tools
Headers look incomplete
Cache and service-worker responses can omit original request headers, while security restrictions can show provisional headers. Treat those fields as observations with context, not as a complete packet trace.
Duplicate or missing records appear
Redirects, retries, preflight requests, service workers and caching can all alter the event sequence. Preserve request IDs, redirect links, initiators and cache flags rather than deduplicating solely by URL.
JSON is unreadable
Check the base64Encoded flag before decoding. Also inspect the MIME type, compression handling supplied by the wrapper, and whether the response is actually a stream or binary payload.
Performance, reliability and security considerations
Network event logging adds bookkeeping and body retrieval, not a fixed throughput guarantee. Retrieving every body can increase memory and output size; filter early, cap retained data, and write records incrementally for long sessions. A timeout should end the capture cleanly and close the browser so a stuck page does not leak a process.
CDP is tied to the browser and wrapper versions. Pin versions in CI, test protocol commands after upgrades, and treat tip-of-tree changes as compatibility work. Captured headers and bodies may contain credentials or personal data: restrict file permissions, redact before sharing, and obtain authorization for every target.
Best Value
Or skip the browser setup
If your goal is a clean visual capture rather than AJAX diagnostics, ScreenshotNeo provides a one-call website screenshot API and MCP server. It is not a replacement for CDP network logs, but it avoids maintaining a browser harness for screenshots.
cURL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
See the ScreenshotNeo documentation for parameters. Before the shot, it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups and chat widgets. Bot checks, blank pages, timeouts, failed loads and cache hits are not billed as clean shots, and response headers identify the page verdict and billing status. Its MCP server includes take_screenshot, get_page_info and capture_pdf for Claude, Cursor and other MCP clients. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots.
Create a free ScreenshotNeo account to try the 1,000 monthly screenshots without a card.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallFrequently Asked Questions
Can CDP capture requests made before my script attaches?
No. Enable Network and attach listeners before navigation or before the triggering interaction; reload when necessary.
Does a captured response prove I can replay the API call?
No. The record reflects one browser run. Cookies, authorization, CSRF state, redirects, service workers and server-side conditions may be required for replay.
Should I use a proxy instead of CDP?
A proxy observes traffic outside the page, while CDP associates events with browser targets and initiators. Choose based on whether you need browser context, cross-process coverage, or transport-level inspection.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

