How to Get a Request’s Response in Puppeteer
Use request.response() for an existing Puppeteer request, or wait for a future response with page.waitForResponse(). Learn how to match, inspect, and troubleshoot responses.
To get the response for a Puppeteer HTTPRequest you already have, call request.response(). It returns the matching HTTPResponse if one has arrived, or null while the response is still pending. If you need the response that a click or navigation will trigger, register page.waitForResponse() before performing that action, then await the response.
const responsePromise = page.waitForResponse(response =>
response.url().includes('/api/data') &&
response.request().method() === 'GET'
);
await page.click('button#load-data');
const response = await responsePromise;
console.log(response.status());
console.log(await response.json());
The endpoint and selector above are examples: replace them with the URL and action used by your page. Puppeteer documents HTTPRequest.response() as nullable and Page.waitForResponse() as returning a promise for the matched response. See the HTTPRequest response reference and Page.waitForResponse reference.
1. Choose the right Puppeteer API
| Your situation | Use | You receive |
|---|---|---|
You already have an HTTPRequest |
request.response() |
HTTPResponse or null |
| An action will cause the response and you need to wait | page.waitForResponse(urlOrPredicate) |
A promise resolving to HTTPResponse |
| You need to wait for an outgoing request | page.waitForRequest(urlOrPredicate) |
A promise resolving to HTTPRequest; its response may not have arrived yet |
| You want to observe page network traffic generally | request and response page events |
Request and response objects at their respective lifecycle stages |
The names describe different moments. A request event fires when a request is issued; a response event fires when a response arrives. requestfinished indicates the response body has downloaded and the request completed. A request-level failure emits requestfailed instead of requestfinished, and can occur instead of receiving a response. An HTTP status such as 404 or 503 is still a response and does not by itself mean the request failed at the transport level. Inspect the status separately. See Puppeteer’s network logging guide.
2. Get a response from an existing request
When you receive an HTTPRequest in a listener or from waitForRequest(), request.response() is a synchronous accessor. It does not wait. If it returns null, the matching response has not arrived yet.
page.on('request', request => {
if (request.url().includes('/api/data')) {
const response = request.response();
console.log(response); // Often null at the request event stage
}
});
To obtain the eventual response for an already observed request, correlate it with the page’s response event, or set up a response wait before the action that triggers it. The event listener is the right choice when you are monitoring many requests; the waiter is usually simpler for one known action.
3. Wait for a response caused by an action
Create the wait promise before clicking, submitting, navigating, or otherwise triggering network activity. Then await both the action and the response. This avoids missing a fast response that arrives before the waiter is registered.
const responsePromise = page.waitForResponse(response =>
response.url() === 'https://example.com/api' &&
response.status() === 200
);
await page.click('#submit');
const response = await responsePromise;
console.log('Status:', response.status());
console.log('OK:', response.ok());
console.log('Headers:', response.headers());
A predicate can inspect the URL, status, or originating request, including its HTTP method. If multiple requests could match, make the predicate specific enough to select the intended one. Puppeteer also accepts a URL string as the first argument, but a predicate is more precise when the same URL may be requested more than once or with different methods.
Match method, URL, and status
const responsePromise = page.waitForResponse(response => {
const request = response.request();
return response.url().includes('/api/orders') &&
request.method() === 'POST' &&
response.status() >= 200 &&
response.status() < 300;
});
await page.click('#place-order');
const response = await responsePromise;
const payload = await response.json();
In a diagnostic flow, you may want to match the URL and method without requiring a success status, then inspect the status afterward. Otherwise, a server error response will not match and the wait may end in a timeout, hiding the useful error response.
Set a timeout or cancellation signal
waitForResponse() accepts an optional options object with timeout in milliseconds and an abort signal. The documented default timeout is 30 seconds; use 0 to disable it. The page’s default timeout can also be changed with page.setDefaultTimeout(). Prefer a bounded timeout in automation so a missing request cannot hang a job indefinitely.
const controller = new AbortController();
const responsePromise = page.waitForResponse(
response => response.url().includes('/api/data'),
{ timeout: 10_000, signal: controller.signal }
);
await page.click('#load-data');
const response = await responsePromise;
// If another branch decides the wait is no longer needed:
// controller.abort();
4. Inspect the HTTPResponse
Once resolved, an HTTPResponse lets you inspect the HTTP status, success classification, headers, URL, originating request, and body. Choose a body reader that matches the response content type. For JSON, call json(); for text, call text(); for raw bytes, call buffer().
const response = await responsePromise;
console.log(response.url());
console.log(response.status());
console.log(response.statusText());
console.log(response.ok());
console.log(response.headers());
const contentType = response.headers()['content-type'] || '';
if (contentType.includes('application/json')) {
const data = await response.json();
console.log(data);
} else {
const text = await response.text();
console.log(text);
}
ok() answers whether the status is in the successful HTTP range; it is separate from whether the browser successfully completed the network request. A 404 or 503 can produce an HTTPResponse with ok() === false. Handle unsuccessful statuses explicitly when they are expected possibilities.
5. Runnable Node.js example
Install Puppeteer in a new project with npm install puppeteer. Save this as response.js and run node response.js. It loads a page, observes an example API response if that page makes one, and closes the browser in a finally block. Replace the example URL and matching predicate with the page and response relevant to your workflow.
const puppeteer = require('puppeteer');
async function main() {
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
const responsePromise = page.waitForResponse(
response => response.url().includes('/api/data'),
{ timeout: 15_000 }
);
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
// If the page only sends the request after user input, register the
// wait immediately before the action instead of before navigation.
// await page.click('#load-data');
const response = await responsePromise;
console.log({
url: response.url(),
status: response.status(),
ok: response.ok(),
body: await response.text(),
});
} finally {
await browser.close();
}
}
main().catch(error => {
console.error(error);
process.exitCode = 1;
});
The example uses a placeholder endpoint and does not assert that a particular site sends that request. For a click-triggered request, create responsePromise immediately before the click, as in the earlier example. For a request that occurs during navigation, set up the waiter before calling page.goto().
6. Observe all responses with events
For logging or collecting many responses, listen to the page’s events. Avoid reading every body by default: a page may make many requests, and response bodies can be large or binary.
page.on('request', request => {
console.log('REQUEST', request.method(), request.url());
});
page.on('response', response => {
console.log('RESPONSE', response.status(), response.url());
});
page.on('requestfinished', request => {
console.log('FINISHED', request.url());
});
page.on('requestfailed', request => {
console.log('FAILED', request.url(), request.failure()?.errorText);
});
Register listeners before navigation or interaction to capture the full sequence. Remove listeners when finished if the page remains active for a long time, so repeated setup does not produce duplicate logs or retain unnecessary callbacks.
7. When to use waitForRequest
page.waitForRequest() resolves when a matching outgoing request is issued. It returns an HTTPRequest, not an HTTPResponse. Its response() can still be null at that point. Use it when you need to inspect request details such as method or post data; use waitForResponse() when the response itself is what you need.
const requestPromise = page.waitForRequest(request =>
request.url().includes('/api/orders') && request.method() === 'POST'
);
await page.click('#place-order');
const request = await requestPromise;
console.log(request.url());
console.log(request.method());
console.log(request.postData());
// This can be null because the response may not have arrived yet.
console.log(request.response());
Official references: Page.waitForRequest() and HTTPRequest.response().
8. Redirects and request lifecycle edge cases
- Redirects: a redirect response completes the original request and causes a new request to the redirected URL. If matching the final destination, account for the new URL and inspect request URLs or the redirect chain.
- HTTP errors: 404 and 503 are HTTP responses. Read
status()andok(); do not expectrequestfailedmerely because the status is an error. - Network failures: DNS, connection, or other request-level failures may result in
requestfailedwithout a response object. In that case there is no HTTP status to inspect. - Several matching requests: a broad URL substring may match analytics, retries, or requests with different methods. Filter by exact URL, method, status, or other response/request metadata.
- Response body read: choose JSON or text only when appropriate for the content type. A binary response should be read as a buffer. Treat malformed JSON as a parsing error separate from the HTTP status.
- Frames and popups: page-level events are associated with the page being observed. If a flow opens a new page or issues traffic from a separate target, attach listeners and waiters to the relevant page.
9. Common problems and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
request.response() returns null |
The request event fired before its response arrived. | Await page.waitForResponse() or handle the later response event. |
waitForResponse() times out |
The waiter was registered after the action, the predicate is too strict, or the action did not issue that request. | Register the waiter first; log request and response URLs; check method, redirects, and whether the interaction actually triggered traffic. |
| The wrong response is returned | The predicate matches a repeated URL or several similar requests. | Filter by exact URL and request method, and include a status condition only if error responses are not needed. |
| The response is 404 or 503 but there is no request failure | HTTP error status is a completed response, not necessarily a transport failure. | Inspect response.status() and response.ok() separately from request.failure(). |
response.json() throws |
The body is not valid JSON, is empty, or is a different content type. | Inspect the content type and status, then read text() or buffer() as appropriate; handle parse errors. |
| Navigation wait hangs or misses the response | The response wait was attached too late, or a URL predicate expects the pre-redirect URL while the response URL differs. | Create the promise before goto() or the action and account for redirect targets. |
| Waits accumulate or logs duplicate | Listeners were registered repeatedly and left attached. | Use a one-off waiter for a single response or remove event listeners when monitoring ends. |
10. Performance, reliability, and cost
A response waiter adds little overhead compared with launching and running a browser; the main delay is the page’s network and application behavior. Keep predicates fast and narrow. Avoid parsing or downloading every response body when only a status or header is needed. Set a timeout that fits the operation, close browser instances in cleanup paths, and log the request URL and method alongside failures so intermittent issues can be diagnosed.
Browser automation consumes compute and memory, especially when many pages run concurrently or load heavy sites. Reuse a browser process for related work where appropriate, create isolated pages or contexts for tasks, and limit concurrency based on available resources. Puppeteer itself has no per-request fee stated in these API references; operational cost depends on where and how the browser is run.
11. Or skip the browser setup
If the job is to capture a web page as an image or PDF rather than inspect its network response, ScreenshotNeo provides a website screenshot API and MCP server. A single GET request returns a PNG, JPEG, WebP, or PDF. See the ScreenshotNeo API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
- Cookie banners are accepted and removed before capture, along with known consent platforms, newsletter popups, and chat widgets; each step can be turned off.
- Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed. Response headers identify the page verdict and billing status.
- An MCP server exposes screenshot, page information, and PDF capture tools for AI agents and MCP clients.
- The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots.
Sign up for ScreenshotNeo and get 1,000 free screenshots a month with no card.
12. FAQ
Does request.response() wait for the response?
No. It returns the response if it has arrived, otherwise null. Use waitForResponse() to wait.
Can waitForResponse match an asynchronous predicate?
Yes. Puppeteer accepts an awaitable predicate, which can perform asynchronous checks, including reading response text. Keep such predicates selective because they may need to inspect multiple candidate responses.
Should I use requestfinished to get the response body?
Use the response object to inspect status, headers, and body. requestfinished marks the completed request lifecycle; it is not a substitute for choosing the response you need.
What timeout does waitForResponse use by default?
The current Puppeteer reference documents a 30-second default, configurable through the call’s options or the page’s default timeout. Pass timeout: 0 to disable the wait timeout.


