웹훅을 통해 ScreenshotMachine API를 사용하여 필요에 따라 웹사이트 스크린샷 생성
중급
이것은Building Blocks분야의자동화 워크플로우로, 12개의 노드를 포함합니다.주로 If, Code, Webhook, HttpRequest, RespondToWebhook 등의 노드를 사용하며. 웹훅을 통해 ScreenshotMachine API를 사용하여 필요에 따라 웹사이트 스크린샷 생성
사전 요구사항
- •HTTP Webhook 엔드포인트(n8n이 자동으로 생성)
- •대상 API의 인증 정보가 필요할 수 있음
카테고리
워크플로우 미리보기
노드 연결 관계를 시각적으로 표시하며, 확대/축소 및 이동을 지원합니다
워크플로우 내보내기
다음 JSON 구성을 복사하여 n8n에 가져오면 이 워크플로우를 사용할 수 있습니다
{
"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": "노트: Webhook 입력",
"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": "노트: 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": "노트: 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": "노트: 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": "노트: URL 확인 (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": "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": "URL 확인 (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": "SSRF 대응 URL 유효성 검사",
"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 유효함",
"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": "스크린샷 촬영",
"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": "스크린샷 데이터로 응답",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
3000,
1920
],
"parameters": {
"options": {},
"respondWith": "allIncomingItems"
},
"typeVersion": 1.2
},
{
"id": "38161c96-327c-42af-ab14-5825f6f2aafd",
"name": "유효성 검사 오류로 응답",
"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
}
]
]
}
}
}자주 묻는 질문
이 워크플로우를 어떻게 사용하나요?
위의 JSON 구성 코드를 복사하여 n8n 인스턴스에서 새 워크플로우를 생성하고 "JSON에서 가져오기"를 선택한 후, 구성을 붙여넣고 필요에 따라 인증 설정을 수정하세요.
이 워크플로우는 어떤 시나리오에 적합한가요?
중급 - 빌딩 블록
유료인가요?
이 워크플로우는 완전히 무료이며 직접 가져와 사용할 수 있습니다. 다만, 워크플로우에서 사용하는 타사 서비스(예: OpenAI API)는 사용자 직접 비용을 지불해야 할 수 있습니다.
관련 워크플로우 추천
Webhook을 통해 콘텐츠 요약 생성기 (ApyHub)
Webhook을 통해 내용 요약 생성기 (ApyHub)
Webhook
Http Request
Respond To Webhook
+
Webhook
Http Request
Respond To Webhook
8 노드ist00dent
빌딩 블록
TimeZoneDB API를 통한 시간대 변환 통합
TimeZoneDB API를 통합하여 시간대를 변환합니다.
Webhook
Http Request
Respond To Webhook
+
Webhook
Http Request
Respond To Webhook
6 노드ist00dent
빌딩 블록
ExchangeRate.host를 통해 Webhook를 사용하여 화폐 변환
ExchangeRate.host를 통해 Webhook를 사용하여 화폐 변환
Webhook
Http Request
Respond To Webhook
+
Webhook
Http Request
Respond To Webhook
6 노드ist00dent
빌딩 블록
Webhook를 통해 JSON 문자열 검증
Webhook을 통해 JSON 문자열��인
Code
Webhook
Respond To Webhook
+
Code
Webhook
Respond To Webhook
6 노드ist00dent
빌딩 블록
Webhook을 통해 IP 지리적 위치 조사
IP-API.com을 통해 Webhook를 사용하여 IP 위치 정보 확인
Webhook
Http Request
Respond To Webhook
+
Webhook
Http Request
Respond To Webhook
6 노드ist00dent
빌딩 블록
day9_암호화폐 업데이트 수신
CoinGecko를 사용한 암호화폐 시장 분석: 변동성 지표 및 투자 신호
If
Set
Switch
+
If
Set
Switch
26 노드ist00dent
금융
워크플로우 정보
난이도
중급
노드 수12
카테고리1
노드 유형6
저자
ist00dent
@ist00dentI’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.
외부 링크
n8n.io에서 보기 →
이 워크플로우 공유