ScreenshotNeo

BlogHow-to

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.

By the ScreenshotNeo team30 September 20268 min read

How to Parse POST Data in PhantomJS

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.

The request callback observes the outgoing body before you parse its format.
The request callback observes the outgoing body before you parse its format.
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.

A hosted capture service can remove common overlays before producing the image.
A hosted capture service can remove common overlays before producing the image.

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.