How to Parse POST Data in PhantomJS
Capture outgoing POST bodies in PhantomJS with onResourceRequested, parse JSON or forms safely, send controlled requests, and troubleshoot legacy-runtime issues.

Use page.onResourceRequested and inspect requestData.postData. Check requestData.method first so you only process POST requests. The callback observes outgoing traffic generated by the page; it does not automatically decode JSON, URL-encoded forms, or multipart data.
var page = require('webpage').create();
page.onResourceRequested = function (requestData, networkRequest) {
if (requestData.method === 'POST') {
console.log('POST to ' + requestData.url);
console.log(requestData.postData);
}
};
page.open('https://example.com', function (status) {
console.log('Page open: ' + status);
});
The first callback argument contains request metadata, including method, URL, headers and the body field used above. The second argument, networkRequest, is the request-control object for operations such as aborting a request, changing its URL or setting headers. Read the body from requestData, not from networkRequest. The PhantomJS WebPage API documents the callback; the postData property is also identified in the directly relevant community Q&A.
1. Observe POST requests made by a page
Register the handler before calling page.open. Forms, XHR calls, fetch implementations supported by the page, and navigation requests can then be observed as PhantomJS asks for resources.

var system = require('system');
var page = require('webpage').create();
page.onResourceRequested = function (requestData, networkRequest) {
if (requestData.method !== 'POST') {
return;
}
console.log(JSON.stringify({
method: requestData.method,
url: requestData.url,
headers: requestData.headers,
postData: requestData.postData
}, null, 2));
};
page.onError = function (message, trace) {
console.error(message);
};
var target = system.args[1] || 'https://example.com';
page.open(target, function (status) {
console.log('Open status: ' + status);
window.setTimeout(function () { phantom.exit(); }, 1000);
});
requestData.method is normally an uppercase HTTP method, but compare defensively if your page or wrapper changes casing. Logging headers and bodies can expose credentials, tokens and personal data; redact those values before writing logs.
Capture one request without flooding the console
var seen = 0;
page.onResourceRequested = function (requestData, networkRequest) {
if (requestData.method === 'POST' && seen < 1) {
seen += 1;
console.log(requestData.url);
console.log(requestData.postData || '(empty body)');
}
};
2. Parse JSON request bodies
postData is request-body text. If the request declares JSON, parse it explicitly and handle malformed input. Do not assume every POST body is JSON: a form submission usually uses URL encoding, and a file upload may be multipart.
function parseJsonBody(requestData) {
var body = requestData.postData || '';
if (!body) {
return null;
}
try {
return JSON.parse(body);
} catch (error) {
console.error('Invalid JSON from ' + requestData.url + ': ' + error.message);
return null;
}
}
page.onResourceRequested = function (requestData) {
if (requestData.method !== 'POST') {
return;
}
var contentType = '';
for (var i = 0; i < requestData.headers.length; i += 1) {
if (requestData.headers[i].name.toLowerCase() === 'content-type') {
contentType = requestData.headers[i].value.toLowerCase();
break;
}
}
if (contentType.indexOf('application/json') !== -1) {
var value = parseJsonBody(requestData);
if (value) {
console.log(JSON.stringify(value));
}
}
};
Some servers append a charset, such as application/json; charset=utf-8, so test the media-type prefix rather than requiring an exact string. JSON can be a scalar, array or object; validate the shape your application expects before reading properties.
3. Parse URL-encoded form data
For application/x-www-form-urlencoded, split and decode pairs with a parser available in your PhantomJS environment. A small implementation is useful when you cannot install a package.
function parseFormBody(body) {
var result = {};
if (!body) {
return result;
}
body.split('&').forEach(function (pair) {
if (!pair) return;
var parts = pair.split('=');
var key = decodeURIComponent((parts.shift() || '').replace(/\\+/g, ' '));
var value = decodeURIComponent(parts.join('=').replace(/\\+/g, ' '));
if (Object.prototype.hasOwnProperty.call(result, key)) {
if (!(result[key] instanceof Array)) result[key] = [result[key]];
result[key].push(value);
} else {
result[key] = value;
}
});
return result;
}
page.onResourceRequested = function (requestData) {
if (requestData.method === 'POST') {
console.log(JSON.stringify(parseFormBody(requestData.postData || '')));
}
};
This handles repeated keys by storing an array and converts plus signs to spaces. It is still a limited parser: malformed percent escapes can throw, and nested conventions such as user[name] remain literal keys. For production scripts, use a well-reviewed query-string parser compatible with the PhantomJS runtime you deploy.
Multipart and binary bodies
The reviewed PhantomJS references do not specify a universal representation for multipart request bodies. Treat the value as opaque text unless you have confirmed the runtime’s behavior for your exact upload. Do not split multipart data on an arbitrary delimiter: boundaries, binary bytes and encoding rules must be honored. When you control the page, prefer logging the logical fields before submission or inspect the server-side request.
4. Distinguish request data from response data
onResourceRequested runs for the outgoing request. Use onResourceReceived for response metadata such as status, content type, headers and transfer stage.
page.onResourceReceived = function (response) {
console.log(response.status + ' ' + response.contentType + ' ' + response.url);
};
Large resources can produce multiple onResourceReceived callbacks, one for each chunk. A response callback therefore is not a substitute for request-body capture, and a single response status should not be inferred from one arbitrary chunk event. The page-open callback’s status value is a load result such as success or fail; it is not the HTTP status code.
5. Send a controlled POST with page.open
If you need to create a request rather than observe one generated by page behavior, pass a settings object to page.open. Match the server’s expected serialization and content type.
var page = require('webpage').create();
var settings = {
operation: 'POST',
encoding: 'utf8',
headers: { 'Content-Type': 'application/json' },
data: JSON.stringify({ some: 'data', another: ['custom', 'data'] })
};
page.open('https://example.com/api', settings, function (status) {
console.log('Load result: ' + status);
phantom.exit();
});
The API also accepts URL-encoded text, for example user=username&password=password. Set the matching content type and encoding. The callback reports page-load success or failure, while onResourceReceived exposes HTTP response metadata.
6. A complete diagnostic script
This script records method, URL, headers and a safely truncated body, then attempts JSON or form parsing. It avoids printing an entire secret-bearing payload.
var system = require('system');
var page = require('webpage').create();
var MAX_BODY = 4096;
function headerValue(headers, name) {
name = name.toLowerCase();
for (var i = 0; i < headers.length; i += 1) {
if (headers[i].name.toLowerCase() === name) return headers[i].value;
}
return '';
}
function redact(value) {
return value.replace(/(password|token|secret|authorization)=([^&]*)/ig, '$1=[redacted]');
}
page.onResourceRequested = function (requestData) {
if (requestData.method !== 'POST') return;
var body = requestData.postData || '';
var contentType = headerValue(requestData.headers, 'content-type').toLowerCase();
var sample = redact(body.slice(0, MAX_BODY));
console.log('POST ' + requestData.url);
console.log('Content-Type: ' + contentType);
console.log('Body (' + body.length + ' bytes): ' + sample);
if (contentType.indexOf('application/json') !== -1) {
try { console.log(JSON.stringify(JSON.parse(body))); }
catch (e) { console.error('JSON parse failed: ' + e.message); }
}
};
page.open(system.args[1] || 'https://example.com', function (status) {
console.log('Page status: ' + status);
window.setTimeout(function () { phantom.exit(); }, 1000);
});
7. Troubleshooting checklist
| Symptom | Likely cause | Fix |
|---|---|---|
| No output | The handler was attached after navigation, or the page made no POST. | Register it before page.open; trigger the form or XHR; log every method temporarily. |
postData is empty |
The request has no body, uses another method, or the runtime does not expose the payload for that body type. | Log method, URL and headers; verify the browser actually submitted data; treat multipart as runtime-specific. |
| JSON parse error | The body is form-encoded, malformed, prefixed with another value, or truncated by your own logging. | Inspect Content-Type, parse the original body, and catch errors. |
| Form values are wrong | Plus signs, percent escapes or repeated keys were handled incorrectly. | Decode + as spaces, use decodeURIComponent, and preserve duplicate keys. |
| Expected HTTP status is missing | You are reading the page.open callback status. |
Observe onResourceReceived for response status. |
| Several response events appear | Large responses are delivered in chunks. | Use the response stage and URL to correlate events; do not treat each chunk as a new request. |
| Modern site fails to load | PhantomJS is legacy software with an old rendering and TLS stack. | Confirm the site supports the runtime, or move capture and inspection to a maintained browser. |
8. Security, performance and reliability
- Secrets: redact cookies, Authorization headers, passwords and tokens. Store diagnostic output with the same access controls as application logs.
- Overhead: request callbacks run for every resource. Filter by method and, when possible, URL before serializing large objects.
- Timing: a page can issue POSTs after initial load. Wait for the user action or a known selector instead of exiting immediately.
- Retries: do not replay a captured POST blindly. It may create a duplicate order or mutation. Check idempotency rules first.
- Runtime status: the PhantomJS project says development is suspended, 2.1 is the latest stable release, and version 2.1.0 dates to 2016-01-23. Treat compatibility with current websites as an open question, not a guarantee. See the project repository and 2.1 release information.
9. Or skip the browser setup
If your actual goal is a clean visual capture of a page after its requests complete, ScreenshotNeo provides a single API call. Its capture service accepts the cookie or consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups and chat widgets before the shot. Bot checks, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing result. An MCP server also lets Claude, Cursor and other MCP clients call take_screenshot, get_page_info and capture_pdf.

See the ScreenshotNeo API documentation for all options.
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}`);
You can also choose full-page capture with lazy images loaded, a CSS-selected element, dark mode, device presets or custom viewports, retina scale, PDF paper and margins, custom CSS or JavaScript, click and wait actions, blocked resource types, headers, cookies, user agents, Authorization, timezone, geolocation, transparent backgrounds, resizing, a chosen cache TTL, signed links, asynchronous webhooks, bulk capture of up to 100 URLs per call and usage reporting. Every feature is included on every plan. The Free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000.
Create a free ScreenshotNeo account and get 1,000 screenshots a month with no card.
10. FAQ
Does onResourceRequested expose response bodies?
No. It is for outgoing request metadata. Use response callbacks for response status and headers; capturing response content is a separate problem.
Can I modify a POST in the callback?
The second callback argument is the control object for supported request operations. Changing a body is not established by the references; use page.open when you need a known payload.
Why does a page-open callback say success when my API returned an error?
The callback’s status describes page loading, not the HTTP status code. Inspect onResourceReceived.
Is PhantomJS suitable for new automation?
It is a legacy, suspended project. Use it when maintaining an existing script requires it, and evaluate a maintained browser for new work.


