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 screenshotfreeapiInitialization
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});