PHP website screenshots
Take website screenshots in PHP and Laravel with the Urlbox SDK — signed render links, sync renders saved to disk, and async renders with verified webhooks.
The urlbox/screenshots Composer package (source on GitHub) wraps the Urlbox API for plain PHP and Laravel. Use it to:
- Generate a render link — a signed URL for an
<img>tag. The browser fetches the screenshot, so your page renders straight away. - Render synchronously — wait for the render and get a
renderUrl, or save the file straight to disk. - Render asynchronously — start a render and get the result by webhook or by polling. Best for long or bulk renders.
See Sync vs Async for the trade-offs.
Install
composer require urlbox/screenshotsFind your API key, secret and webhook secret in your project's settings on the dashboard.
<?php
require 'vendor/autoload.php';
use Urlbox\Screenshots\Urlbox;
$urlbox = Urlbox::fromCredentials(
getenv('URLBOX_KEY'),
getenv('URLBOX_SECRET'),
getenv('URLBOX_WEBHOOK_SECRET') // optional, only needed to verify webhooks
);Laravel
The package registers itself in Laravel 5.5 and later. Add your credentials to config/services.php:
'urlbox' => [
'key' => env('URLBOX_KEY'),
'secret' => env('URLBOX_SECRET'),
'webhook_secret' => env('URLBOX_WEBHOOK_SECRET'),
],and to your .env:
URLBOX_KEY=YOUR_URLBOX_KEY
URLBOX_SECRET=YOUR_URLBOX_SECRET
URLBOX_WEBHOOK_SECRET=YOUR_URLBOX_WEBHOOK_SECRETGenerate a render link
$imgUrl = $urlbox->generateSignedUrl([
'url' => 'example.com',
'width' => 1280,
'height' => 1024,
]);
// https://api.urlbox.com/v1/API_KEY/TOKEN/png?url=example.com&width=1280&height=1024
echo '<img src="' . htmlspecialchars($imgUrl) . '" alt="Screenshot of example.com">';The link is signed with your secret, so nobody can change the options and spend your credits. The first request renders the page, and later requests for the same link are served from the cache. See Render links for caching and refresh options.
Render synchronously
render() calls POST /v1/render/sync and returns the decoded response. Pass a path as the second argument to download the file too:
$result = $urlbox->render(
[
'url' => 'https://example.com',
'full_page' => true,
'hide_cookie_banners' => true,
],
__DIR__ . '/screenshot.png'
);
echo $result['renderUrl']; // temporary URL, expires after 30 days
echo $result['localPath']; // where the file was savedRender asynchronously
renderAsync() starts a render and returns straight away with a renderId and a statusUrl:
$render = $urlbox->renderAsync([
'url' => 'https://example.com',
'full_page' => true,
// Omit webhook_url if you'd rather poll (option 2 below).
'webhook_url' => 'https://your-app.com/webhooks/urlbox',
]);
$renderId = $render['renderId'];
$statusUrl = $render['statusUrl'];Then collect the result in one of two ways.
Option 1: receive a webhook
Urlbox posts the result to your webhook_url when the render finishes. Check the signature with verifyWebhookSignature() before you trust the payload. The client needs your project's webhook secret (the third argument to fromCredentials), which is different from your API secret.
$header = $_SERVER['HTTP_X_URLBOX_SIGNATURE'] ?? '';
$body = file_get_contents('php://input'); // the raw body, exactly as sent
if (!$urlbox->verifyWebhookSignature($header, $body)) {
http_response_code(401);
exit;
}
$event = json_decode($body, true);
if ($event['event'] === 'render.succeeded') {
$renderUrl = $event['result']['renderUrl'];
}In Laravel, use $request->header('X-Urlbox-Signature') and $request->getContent() in the controller instead. See Webhooks for the payload format.
Option 2: poll the status URL
No public endpoint to receive webhooks (an Artisan command, a queued job, a CLI script)? Poll statusUrl until the render finishes. The SDK already depends on Guzzle:
use GuzzleHttp\Client;
function waitForRender(string $statusUrl, int $timeout = 300): array
{
$http = new Client(['timeout' => 30]);
$deadline = time() + $timeout;
while (time() < $deadline) {
$response = $http->get($statusUrl, [
'headers' => ['Authorization' => 'Bearer ' . getenv('URLBOX_SECRET')],
'http_errors' => false, // a 404 still carries a JSON status
]);
$render = json_decode((string) $response->getBody(), true);
if ($render['status'] === 'succeeded') {
return $render; // includes renderUrl and size
}
if (in_array($render['status'], ['failed', 'not-found'], true)) {
throw new RuntimeException($render['reason'] ?? $render['status']);
}
sleep(2); // "created" or "retrying": still working
}
throw new RuntimeException('Timed out waiting for render');
}
$renderUrl = waitForRender($statusUrl)['renderUrl'];Send your secret with each poll, so only your project can read the render's status.
Next steps
- Browse every option in the Render Options reference.
- See Common Problems if a screenshot comes back blank, blocked or cut off.
- Read the API reference for every endpoint, error code and response field.