Génération de captures d'écran de sites web à la demande avec l'API ScreenshotMachine via Webhooks

Intermédiaire

Ceci est unBuilding Blocksworkflow d'automatisation du domainecontenant 12 nœuds.Utilise principalement des nœuds comme If, Code, Webhook, HttpRequest, RespondToWebhook. Générer des captures d'écran de sites web à la demande avec l'API ScreenshotMachine via Webhooks

Prérequis
  • Point de terminaison HTTP Webhook (généré automatiquement par n8n)
  • Peut nécessiter les informations d'identification d'authentification de l'API cible
Aperçu du workflow
Visualisation des connexions entre les nœuds, avec support du zoom et du déplacement
Exporter le workflow
Copiez la configuration JSON suivante dans n8n pour importer et utiliser ce workflow
{
  "id": "cXnFp41w8eVCH9tx",
  "meta": {
    "instanceId": "1777696fb9fddfee653e70940936c2b1e28ba1f1bde53b7182fbd6eb01988706",
    "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 : Validation et sécurité de l'URL",
      "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 : Appel Screenshot API (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 : Réponse Webhook",
      "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 : Résolution d'URL (Requête HEAD)",
      "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": "Recevoir l'URL Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        1560,
        1920
      ],
      "webhookId": "caf8f5dc-4834-45bb-96b0-d4b508f93e1b",
      "parameters": {
        "path": "caf8f5dc-4834-45bb-96b0-d4b508f93e1b",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "1abb0d7c-9064-4e75-89be-cc7c409ecb84",
      "name": "Résoudre l'URL (Requête HEAD)",
      "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": "Valider l'URL contre le 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": "SI URL valide",
      "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": "Prendre une capture d'écran",
      "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": "Répondre avec les données de capture",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        3000,
        1920
      ],
      "parameters": {
        "options": {},
        "respondWith": "allIncomingItems"
      },
      "typeVersion": 1.2
    },
    {
      "id": "38161c96-327c-42af-ab14-5825f6f2aafd",
      "name": "Répondre avec l'erreur de validation",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        3000,
        2080
      ],
      "parameters": {
        "options": {},
        "respondWith": "text",
        "responseBody": "={{ $json.error.  }}"
      },
      "typeVersion": 1.2
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "e3b7d5c4-f779-4012-9bd1-91135725e0ac",
  "connections": {
    "922a97b7-1df7-40f6-836e-b8ade7a48176": {
      "main": [
        [
          {
            "node": "627362d9-f789-4810-9e2b-5678ee5ac209",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "38161c96-327c-42af-ab14-5825f6f2aafd",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "627362d9-f789-4810-9e2b-5678ee5ac209": {
      "main": [
        [
          {
            "node": "68c3d952-de02-4de1-bd4e-363a28e1ab05",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "86316119-a064-4052-a27c-07c957c71802": {
      "main": [
        [
          {
            "node": "1abb0d7c-9064-4e75-89be-cc7c409ecb84",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1a9218c1-a132-4109-88da-0ac24236de34": {
      "main": [
        [
          {
            "node": "922a97b7-1df7-40f6-836e-b8ade7a48176",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1abb0d7c-9064-4e75-89be-cc7c409ecb84": {
      "main": [
        [
          {
            "node": "1a9218c1-a132-4109-88da-0ac24236de34",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "922a97b7-1df7-40f6-836e-b8ade7a48176",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Foire aux questions

Comment utiliser ce workflow ?

Copiez le code de configuration JSON ci-dessus, créez un nouveau workflow dans votre instance n8n et sélectionnez "Importer depuis le JSON", collez la configuration et modifiez les paramètres d'authentification selon vos besoins.

Dans quelles scénarios ce workflow est-il adapté ?

Intermédiaire - Blocs de construction

Est-ce payant ?

Ce workflow est entièrement gratuit et peut être utilisé directement. Veuillez noter que les services tiers utilisés dans le workflow (comme l'API OpenAI) peuvent nécessiter un paiement de votre part.

Informations sur le workflow
Niveau de difficulté
Intermédiaire
Nombre de nœuds12
Catégorie1
Types de nœuds6
Description de la difficulté

Adapté aux utilisateurs expérimentés, avec des workflows de complexité moyenne contenant 6-15 nœuds

Auteur
ist00dent

ist00dent

@ist00dent

I’m a dedicated automation engineer passionate about no-code and low-code solutions. I design and implement robust n8n workflows—integrating APIs, databases, and messaging—to eliminate manual tasks and accelerate delivery. Leveraging Python and C#, I build scalable, adaptable automations that empower teams to focus on high-value work.

Liens externes
Voir sur n8n.io

Partager ce workflow

Catégories

Catégories: 34