Node.js SDK

The official TypeScript/JavaScript SDK wraps every API endpoint with typed methods and includes a polling helper for waiting on async jobs.

Installation

npm / yarn / pnpm
1npm install screenshotfreeapi

Initialization

Initialize the client
1import { ScreenshotFreeAPIClient } from 'screenshotfreeapi'; 2 3const client = new ScreenshotFreeAPIClient({ 4 apiKey: process.env.SCREENSHOTFREEAPI_KEY, // required 5 timeout: 30_000, // optional, ms 6});
Pass apiKey via an environment variable. The SDK throws at construction time if the key is missing.

Web screenshots

client.screenshots.web()
1// Basic capture 2const job = await client.screenshots.web({ 3 url: 'https://stripe.com/pricing', 4 format: 'png', 5}); 6 7// With AI targeting 8const aiJob = await client.screenshots.web({ 9 url: 'https://stripe.com/pricing', 10 description: 'the pricing comparison table', 11}); 12 13// With full options 14const richJob = await client.screenshots.web({ 15 url: 'https://stripe.com/pricing', 16 dimensions: { width: 1440, height: 900 }, 17 fullPage: true, 18 blockAds: true, 19 acceptCookies: true, 20 webhookUrl: 'https://your-app.com/webhook', 21});

Mobile screenshots

client.screenshots.mobile()
1const job = await client.screenshots.mobile({ 2 appName: 'Instagram', 3 platform: 'both', 4 includeStoreListing: true, 5 deviceEmulation: 'iPhone 14', 6});

Job helpers

client.jobs.*
1// Poll until done (built-in exponential backoff) 2const result = await client.jobs.waitForResult(job.jobId, { 3 pollIntervalMs: 2000, 4 timeoutMs: 60_000, 5}); 6 7console.log(result.screenshotUrl); // presigned S3 URL 8console.log(result.dimensions); 9console.log(result.metadata.processingMs); 10 11// Get a fresh URL for an existing job 12const fresh = await client.jobs.getResult('job_web_7a91bcd3');

Webhook verification

Verify the X-ScreenshotFree-Signature header on every incoming webhook using HMAC-SHA256 and a timing-safe comparison. Always read the raw request body before parsing JSON.

Verify webhook signature
1import crypto from 'node:crypto'; 2 3// Header format: "X-ScreenshotFree-Signature: t=<unix_ts>,v1=<hex_hmac>" 4// Signed content is "<timestamp>.<raw_body>" — NOT the raw body alone. 5function verifyWebhook(rawBody: Buffer, header: string, secret: string, toleranceSeconds = 300): boolean { 6 const parts = Object.fromEntries(header.split(',').map((p) => p.split('='))); 7 const timestamp = parseInt(parts['t'] ?? '0', 10); 8 const v1 = parts['v1'] ?? ''; 9 if (!timestamp || !v1) return false; 10 11 // Reject replayed webhooks older than the tolerance window 12 if (Math.floor(Date.now() / 1000) - timestamp > toleranceSeconds) return false; 13 14 const expected = crypto 15 .createHmac('sha256', secret) 16 .update(`${timestamp}.${rawBody}`) 17 .digest('hex'); 18 19 const expectedBuf = Buffer.from(expected, 'hex'); 20 const receivedBuf = Buffer.from(v1, 'hex'); 21 if (expectedBuf.length !== receivedBuf.length) return false; 22 return crypto.timingSafeEqual(expectedBuf, receivedBuf); 23} 24 25// Express handler example 26app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => { 27 const sig = req.headers['x-screenshotfree-signature'] as string ?? ''; 28 if (!verifyWebhook(req.body, sig, process.env.WEBHOOK_SECRET!)) { 29 return res.status(401).json({ error: 'Invalid signature' }); 30 } 31 const event = JSON.parse(req.body.toString()); 32 if (event.event === 'job.completed') { 33 // resultUrl is a relative path — fetch it against the API base URL 34 const result = await fetch(`https://api.screenshotfreeapi.com${event.resultUrl}`, { 35 headers: { Authorization: `Bearer ${process.env.SCREENSHOTFREEAPI_KEY}` }, 36 }).then((r) => r.json()); 37 console.log('Screenshot ready:', result.screenshots[0].url); 38 } 39 res.sendStatus(200); 40});