SECURITY / WHAT THE CODE ENFORCES
How Screenshotly handles your keys, URLs, and captures.
This page describes controls that are implemented in the service today. It does not list certifications we do not hold. For how to secure your own integration, read the integration security guide.
Last reviewed 13 November 2026
01 / API keys
Keys are stored as hashes and always expire.
Every API request is authenticated with a workspace key sent in the x-api-key header or as a Bearer token.
- Generated from 32 random bytes
- Keys use the scr_ prefix followed by 64 hexadecimal characters from a cryptographically secure random source.
- Stored only as SHA-256 hashes
- The plaintext key is shown once when it is created. Screenshotly cannot display or recover it later.
- Expiry on every key
- Each key has an expiry date. Keys created in the dashboard expire after 30 days. Expired and revoked keys are rejected with 401.
- Revocation and limits
- A workspace can hold up to 10 active keys. Only the key creator or a workspace Owner or Admin can revoke a key.
- Verified email required
- The API rejects keys with 403 until the account email address has been verified.
- Spend controls
- Account owners can set a hard usage limit below the plan quota and a usage alert threshold, which caps what a leaked key can consume.
02 / Target URLs
The renderer cannot be pointed at private networks.
A screenshot API fetches URLs on your behalf, so server-side request forgery (SSRF) is the central risk. These checks run on every capture.
- HTTP and HTTPS only
- Other schemes are rejected, and URLs that embed a username or password are refused.
- Resolved-address checks
- The hostname is resolved and the request is refused if any address is loopback, private (RFC 1918), carrier-grade NAT, link-local (including the 169.254.169.254 cloud metadata address), multicast, reserved, or an IPv6 unique-local or link-local address. IPv4-mapped IPv6 forms are checked too.
- Blocked internal hostnames
- localhost and names ending in .localhost, .local, .internal, or .home.arpa are refused before resolution.
- Every subrequest is checked
- While the page renders, each request the browser makes, including redirects, scripts, images, and frames, passes through the same check. Requests that fail it are aborted.
- Webhook destinations too
- Webhook URLs are checked when you submit the request and again at delivery. Webhook requests do not follow redirects and time out after 10 seconds.
03 / Rendering
Each capture gets its own browser.
Credentials you send for authenticated captures should only ever reach the page you asked for.
- Fresh browser per capture
- Every capture launches a new headless Chromium instance and closes it when the capture finishes, so cookies and page state are not carried between requests.
- Cookie domain matching
- Cookies you send must belong to the target hostname or one of its parent domains; mismatched cookie domains are rejected with 400.
- Plan-gated credentials and code
- Custom headers and cookies require Basic or higher. Custom JavaScript requires Growth or higher. The public playground accepts none of them, nor webhooks or storage delivery.
- Strict request schema
- Request bodies are limited to 64 KiB and validated against a strict schema; unknown fields are rejected. Forwarding and hop-by-hop headers such as Host and X-Forwarded-For cannot be set.
04 / Capture data
Captures are stored only when you ask.
What happens to a captured file depends on how you request it. Account, usage, and job records are covered by the privacy policy.
- Synchronous responses
- A standard request returns the file in the response body and does not write it to Screenshotly storage. Binary responses are sent with Cache-Control: no-store unless you enable caching.
- Opt-in caching
- With cache enabled (Basic and higher), the result is kept for the cacheTtl you choose, from 60 seconds to 24 hours. The cache key is a hash of every capture parameter, including headers and cookies, so credential values never appear in key names and different credentials never share an entry.
- Retained delivery and async jobs
- Files from storage delivery or async jobs (Basic and higher) are written under a workspace-scoped path with a SHA-256 checksum. They expire after the retention period in the workspace owner's account settings: 7 days by default, configurable from 1 to 30 days. Each file's expiry is fixed when it is captured, so a change applies to later captures.
- Signed, expiring download links
- Stored files are served only through signed links that expire. Links returned by storage delivery and webhooks last 24 hours; job result links last 15 minutes. Expired files return 410 and are deleted by the capture worker.
- Encrypted job payloads
- Queued capture requests and webhook deliveries, which can contain your cookies, headers, and webhook secrets, are encrypted with AES-256-GCM before they are written to the database.
05 / Webhooks
Webhook payloads can be verified.
Set webhook.secret on a request to have each delivery signed.
- HMAC-SHA256 signature
- The raw JSON body is signed with your secret and sent as X-Screenshotly-Signature: sha256=<hex>. Compare it in constant time before trusting the payload.
- Timestamp
- Each delivery carries X-Screenshotly-Timestamp, and the same value appears as timestamp inside the signed body. Check the body value to reject stale or replayed deliveries.
- Bounded retries
- A failed delivery is retried with increasing delays, up to 5 attempts in total.
06 / Platform
Accounts, payments, and transport.
Parts of the service rely on specialist providers listed in the privacy policy.
- Dashboard sign-in
- Dashboard accounts are managed by Clerk. Workspaces have Owner, Admin, and Member roles; only Owners and Admins can add members.
- Payments
- Payments are processed by Stripe. Screenshotly does not store card numbers.
- HTTPS everywhere
- The site and API are served over HTTPS with HTTP Strict Transport Security (two-year max-age, includeSubDomains, preload), a Content Security Policy, X-Frame-Options: DENY, and X-Content-Type-Options: nosniff.
07 / Limits of this page
What we do not claim.
- We do not publish a SOC 2, ISO 27001, or similar third-party audit report, and this page is not a compliance attestation.
- We do not offer a contractual uptime SLA. The status page shows live checks, not historical uptime.
- Screenshots are a copy of whatever the page displays. Do not capture pages containing regulated data, such as health or payment card data, unless your own compliance review allows it.
08 / Reporting
Report a vulnerability privately.
Email security@screenshotly.app with a description, steps to reproduce, and the impact you observed. Please do not open a public issue or disclose the problem before it is fixed, and do not include live API keys or customer data in the report.
If you think one of your own keys has been exposed, revoke it under Dashboard → API keys first, then create a replacement.
Related: Privacy policy · Terms · Status · API authentication docs