AutomationFlowsWeb Scraping › Generate Website Screenshots On-demand with Screenshotmachine API via Webhooks

Generate Website Screenshots On-demand with Screenshotmachine API via Webhooks

Byist00dent @ist00dent on n8n.io

This n8n template enables you to instantly generate high-quality screenshots of any specified public URL by simply sending a webhook request. It’s an indispensable tool for developers, content creators, marketers, or anyone needing on-demand visual captures of web pages without…

Webhook trigger★★★★☆ complexity12 nodesHTTP Request
Web Scraping Trigger: Webhook Nodes: 12 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #4594 — we link there as the canonical source.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "id": "cXnFp41w8eVCH9tx",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Generate Website Screenshots On-Demand with ScreenshotMachine API via Webhooks",
  "tags": [],
  "nodes": [
    {
      "id": "57d3821e-cba4-47fc-b92a-c1dd2e6337ca",
      "name": "Note: Webhook Input",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1500,
        1740
      ],
      "parameters": {
        "height": 340,
        "content": "This node listens for incoming POST requests. It expects a JSON body with a 'url' property (the website URL you want to screenshot)."
      },
      "typeVersion": 1
    },
    {
      "id": "6b55c36a-1e8f-4609-af02-e09347b1f06e",
      "name": "Note: URL Validation & Security",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2060,
        1700
      ],
      "parameters": {
        "color": 3,
        "width": 400,
        "height": 360,
        "content": "This crucial node validates the incoming 'url' to prevent Server-Side Request Forgery (SSRF) vulnerabilities. It checks for valid HTTP/HTTPS protocols and ensures the URL does not point to internal/private IP addresses or localhost.\n\nSince 'URL' object is unavailable, it uses string-based parsing and relies on the preceding HEAD request for initial URL resolution and connectivity check."
      },
      "typeVersion": 1
    },
    {
      "id": "4d67e039-3198-4ff1-b72b-94dccf3125df",
      "name": "Note: Screenshot API Call (GET)",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2500,
        1660
      ],
      "parameters": {
        "color": 4,
        "width": 340,
        "height": 320,
        "content": "This node makes an HTTP GET request to the ScreenshotMachine API using the validated URL. Remember to replace 'YOUR_API_KEY' in the URL parameter with your actual API key.\n\nThis method is critical: ScreenshotMachine typically uses GET for this type of request."
      },
      "typeVersion": 1
    },
    {
      "id": "296d8f30-1015-4070-acc0-7e1b2e3cbac8",
      "name": "Note: Webhook Response",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2880,
        1780
      ],
      "parameters": {
        "color": 5,
        "width": 340,
        "height": 480,
        "content": "1. If the URL is invalid or blocked by security checks, it sends a clear error message.\n2. If the screenshot is successful, it sends the data received from ScreenshotMachine API back to the original webhook caller."
      },
      "typeVersion": 1
    },
    {
      "id": "548687ec-3af4-4d2b-a8ac-93eecaabcb6c",
      "name": "Note: Resolve URL (HEAD Request)",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1780,
        1820
      ],
      "parameters": {
        "color": 2,
        "height": 340,
        "content": "This node performs a HEAD request to the incoming URL.\n\nIt checks if the URL is reachable and resolves any redirects. This acts as an initial connectivity and basic validity check before more granular SSRF validation."
      },
      "typeVersion": 1
    },
    {
      "id": "86316119-a064-4052-a27c-07c957c71802",
      "name": "Receive URL Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        1560,
        1920
      ],
      "parameters": {
        "path": "caf8f5dc-4834-45bb-96b0-d4b508f93e1b",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "1abb0d7c-9064-4e75-89be-cc7c409ecb84",
      "name": "Resolve URL (HEAD Request)",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueErrorOutput",
      "position": [
        1840,
        2000
      ],
      "parameters": {
        "url": "={{$json.body.url}}",
        "method": "HEAD",
        "options": {}
      },
      "typeVersion": 4.2
    },
    {
      "id": "1a9218c1-a132-4109-88da-0ac24236de34",
      "name": "Validate URL for SSRF",
      "type": "n8n-nodes-base.code",
      "position": [
        2120,
        1900
      ],
      "parameters": {
        "jsCode": "for (const item of $input.all()) {\n  let isValidUrl = false; // Start with false, will be set to true if passes checks\n  let errorMessage = '';\n  let finalUrl = null; // This will store the validated/resolved URL\n\n  const headResponse = item.json; // Assuming the HTTP Request node outputs directly to item.json\n\n  // Check if the HEAD request was successful (status 2xx or 3xx for redirects)\n  const statusCode = headResponse.statusCode;\n  console.log('DEBUG: HEAD Request Status Code:', statusCode);\n  console.log('DEBUG: HEAD Request Headers:', JSON.stringify(headResponse.headers));\n\n  if (statusCode >= 200 && statusCode < 400) {\n    // Get the final URL after redirects, or original URL if no redirect\n    let resolvedUrlString = headResponse.headers?.['x-final-url'] || headResponse.url || headResponse.request?.url;\n\n    console.log('DEBUG: Resolved URL string from HEAD request:', resolvedUrlString);\n\n    if (resolvedUrlString) {\n        finalUrl = resolvedUrlString;\n\n        // Manual parsing of protocol and hostname from resolvedUrlString\n        let protocol = '';\n        let hostname = '';\n\n        const protocolMatch = finalUrl.match(/^(\\w+):\\/\\//);\n        if (protocolMatch && protocolMatch[1]) {\n            protocol = protocolMatch[1];\n            // Remove protocol and leading slashes for hostname extraction\n            let tempUrl = finalUrl.substring(protocol.length + 3); // e.g., \"https://www.example.com/path\" -> \"www.example.com/path\"\n            const slashIndex = tempUrl.indexOf('/');\n            if (slashIndex !== -1) {\n                hostname = tempUrl.substring(0, slashIndex); // e.g., \"www.example.com\"\n            } else {\n                hostname = tempUrl; // e.g., \"www.example.com\" (if no path)\n            }\n        }\n        console.log('DEBUG: Manually parsed Protocol:', protocol, 'Hostname:', hostname);\n\n        // Basic protocol check\n        if (protocol === 'http' || protocol === 'https') {\n            isValidUrl = true; // Protocol is good so far\n        } else {\n            isValidUrl = false;\n            errorMessage = 'Only HTTP or HTTPS protocols are allowed.';\n            console.log('ERROR: Protocol check failed. Detected protocol:', protocol);\n        }\n\n        // Check for private IPs or localhost to prevent SSRF (only if valid so far)\n        if (isValidUrl && hostname) { // Ensure hostname was extracted\n            if (hostname === 'localhost' || hostname === '127.0.0.1') {\n                isValidUrl = false;\n                errorMessage = 'Access to localhost is not allowed for security reasons.';\n                console.log('ERROR: Localhost or 127.0.0.1 detected.');\n            } else {\n                const parts = hostname.split('.');\n                // Check if it's an IPv4 address (4 parts) and all parts are numbers\n                if (parts.length === 4 && parts.every(part => !isNaN(parseInt(part)) && isFinite(part) && parseInt(part) >= 0 && parseInt(part) <= 255)) {\n                    const ip = parts.map(Number);\n                    if (ip[0] === 10 || (ip[0] === 172 && ip[1] >= 16 && ip[1] <= 31) || (ip[0] === 192 && ip[1] === 168)) {\n                        isValidUrl = false;\n                        errorMessage = 'Access to private IP addresses is not allowed for security reasons.';\n                        console.log('ERROR: Private IPv4 address detected:', ip);\n                    }\n                }\n            }\n        } else if (!hostname) { // If hostname couldn't be extracted, it's not valid\n             isValidUrl = false;\n             errorMessage = 'Could not determine hostname from URL.';\n             console.log('ERROR: Hostname could not be extracted.');\n        }\n\n    } else { // No resolved URL string from HEAD request\n        isValidUrl = false;\n        errorMessage = 'Failed to get a resolved URL string from HEAD request response.';\n        console.log('ERROR: No resolved URL string found in HEAD request output.');\n    }\n  } else { // HEAD request returned non-2xx/3xx status\n    isValidUrl = false;\n    errorMessage = `URL is unreachable or returned status code ${statusCode || 'unknown'}.`;\n    console.log(`ERROR: HEAD request failed with status code: ${statusCode}`);\n  }\n\n  // Add validation status to the item's JSON\n  item.json.isValidUrl = isValidUrl;\n  item.json.errorMessage = errorMessage;\n  item.json.validatedUrl = isValidUrl ? finalUrl : null;\n  item.json.statusCode = statusCode; // Add status code for debug from HEAD request\n  console.log('DEBUG: Final isValidUrl for this item:', isValidUrl, 'Final errorMessage:', errorMessage);\n}\n\nreturn $input.all();"
      },
      "typeVersion": 2
    },
    {
      "id": "922a97b7-1df7-40f6-836e-b8ade7a48176",
      "name": "IF URL Valid",
      "type": "n8n-nodes-base.if",
      "position": [
        2380,
        1980
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "9c45d5bc-62c0-487c-a4ad-e2409785cf94",
              "operator": {
                "type": "string",
                "operation": "notExists",
                "singleValue": true
              },
              "leftValue": "={{ $json.errorMessage ?? $json.error}}",
              "rightValue": ""
            }
          ]
        },
        "looseTypeValidation": true
      },
      "typeVersion": 2.2
    },
    {
      "id": "627362d9-f789-4810-9e2b-5678ee5ac209",
      "name": "Take Screenshot",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2620,
        1840
      ],
      "parameters": {
        "url": "=https://api.screenshotmachine.com?key=YOUR_API_KEY&url={{$json.validatedUrl}}",
        "options": {}
      },
      "typeVersion": 4.2
    },
    {
      "id": "68c3d952-de02-4de1-bd4e-363a28e1ab05",
      "name": "Respond with Screenshot Data",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        3000,
        1920
      ],
      "parameters": {
        "options": {},
        "respondWith": "allIncomingItems"
      },
      "typeVersion": 1.2
    },
    {
      "id": "38161c96-327c-42af-ab14-5825f6f2aafd",
      "name": "Respond with Validation Error",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        3000,
        2080
      ],
      "parameters": {
        "options": {},
        "respondWith": "text",
        "responseBody": "={{ $json.error.  }}"
      },
      "typeVersion": 1.2
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "e3b7d5c4-f779-4012-9bd1-91135725e0ac",
  "connections": {
    "IF URL Valid": {
      "main": [
        [
          {
            "node": "Take Screenshot",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond with Validation Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Take Screenshot": {
      "main": [
        [
          {
            "node": "Respond with Screenshot Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Receive URL Webhook": {
      "main": [
        [
          {
            "node": "Resolve URL (HEAD Request)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate URL for SSRF": {
      "main": [
        [
          {
            "node": "IF URL Valid",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Resolve URL (HEAD Request)": {
      "main": [
        [
          {
            "node": "Validate URL for SSRF",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "IF URL Valid",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This n8n template enables you to instantly generate high-quality screenshots of any specified public URL by simply sending a webhook request. It’s an indispensable tool for developers, content creators, marketers, or anyone needing on-demand visual captures of web pages without…

Source: https://n8n.io/workflows/4594/ — original creator credit. Request a take-down →

More Web Scraping workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Web Scraping

This n8n template provides enterprise-level version control for your workflows using GitHub integration. Stop losing hours to broken workflows and manual exports – get proper commit history, visual di

n8n, Execute Workflow Trigger, HTTP Request +1
Web Scraping

This flow creates dummy files for every item added in your *Arrs (Radarr/Sonarr) with the tag .

HTTP Request, Ssh
Web Scraping

This workflow acts as a central API gateway for all technical indicator agents in the Binance Spot Market Quant AI system. It listens for incoming webhook requests and dynamically routes them to the c

HTTP Request
Web Scraping

Sign PDF documents with legally-compliant digital signatures using X.509 certificates. Supports multiple PAdES signature levels (B, T, LT, LTA) with optional visible stamps.

Execute Command, HTTP Request, Read Write File +1
Web Scraping

📡 This workflow serves as the central Alpha Vantage API fetcher for Tesla trading indicators, delivering cleaned 20-point JSON outputs for three timeframes: , , and . It is required by the following a

HTTP Request