How to Screenshot GST Invoice Webpages in PHP Using Headless Chrome
Capture a rendered GST invoice webpage in PHP with headless Chrome, wait for dynamic content, preserve the QR code, and troubleshoot common capture failures.
Direct answer: Use Symfony Panther to launch headless Chrome from PHP, open the invoice webpage, wait for an invoice-specific element to render, set a deliberate viewport, and save a screenshot. This captures what Chrome rendered; it does not create, issue, validate, or make a GST invoice compliant.
For a PHP application that needs to wait for JavaScript-rendered invoice content or interact with the page, Panther is the practical route. For a simple fixed-URL capture with few interactions, Chrome’s headless command-line mode can save a screenshot directly. The examples below use an authorized invoice page; do not expose credentials or capture invoices you are not permitted to access.
1. Install PHP browser automation and Chrome
Install Symfony Panther with Composer and make Chrome or Chromium plus a compatible driver available in the runtime environment. Follow the Symfony Panther documentation for installation and environment setup. The browser must be able to start in the environment where PHP runs; a local desktop Chrome installation is not automatically available inside a container or server.
composer require symfony/panther
Keep Chrome and its driver compatible and provision them in deployment rather than assuming that a development machine’s browser is present in production. Run the capture process with filesystem permission to write to the chosen output directory.
2. Capture an invoice webpage with Panther
Save this as capture-invoice.php. Replace the URL and selector with values for your authorized invoice page. The example waits for #invoice, sets a desktop viewport, then writes a PNG file.
<?php
require __DIR__ . '/vendor/autoload.php';
use Symfony\Component\Panther\Client;
$invoiceUrl = 'https://example.com/invoices/123'; // Replace with your authorized URL.
$outputPath = __DIR__ . '/invoice.png';
$client = Client::createChromeClient();
try {
$client->getWebDriver()->manage()->window()->setSize(1440, 1200);
$client->request('GET', $invoiceUrl);
// Wait for the invoice to appear instead of guessing with a short sleep.
$client->waitFor('#invoice');
// If the page populates fields after the container appears, wait for a
// stable value or a more specific selector from your application.
$client->waitFor('#invoice .invoice-number');
$client->takeScreenshot($outputPath);
fwrite(STDOUT, "Saved screenshot to {$outputPath}" . PHP_EOL);
} finally {
$client->quit();
}
The selector waits are examples: use stable selectors that mean the invoice data and any required QR code are actually ready. A container can appear before asynchronous data, fonts, images, or the QR code have finished loading. Panther’s browser automation supports JavaScript pages, element waits, window sizing, and screenshot capture; see the Symfony Panther guide.
Wait for the QR code when it is applicable
If your invoice page renders an e-invoice QR code, wait for the application’s QR element as well as the invoice body. Replace #invoice .qr-code with the real selector. For a canvas-rendered QR, wait for its canvas selector and, if necessary, for the application to signal that rendering is complete.
$client->waitFor('#invoice .qr-code');
Choose a viewport that keeps the QR code and required invoice fields visible, or capture the whole page if the page is taller than the viewport. Inspect the resulting image for clipped totals, missing fonts or images, absent QR codes, and browser overlays. This is a visual record of the webpage, not proof that the displayed invoice satisfies tax requirements.
3. Alternative: use Chrome headless from PHP
When you only need a URL capture and do not need PHP-level browser interaction, invoke Chrome’s documented headless screenshot mode from PHP. Chrome supports --screenshot, --window-size, and a bounded --timeout. Verify the executable path and supported flags for the Chrome or Chromium version installed in your runtime.
<?php
$url = 'https://example.com/invoices/123'; // Replace with your authorized URL.
$output = __DIR__ . '/invoice.png';
$chrome = '/usr/bin/google-chrome'; // Adjust for the runtime.
$command = sprintf(
'%s --headless --disable-gpu --window-size=1440,1200 --timeout=10000 --screenshot=%s %s 2>&1',
escapeshellarg($chrome),
escapeshellarg($output),
escapeshellarg($url)
);
exec($command, $lines, $status);
if ($status !== 0 || !is_file($output) || filesize($output) === 0) {
throw new RuntimeException("Chrome capture failed: " . implode("\n", $lines));
}
echo "Saved screenshot to {$output}" . PHP_EOL;
Chrome’s --timeout bounds how long capture waits; it is not an invoice-specific readiness check. A fixed delay can still capture a loading state if the site takes longer or populates data late. Use Panther when you need to wait for a known element, authenticate through browser flows, or interact with the page. See Chrome Headless documentation.
4. Choose the capture path
| Path | Use it when | Tradeoff |
|---|---|---|
| Symfony Panther | Your PHP code needs JavaScript rendering, element waits, navigation, or browser interaction. | Requires PHP dependencies and Chrome/driver setup. |
| Chrome headless CLI | You need a straightforward capture of a fixed URL at known dimensions. | Less convenient for login flows, application-specific readiness, or interaction. |
| ScreenshotNeo API | You want a screenshot without provisioning and managing a browser in your PHP runtime. | Requires an API key and a network request; inspect the returned status and billing headers. |
5. Understand what a GST invoice screenshot proves
A screenshot records the rendered webpage. It does not issue an invoice, generate an Invoice Reference Number (IRN), or validate the tax particulars. CBIC Rule 46 sets out tax invoice particulars, including supplier details, GSTIN, a consecutive serial number unique for a financial year, issue date, and recipient information. The e-invoice process in Rule 48(4) applies to notified persons and involves FORM GST INV-01 particulars and an IRN obtained through the prescribed portal workflow. Review the CBIC CGST Rules, including Rules 46 and 48, and current amendments for the applicable requirements.
Where e-invoicing provisions apply, GSTN guidance describes the IRP-returned QR code and data encoded in it, including supplier and recipient GSTINs, invoice number and date, invoice value, line-item count, main-item HSN code, IRN, and IRN generation date. If the webpage displays this code, wait for it to render and keep it large enough to remain legible in the saved image. See the GSTN e-invoice FAQ. Whether the screenshot is suitable for a particular recordkeeping or business process depends on that process; it does not replace the underlying invoice or compliance workflow.
6. ScreenshotNeo: capture without browser setup
If you would rather not install and maintain Chrome in the PHP runtime, ScreenshotNeo is a website screenshot API and MCP server for developers. Send one GET request with a URL to receive an image or PDF. Cookie and consent banners, newsletter popups, and chat widgets are removed before capture; each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status.
See the ScreenshotNeo API documentation for request options and response details. This PHP example saves the returned image bytes:
<?php
$query = http_build_query([
'access_key' => 'YOUR_API_KEY',
'url' => 'https://example.com/invoices/123',
]);
$apiUrl = 'https://api.screenshotneo.com/v1/shot?' . $query;
$context = stream_context_create([
'http' => [
'timeout' => 90,
'ignore_errors' => true,
],
]);
$body = file_get_contents($apiUrl, false, $context);
if ($body === false) {
throw new RuntimeException('ScreenshotNeo request failed at the network or HTTP layer.');
}
file_put_contents(__DIR__ . '/invoice.webp', $body);
echo "Saved ScreenshotNeo response to invoice.webp" . PHP_EOL;
For production code, check the HTTP status and response content type before treating the response body as an image; also read X-Page-Verdict and X-Billed to distinguish page outcomes and billing. Keep the access key on your server and out of browser JavaScript or public repositories. The endpoint supports PNG, JPEG, or WebP images and PDF, along with options such as full-page capture, element selection, waits, custom headers and cookies, caching, and asynchronous jobs. The docs list supported parameters; do not assume a parameter is enabled without consulting them.
cURL:
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com/invoices/123 \
-o invoice.webp
Python:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com/invoices/123"},
timeout=90,
)
r.raise_for_status()
with open("invoice.webp", "wb") as f:
f.write(r.content)
Node.js:
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://example.com/invoices/123'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await (await import('node:fs/promises')).writeFile('invoice.webp', bytes);
ScreenshotNeo also has an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. It offers 1,000 screenshots per month free with no card; paid plans start at $5 for 3,000, and every feature is on every plan. Sign up for 1,000 free screenshots a month, with no card required.
7. Troubleshooting
| Symptom | Likely cause | What to do |
|---|---|---|
| Chrome will not start | Chrome/Chromium is missing, the driver is unavailable or incompatible, or the runtime cannot launch the browser. | Install the browser and compatible driver in the same runtime; verify executable paths and permissions. Check Panther and browser startup output. |
| Screenshot is blank or shows a loader | The capture happened before client-side rendering or invoice data retrieval completed. | Wait for an invoice-specific selector and a populated value, not just document navigation. For CLI capture, increase the bounded timeout or use Panther for a condition-based wait. |
| Invoice fields are missing | The page requires authentication, a session cookie, or a redirect completed after the initial navigation. | Use an authorized authenticated browser session or configure the required authentication flow. Check the final page URL and visible page state before capturing. |
| QR code or logo is absent | An image or canvas has not finished rendering, or the element is outside the captured area. | Wait for its selector or app readiness signal, use appropriate viewport/full-page behavior, then inspect the saved image. |
| Totals or footer are clipped | The content exceeds the viewport or is positioned beyond the visible region. | Increase the viewport or capture full-page where supported. Confirm the final image dimensions and inspect the bottom of the invoice. |
| Fonts differ or text wraps | Web fonts have not loaded, or the viewport differs from the intended layout. | Wait for the page’s readiness state, use consistent dimensions, and verify the final render in the target runtime. |
| Chrome CLI exits successfully but no usable file exists | The output path is unwritable, wrong, or the page failed before an image was saved. | Use an absolute writable path and verify that the output exists and has nonzero size after execution. |
| ScreenshotNeo response is not an image | The request returned an error or a non-success page verdict. | Check HTTP status, content type, X-Page-Verdict, and X-Billed; correct the URL, access key, or page issue before saving as an image. |
8. Performance, reliability, and cost
- Wait on meaning, not a tiny fixed delay. An invoice selector or readiness signal avoids many premature captures. Keep a bounded timeout so a missing element does not leave a worker waiting indefinitely.
- Control the rendering environment. Pin consistent Chrome/driver versions and viewport dimensions where repeatability matters. Remote fonts, images, and scripts can make a page depend on external services.
- Manage browser lifecycle. Close Panther clients after capture and isolate failures per job. For batch work, limit concurrent browsers to the CPU and memory available to the PHP workers.
- Validate artifacts. Check that the output exists and is nonempty; for important records, inspect image dimensions and confirm required fields and QR visibility.
- Account for operating cost. Self-hosted capture uses your compute and browser maintenance; no benchmark or per-capture cost is implied here. ScreenshotNeo pricing is 1,000 free shots per month, then Starter $5 for 3,000, Growth $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000. Yearly billing gives two months free. Only clean shots are billed, and each response indicates page verdict and billing.
- Treat invoice data as sensitive. Screenshots may contain personal or financial information. Store them with access controls and retention appropriate to your workflow, and avoid logging full URLs if they contain sensitive tokens.
9. Frequently asked questions
Can PHP take a screenshot without Selenium?
Yes. Symfony Panther provides PHP browser automation around Chrome and WebDriver. For a basic URL capture, PHP can also invoke Chrome’s headless CLI.
Does a screenshot count as a GST invoice?
No. It is an image of the page as rendered. It does not issue an invoice, obtain an IRN, or establish compliance with invoice rules.
Should I capture the whole page or just the visible viewport?
Use a viewport capture when the complete invoice fits and you need a predictable frame. Use full-page capture when content extends below it, then check that small text and the QR code remain readable.
Can I automate captures from an AI coding assistant?
ScreenshotNeo’s MCP server exposes screenshot, page information, and PDF capture tools to Claude, Cursor, and other MCP clients.
Sources
- Symfony Panther documentation — browser setup, navigation, waits, window size, and screenshots.
- Chrome Headless documentation — command-line screenshot, dimensions, and timeout.
- CBIC CGST Rules — invoice particulars and e-invoice process.
- GSTN e-invoice FAQ — IRP QR code and encoded invoice data.
- ScreenshotNeo API documentation — API parameters and response details.


