This workflow corresponds to n8n.io template #13448 — we link there as the canonical source.
This workflow follows the Google Sheets → HTTP Request recipe pattern — see all workflows that pair these two integrations.
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 →
{
"id": "ETHbjTJvq3p1U2k1b2z4W",
"name": "Phishing URL Reputation Checker",
"tags": [],
"nodes": [
{
"id": "9f276b8f-a5f7-4ee9-9bdc-5de4df3071e1",
"name": "Webhook - Submit URL for Analysis",
"type": "n8n-nodes-base.webhook",
"position": [
-816,
496
],
"parameters": {
"path": "phishing-check",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 2.1
},
{
"id": "d0dda112-56e7-42e5-82f6-50d074ca4523",
"name": "Normalize Input URL",
"type": "n8n-nodes-base.code",
"position": [
-624,
496
],
"parameters": {
"jsCode": "let rawUrl = $input.first().json.body.url;\n\n// Ensure string\nrawUrl = typeof rawUrl === 'string' ? rawUrl : '';\nrawUrl = rawUrl.trim();\n\nif (!rawUrl) {\n return [{\n original_url: rawUrl,\n normalized_url: \"\",\n is_valid: false\n }];\n}\n\nlet normalizedUrl = rawUrl;\n\n// Add scheme if missing\nif (!/^[a-zA-Z]+:\\/\\//.test(normalizedUrl)) {\n normalizedUrl = 'http://' + normalizedUrl;\n}\n\n// Lightweight validation without URL class or regex rules\nlet isValid = false;\n\ntry {\n // This trick works in restricted sandbox\n const parts = normalizedUrl.split('://');\n\n if (parts.length === 2) {\n const protocol = parts[0].toLowerCase();\n const hostPart = parts[1].split('/')[0];\n\n if (\n (protocol === 'http' || protocol === 'https') &&\n hostPart.length > 0 &&\n hostPart.includes('.')\n ) {\n isValid = true;\n }\n }\n\n} catch (err) {\n isValid = false;\n}\n\nreturn [{\n original_url: rawUrl,\n normalized_url: normalizedUrl,\n is_valid: isValid\n}];\n\n\n\n\n"
},
"typeVersion": 2
},
{
"id": "be6503df-d23c-4eda-98ad-ad6112faec0d",
"name": "IF - URL is Valid?",
"type": "n8n-nodes-base.if",
"position": [
-384,
496
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "96418dd7-c3d2-4384-b3b2-fd4645e0b977",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.is_valid }}",
"rightValue": false
}
]
}
},
"typeVersion": 2.3
},
{
"id": "35b3ab91-a3c1-460c-a5e2-ada7bbe610c3",
"name": "Respond - Invalid URL error",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
-368,
720
],
"parameters": {
"options": {},
"respondWith": "json",
"responseBody": "{\n \"error\": \"Invalid or malformed URL\",\n \"message\": \"Please submit a valid URL\"\n}\n"
},
"typeVersion": 1.5
},
{
"id": "1e01c712-1adb-4f25-9f19-f87dadae2594",
"name": "VirusTotal - Submit URL for Scan",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueErrorOutput",
"position": [
-64,
480
],
"parameters": {
"url": "https://www.virustotal.com/api/v3/urls",
"method": "POST",
"options": {},
"sendBody": true,
"contentType": "form-urlencoded",
"sendHeaders": true,
"authentication": "genericCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "url",
"value": "={{ $json.normalized_url }}"
}
]
},
"genericAuthType": "httpHeaderAuth",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/x-www-form-urlencoded"
}
]
}
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.3
},
{
"id": "b62c79d8-e809-48ed-a83a-abc98038b1df",
"name": "Wait - VirusTotal Scan Processing",
"type": "n8n-nodes-base.wait",
"position": [
256,
480
],
"parameters": {
"amount": 10
},
"typeVersion": 1.1
},
{
"id": "c0f96217-dd5d-4cab-a4b2-353644158989",
"name": "VirusTotal - Get Scan Analysis",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueErrorOutput",
"position": [
432,
480
],
"parameters": {
"url": "=https://www.virustotal.com/api/v3/analyses/{{ $json.data.id }}",
"options": {},
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.3
},
{
"id": "7dbe02af-08e2-4b45-83c2-f2b93d4e8d9d",
"name": "IF - VirusTotal Analysis Completed?",
"type": "n8n-nodes-base.if",
"position": [
688,
512
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "8dcc6478-58cb-4fd9-93c9-a2bc02011889",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.data.attributes.status }}",
"rightValue": "completed"
}
]
}
},
"typeVersion": 2.3
},
{
"id": "63c7b3f7-4730-452b-92d2-42c94592478e",
"name": "Wait - Retry VT Analysis Poll",
"type": "n8n-nodes-base.wait",
"position": [
256,
704
],
"parameters": {
"amount": 15
},
"typeVersion": 1.1
},
{
"id": "5a7f0141-9a96-469f-83fa-ccb2c998dfb1",
"name": "Extract VirusTotal Verdict Stats",
"type": "n8n-nodes-base.code",
"position": [
960,
496
],
"parameters": {
"jsCode": "const stats = $json.data.attributes.stats;\nconst url_info = $json.meta.url_info;\n\nreturn [{\n vt_malicious: stats.malicious || 0,\n vt_suspicious: stats.suspicious || 0,\n vt_harmless: stats.harmless || 0,\n vt_undetected: stats.undetected || 0,\n vt_status: $json.data.attributes.status,\n url: url_info.url\n}];\n"
},
"typeVersion": 2
},
{
"id": "2c15f50f-9139-4760-8a52-4f43c4b190c4",
"name": "Build Phishing Verdict",
"type": "n8n-nodes-base.code",
"position": [
1216,
496
],
"parameters": {
"jsCode": "let risk = \"Low\";\nlet verdict = \"SAFE\";\n\nconst malicious = $json.vt_malicious || 0;\nconst suspicious = $json.vt_suspicious || 0;\nlet url = $json.url || \"\";\n\n// High confidence phishing\nif (malicious >= 3) {\n risk = \"High\";\n verdict = \"PHISHING\";\n}\n\n// Medium confidence suspicious (few malicious engines)\nelse if (malicious >= 1 && malicious <= 2) {\n risk = \"Medium\";\n verdict = \"SUSPICIOUS\";\n}\n\n// Medium confidence suspicious (multiple suspicious engines)\nelse if (suspicious >= 3) {\n risk = \"Medium\";\n verdict = \"SUSPICIOUS\";\n}\n\n// Defang the URL if verdict is NOT SAFE\nif (verdict !== \"SAFE\") {\n // Simple defanging: replace . with [.] and hxxp:// instead of http\n url = url.replace(/^http:\\/\\//i, \"hxxp://\")\n .replace(/^https:\\/\\//i, \"hxxps://\")\n .replace(/\\./g, \"[.]\");\n}\n\nreturn [{\n url,\n verdict,\n risk_level: risk, \n engines: {\n virustotal: {\n malicious,\n suspicious\n }\n }\n}];\n"
},
"typeVersion": 2
},
{
"id": "7f4b09de-1bd0-4416-a086-59a108f3ffc3",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-880,
304
],
"parameters": {
"color": 7,
"width": 384,
"height": 592,
"content": "## URL Input & Normalization\nAccepts user URLs via webhook and ensures consistent formatting before security analysis.\n"
},
"typeVersion": 1
},
{
"id": "37658244-61f2-426f-8043-5517a224039c",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-480,
304
],
"parameters": {
"color": 7,
"width": 320,
"height": 592,
"content": "## Validation\nChecks for malformed or missing URLs and returns an error if validation fails.\n\n"
},
"typeVersion": 1
},
{
"id": "c9f716ab-354f-4d5d-93cf-eb72d636bba4",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-144,
304
],
"parameters": {
"color": 7,
"width": 304,
"height": 592,
"content": "## Threat Intelligence Submission\nSubmits validated URLs to VirusTotal for multi-engine reputation scanning.\n"
},
"typeVersion": 1
},
{
"id": "d8672204-2303-4825-8326-f22944603ff2",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
176,
304
],
"parameters": {
"color": 7,
"width": 688,
"height": 1008,
"content": "## Asynchronous Scan Handling\nPolls VirusTotal until the analysis is completed or retries reach the limit.\n"
},
"typeVersion": 1
},
{
"id": "fcebb1a2-d6e0-445d-9664-27de29b5dec8",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
880,
336
],
"parameters": {
"color": 7,
"height": 384,
"content": "## Detection Signal Extraction\nExtracts VirusTotal detection statistics used for phishing classification.\n"
},
"typeVersion": 1
},
{
"id": "60432bcf-0edc-4014-9651-6f7cd3fdc199",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
1136,
336
],
"parameters": {
"color": 7,
"width": 256,
"height": 384,
"content": "## Phishing Decision Engine\nApplies threshold logic to classify URLs as SAFE, SUSPICIOUS, or PHISHING.\n"
},
"typeVersion": 1
},
{
"id": "6dd7cbd5-43c1-4a28-b0b2-331fc49900ec",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
1408,
336
],
"parameters": {
"color": 7,
"width": 256,
"height": 384,
"content": "## Logging & Output\nStores scan results in Google Sheets for monitoring and incident tracking.\n\n "
},
"typeVersion": 1
},
{
"id": "c015ee89-b8fc-4853-8aaa-54cd4c779279",
"name": "Respond - VT Service Error",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
800,
64
],
"parameters": {
"options": {},
"respondWith": "json",
"responseBody": "{\n \"error\": \"Threat intelligence service unavailable\",\n \"message\": \"VirusTotal request failed. Please try again later.\"\n}"
},
"typeVersion": 1.5
},
{
"id": "c6e341ff-f9bb-4b4c-a9e3-42537fb81edd",
"name": "IF - VT Analysis Error?",
"type": "n8n-nodes-base.if",
"position": [
576,
144
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "a5b806df-b3ce-4743-b2cb-3f297924c14d",
"operator": {
"type": "string",
"operation": "exists",
"singleValue": true
},
"leftValue": "={{$json.error}}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.3
},
{
"id": "e9269973-2a40-42fc-bfac-77a571540389",
"name": "IF - VT Submit Error?",
"type": "n8n-nodes-base.if",
"position": [
112,
128
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "59a51a9b-ffbf-46cf-b7ba-b97ebbb0791e",
"operator": {
"type": "string",
"operation": "exists",
"singleValue": true
},
"leftValue": "={{$json.error}}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.3
},
{
"id": "1ddd4032-8b2d-4033-9c7c-dcddf4079352",
"name": "Log Scan Result",
"type": "n8n-nodes-base.googleSheets",
"position": [
1488,
496
],
"parameters": {
"columns": {
"value": {
"verdict": "={{ $json.verdict }}",
"malicious": "={{ $json.engines.virustotal.malicious }}",
"timestamp": "={{ new Date().toISOString() }}",
"risk_level": "={{ $json.risk_level }}",
"suspicious": "={{ $json.engines.virustotal.suspicious }}",
"original_url": "={{ $json.url }}"
},
"schema": [
{
"id": "timestamp",
"type": "string",
"display": true,
"required": false,
"displayName": "timestamp",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "original_url",
"type": "string",
"display": true,
"required": false,
"displayName": "original_url",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "verdict",
"type": "string",
"display": true,
"required": false,
"displayName": "verdict",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "risk_level",
"type": "string",
"display": true,
"required": false,
"displayName": "risk_level",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "malicious",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "malicious",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "suspicious",
"type": "string",
"display": true,
"required": false,
"displayName": "suspicious",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/14qydoIHflwd-3gYyj7g0QKH5Bfsny_OiLzdnvEKOwfo/edit#gid=0",
"cachedResultName": "Phishing URL scan"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "14qydoIHflwd-3gYyj7g0QKH5Bfsny_OiLzdnvEKOwfo",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/14qydoIHflwd-3gYyj7g0QKH5Bfsny_OiLzdnvEKOwfo/edit?usp=drivesdk",
"cachedResultName": "Phishing URL"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "b6e9da2e-27a0-400a-8bce-d296a9a61752",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
32,
-16
],
"parameters": {
"color": 7,
"width": 944,
"height": 304,
"content": "## Error Handling & Resilience\nHandles VirusTotal API failures, validation errors, and timeout conditions to ensure reliable execution.\n"
},
"typeVersion": 1
},
{
"id": "162e190f-6e38-464d-9350-7fad93cf93c5",
"name": "Increment Retry Counter",
"type": "n8n-nodes-base.code",
"position": [
672,
832
],
"parameters": {
"jsCode": "const retry = $json.retry_count || 0;\nconst newRetry = retry + 1;\n\nreturn [{\n json: {\n ...$json,\n retry_count: newRetry\n }\n}];\n\n"
},
"typeVersion": 2
},
{
"id": "6d76ed8e-d24d-4b30-9ce4-39c628ee0558",
"name": "IF Max Retry Reached?",
"type": "n8n-nodes-base.if",
"position": [
448,
1088
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "a864ef76-3d9a-4909-b6d4-f3d7d5144b84",
"operator": {
"type": "number",
"operation": "gte"
},
"leftValue": "={{ $json.retry_count }}",
"rightValue": 5
}
]
}
},
"typeVersion": 2.3
},
{
"id": "344723b0-aa1b-4584-b1c7-d5d26cb4f719",
"name": "Respond Timeout",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
688,
1072
],
"parameters": {
"options": {},
"respondWith": "json",
"responseBody": "{\n \"status\": \"timeout\",\n \"reason\": \"VirusTotal analysis not ready after max retries\",\n \"url\": \"={{$json.url}}\",\n \"retry_count\": \"={{$json.retry_count}}\"\n}\n"
},
"typeVersion": 1.5
},
{
"id": "e0d9f480-2c11-4567-980c-1602d8c61198",
"name": "Sticky Note8",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1472,
240
],
"parameters": {
"width": 560,
"height": 704,
"content": "## Phishing URL Reputation Checker\nThis workflow analyzes submitted URLs to determine whether they are phishing or malicious using VirusTotal\u2019s threat intelligence data. It validates user input, submits the URL for scanning, polls for results, extracts detection signals, and generates a clear phishing verdict with risk scoring. Results are optionally logged to Google Sheets for tracking and investigation.\n\n### How it works\n1. A webhook accepts a URL from an API, form, chatbot, or automation.\n2. The URL is normalized and validated to prevent malformed or unsafe input.\n3. Valid URLs are submitted to VirusTotal for multi-engine reputation analysis.\n4. The workflow polls VirusTotal asynchronously until the scan is complete or retries are exhausted.\n5. Detection statistics are extracted and evaluated using threshold-based phishing logic.\n6. Suspicious or malicious URLs are defanged to prevent accidental clicks.\n7. The final verdict and risk level are returned and optionally logged to Google Sheets.\n\n### Setup steps\n1. Add your VirusTotal API key in the HTTP Header Auth credentials.\n2. Connect Google Sheets to store scan results.\n3. Trigger the webhook with { \"url\": \"example.com\" }.\n\n### Customization\n1. Adjust phishing thresholds in the \u201cBuild Phishing Verdict\u201d node or add additional reputation sources for stronger detection.\n2. You can add a Slack, Discord, or email notification when the verdict is not SAFE to alert security teams about potential phishing URLs in real time."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"availableInMCP": false,
"executionOrder": "v1"
},
"versionId": "80bb187f-9bb0-43ac-beba-67c32c7bced0",
"connections": {
"Log Scan Result": {
"main": [
[]
]
},
"IF - URL is Valid?": {
"main": [
[
{
"node": "VirusTotal - Submit URL for Scan",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond - Invalid URL error",
"type": "main",
"index": 0
}
]
]
},
"Normalize Input URL": {
"main": [
[
{
"node": "IF - URL is Valid?",
"type": "main",
"index": 0
}
]
]
},
"IF - VT Submit Error?": {
"main": [
[
{
"node": "Respond - VT Service Error",
"type": "main",
"index": 0
}
],
[
{
"node": "Wait - VirusTotal Scan Processing",
"type": "main",
"index": 0
}
]
]
},
"IF Max Retry Reached?": {
"main": [
[
{
"node": "Respond Timeout",
"type": "main",
"index": 0
}
],
[
{
"node": "Wait - Retry VT Analysis Poll",
"type": "main",
"index": 0
}
]
]
},
"Build Phishing Verdict": {
"main": [
[
{
"node": "Log Scan Result",
"type": "main",
"index": 0
}
]
]
},
"IF - VT Analysis Error?": {
"main": [
[
{
"node": "Respond - VT Service Error",
"type": "main",
"index": 0
}
],
[
{
"node": "IF - VirusTotal Analysis Completed?",
"type": "main",
"index": 0
}
]
]
},
"Increment Retry Counter": {
"main": [
[
{
"node": "IF Max Retry Reached?",
"type": "main",
"index": 0
}
]
]
},
"Wait - Retry VT Analysis Poll": {
"main": [
[
{
"node": "VirusTotal - Get Scan Analysis",
"type": "main",
"index": 0
}
]
]
},
"VirusTotal - Get Scan Analysis": {
"main": [
[
{
"node": "IF - VT Analysis Error?",
"type": "main",
"index": 0
}
]
]
},
"Extract VirusTotal Verdict Stats": {
"main": [
[
{
"node": "Build Phishing Verdict",
"type": "main",
"index": 0
}
]
]
},
"VirusTotal - Submit URL for Scan": {
"main": [
[
{
"node": "IF - VT Submit Error?",
"type": "main",
"index": 0
}
]
]
},
"Wait - VirusTotal Scan Processing": {
"main": [
[
{
"node": "VirusTotal - Get Scan Analysis",
"type": "main",
"index": 0
}
]
]
},
"Webhook - Submit URL for Analysis": {
"main": [
[
{
"node": "Normalize Input URL",
"type": "main",
"index": 0
}
]
]
},
"IF - VirusTotal Analysis Completed?": {
"main": [
[
{
"node": "Extract VirusTotal Verdict Stats",
"type": "main",
"index": 0
}
],
[
{
"node": "Increment Retry Counter",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
googleSheetsOAuth2ApihttpHeaderAuth
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This n8n template helps you automatically analyze URLs for phishing and malicious activity using VirusTotal’s multi-engine threat intelligence platform. It validates incoming URLs, submits them for scanning, polls for results, classifies risk, and logs verdicts for monitoring…
Source: https://n8n.io/workflows/13448/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
[SANTOBET] FLUXO TODO - BACKUP. Uses googleSheets, httpRequest, googleSheetsTrigger. Webhook trigger; 57 nodes.
FLUXO DISPARO DATA E HORA. Uses itemLists, googleSheets, httpRequest. Webhook trigger; 48 nodes.
This workflow allows you to accept online payments via YooKassa and log both orders and transactions in Google Sheets — all without writing a single line of code. It supports full payment flow: produc
Transform your n8n instance management with this advanced automation system featuring artificial intelligence-driven workflow selection. This template provides comprehensive maintenance operations wit
Nexus_v6(ล่าสุดจริงๆ)ล่าสุดไกไก. Uses googleSheets, httpRequest. Webhook trigger; 41 nodes.