Python website screenshots
Take website screenshots in Python and Django with the Urlbox SDK — signed render links for templates, direct downloads, and async renders with webhooks.
The urlbox Python package wraps the Urlbox API in a single UrlboxClient. Use it to:
- Generate a render link — a signed URL for an
<img>tag in a Django or Jinja template. The browser fetches the screenshot, so your view returns straight away. - Download a screenshot — fetch the image bytes in a script, Celery task or management command.
- 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
pip install urlboxFind your API key and secret in your project's settings on the dashboard. Read them from the environment rather than committing them:
import os
from urlbox import UrlboxClient
urlbox = UrlboxClient(
api_key=os.environ["URLBOX_API_KEY"],
api_secret=os.environ["URLBOX_SECRET"],
)Generate a render link
screenshot_url = urlbox.generate_url({
"url": "https://github.com",
"thumb_width": 600,
"format": "jpg",
"quality": 80,
})The link is signed with your secret, so nobody can change the options and spend your credits. Pass it to your template:
<img src="{{ screenshot_url }}" alt="Screenshot of github.com">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.
Download a screenshot
get() renders the page and returns a requests response with the image bytes:
response = urlbox.get({
"url": "https://example.com",
"full_page": True,
"hide_cookie_banners": True,
"block_ads": True,
})
response.raise_for_status()
with open("screenshot.png", "wb") as f:
f.write(response.content)The Content-Type header matches the format you asked for (image/png by default, application/pdf for "format": "pdf", and so on).
Render asynchronously
post() starts a render and returns immediately with a renderId and a statusUrl:
response = urlbox.post({
"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",
})
render = response.json()
render_id, status_url = render["renderId"], 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 X-Urlbox-Signature header with the SDK's webhook_validator before you trust the payload. It needs your project's webhook secret, which is different from your API secret. Here it is in Flask:
import os
from flask import Flask, abort, request
from urlbox import InvalidHeaderSignatureError, webhook_validator
app = Flask(__name__)
@app.post("/webhooks/urlbox")
def urlbox_webhook():
payload = request.get_json()
try:
webhook_validator.call(
request.headers.get("X-Urlbox-Signature", ""),
payload,
os.environ["URLBOX_WEBHOOK_SECRET"],
)
except InvalidHeaderSignatureError:
abort(401)
if payload["event"] == "render.succeeded":
print(payload["renderId"], payload["result"]["renderUrl"])
return "", 200The validator also rejects signatures more than five minutes old, which stops replayed requests. See Webhooks for the payload format.
Option 2: poll the status URL
No public endpoint to receive webhooks (a script, a notebook, a cron job)? Poll status_url until the render finishes:
import os
import time
import requests
def wait_for_render(status_url, interval=2, timeout=300):
deadline = time.monotonic() + timeout
headers = {"Authorization": f"Bearer {os.environ['URLBOX_SECRET']}"}
while time.monotonic() < deadline:
render = requests.get(status_url, headers=headers, timeout=30).json()
if render["status"] == "succeeded":
return render # includes renderUrl and size
if render["status"] in ("failed", "not-found"):
raise RuntimeError(render.get("reason", render["status"]))
time.sleep(interval) # "created" or "retrying": still working
raise TimeoutError("Timed out waiting for render")
render_url = wait_for_render(status_url)["renderUrl"]The SDK warns when you call post() without a webhook_url; polling like this is the alternative it means. Send your secret with each poll, so only your project can read the render's status.
Calling the API without the SDK
To build render links yourself, sign the query string with HMAC-SHA256 using your API secret:
import hmac
from hashlib import sha256
from urllib.parse import urlencode
def render_link(options, api_key, api_secret, fmt="png"):
query = urlencode(options, True)
token = hmac.new(api_secret.encode(), query.encode(), sha256).hexdigest()
return f"https://api.urlbox.com/v1/{api_key}/{token}/{fmt}?{query}"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.