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 →
{
"name": "ASM Investigator Enrichment",
"nodes": [
{
"parameters": {
"operation": "append",
"sheetId": "",
"range": "Output!A:ZZ",
"key": "shop_name"
},
"id": "GoogleSheets_Write_Output",
"name": "Write to Output",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 3
},
{
"parameters": {
"operation": "lookup",
"sheetId": "",
"range": "Input!A:F",
"lookupColumn": "processed",
"lookupValue": "FALSE",
"returnAllMatches": false
},
"id": "GoogleSheets_Read_Input",
"name": "Read next Input row",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 3
},
{
"parameters": {
"functionCode": "// Normalize fields and derive domain\nconst row = items[0].json;\nfunction norm(x){return (x||'').toString().trim();}\nconst website_url = norm(row.website_url);\nlet website_domain = '';\ntry {\n if (website_url) {\n website_domain = new URL(website_url).hostname.replace(/^www\\./,'');\n }\n} catch(e){}\nconst qBase = [norm(row.shop_name), norm(row.city), norm(row.state)].filter(Boolean).join(' ');\nreturn [{\n json: {\n ...row,\n website_domain,\n query_base: qBase,\n processed: 'FALSE'\n }\n}];\n"
},
"id": "Fn_Normalize",
"name": "Normalize + Domain",
"type": "n8n-nodes-base.function",
"typeVersion": 2
},
{
"parameters": {
"url": "https://maps.googleapis.com/maps/api/place/textsearch/json",
"options": {},
"queryParametersUi": {
"parameter": [
{
"name": "query",
"value": "{{$json[\"shop_name\"]}} {{$json[\"address\"]}} {{$json[\"city\"]}} {{$json[\"state\"]}} auto repair"
},
{
"name": "key",
"value": "={{$env.GOOGLE_PLACES_API_KEY}}"
}
]
}
},
"id": "HTTP_GPlaces_Search",
"name": "Google Places Search",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4
},
{
"parameters": {
"functionCode": "// Pick best place; pass place_id\nconst res = items[0].json;\nconst best = (res.results||[])[0] || {};\nreturn [{json:{place_id: best.place_id || '', place_name: best.name || ''}}];\n"
},
"id": "Fn_Pick_Place",
"name": "Pick Place",
"type": "n8n-nodes-base.function",
"typeVersion": 2
},
{
"parameters": {
"url": "https://maps.googleapis.com/maps/api/place/details/json",
"options": {},
"queryParametersUi": {
"parameter": [
{
"name": "place_id",
"value": "={{$json[\"place_id\"]}}"
},
{
"name": "fields",
"value": "name,formatted_phone_number,rating,user_ratings_total,website"
},
{
"name": "key",
"value": "={{$env.GOOGLE_PLACES_API_KEY}}"
}
]
}
},
"id": "HTTP_GPlaces_Details",
"name": "Google Places Details",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4
},
{
"parameters": {
"functionCode": "// Merge Places details\nconst d = items[0].json.result || {};\nreturn [{\n json: {\n gbp_place_id: d.place_id || '',\n gbp_name: d.name || '',\n gbp_phone: d.formatted_phone_number || '',\n gbp_rating: d.rating || '',\n gbp_user_ratings_total: d.user_ratings_total || '',\n website_url: d.website || $json.website_url || ''\n }\n}];\n"
},
"id": "Fn_Merge_Places",
"name": "Merge Places",
"type": "n8n-nodes-base.function",
"typeVersion": 2
},
{
"parameters": {
"functionCode": "// Build URLs to fetch (homepage + /contact)\nconst url = $json.website_url || '';\nif (!url) { return [{json: {...$json, site_urls: []}}]; }\nlet base = url;\ntry { base = new URL(url).origin; } catch(e){}\nreturn [{\n json: {\n ...$json,\n site_urls: [base, base + '/contact', base + '/about']\n }\n}];\n"
},
"id": "Fn_Build_Site_URLs",
"name": "Build Site URLs",
"type": "n8n-nodes-base.function",
"typeVersion": 2
},
{
"parameters": {
"url": "={{$json[\"site_urls\"][0]}}",
"options": {
"ignoreResponseCode": true
}
},
"id": "HTTP_Site_Fetch_1",
"name": "Fetch Site 1",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4
},
{
"parameters": {
"url": "={{$json[\"site_urls\"][1]}}",
"options": {
"ignoreResponseCode": true
}
},
"id": "HTTP_Site_Fetch_2",
"name": "Fetch Site 2",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4
},
{
"parameters": {
"url": "={{$json[\"site_urls\"][2]}}",
"options": {
"ignoreResponseCode": true
}
},
"id": "HTTP_Site_Fetch_3",
"name": "Fetch Site 3",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4
},
{
"parameters": {
"functionCode": "// Extract emails and candidate names from fetched HTML\nfunction extractEmails(text){\n const re = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}/ig;\n const m = text.match(re) || [];\n return Array.from(new Set(m));\n}\nfunction extractNames(text){\n // crude: look for patterns like \"Owner: John Smith\" or \"Manager: Jane Doe\"\n const re = /(Owner|Manager|Service Manager|Shop Owner)\\s*[:\\-]\\s*([A-Z][a-z]+\\s+[A-Z][a-z]+)/g;\n const out = []; let m;\n while ((m = re.exec(text)) !== null) out.push(m[2]);\n return Array.from(new Set(out));\n}\nconst htmls = items.map(i => (i.json.body || '') + ' ' + (i.json.data || ''));\nconst big = htmls.join(' ');\nreturn [{\n json: {\n site_found_emails: extractEmails(big).join(';'),\n site_found_names: extractNames(big).join(';')\n }\n}];\n"
},
"id": "Fn_Extract_Email_Name",
"name": "Extract Emails/Names",
"type": "n8n-nodes-base.function",
"typeVersion": 2
},
{
"parameters": {
"url": "={{'https://rdap.org/domain/' + $json.website_domain}}",
"options": {
"ignoreResponseCode": true
}
},
"id": "HTTP_RDAP",
"name": "RDAP WHOIS",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4
},
{
"parameters": {
"functionCode": "// Parse RDAP minimal fields\nconst j = items[0].json || {};\nlet registrar = j.ldhName || '';\nlet email = '';\nlet phone = '';\nif (j.entities) {\n for (const e of j.entities) {\n const v = (e.vcardArray && e.vcardArray[1]) || [];\n for (const row of v) {\n if (row[0] === 'email' && row[3]) email = row[3];\n if (row[0] === 'tel' && row[3]) phone = row[3];\n }\n }\n}\nreturn [{json:{domain_registrar: registrar, whois_email: email, whois_phone: phone}}];\n"
},
"id": "Fn_Parse_RDAP",
"name": "Parse RDAP",
"type": "n8n-nodes-base.function",
"typeVersion": 2
},
{
"parameters": {
"functionCode": "// Build output row and score confidence\nfunction score(s){\n let c = 0;\n if (s.gbp_phone) c += 25;\n if (s.site_found_emails) c += 25;\n if (s.whois_email) c += 15;\n if (s.website_url) c += 10;\n if (s.gbp_rating) c += 5;\n return Math.min(100, c);\n}\nconst now = new Date().toISOString();\nconst out = {\n shop_name: $json.shop_name,\n address: $json.address,\n city: $json.city,\n state: $json.state,\n zip: $json.zip,\n website_url: $json.website_url,\n website_domain: $json.website_domain,\n gbp_place_id: $json.gbp_place_id || '',\n gbp_name: $json.gbp_name || '',\n gbp_phone: $json.gbp_phone || '',\n gbp_rating: $json.gbp_rating || '',\n gbp_user_ratings_total: $json.gbp_user_ratings_total || '',\n yelp_id: '',\n yelp_name: '',\n yelp_phone: '',\n yelp_rating: '',\n yelp_review_count: '',\n sos_owner_name: '',\n sos_entity_type: '',\n sos_source_url: '',\n license_owner: '',\n license_number: '',\n license_source_url: '',\n linkedin_owner_name: '',\n linkedin_owner_profile: '',\n linkedin_source_url: '',\n domain_registrar: $json.domain_registrar || '',\n domain_registrant: '',\n whois_email: $json.whois_email || '',\n whois_phone: $json.whois_phone || '',\n site_found_emails: $json.site_found_emails || '',\n site_found_names: $json.site_found_names || '',\n apollo_owner_email: '',\n apollo_owner_phone: '',\n rocketreach_owner_email: '',\n rocketreach_owner_phone: '',\n confidence_score: 0,\n sources_used: '',\n last_checked_utc: now\n};\nout.confidence_score = score(out);\nconst srcs = [];\nif (out.gbp_place_id) srcs.push('GooglePlaces');\nif (out.site_found_emails) srcs.push('SiteScrape');\nif (out.whois_email) srcs.push('RDAP');\nout.sources_used = srcs.join('|');\nreturn [{json: out}];\n"
},
"id": "Fn_Build_Output",
"name": "Build Output Row",
"type": "n8n-nodes-base.function",
"typeVersion": 2
}
],
"connections": {
"Read next Input row": {
"main": [
[
{
"node": "Normalize + Domain",
"type": "main",
"index": 0
}
]
]
},
"Normalize + Domain": {
"main": [
[
{
"node": "Google Places Search",
"type": "main",
"index": 0
}
]
]
},
"Google Places Search": {
"main": [
[
{
"node": "Pick Place",
"type": "main",
"index": 0
}
]
]
},
"Pick Place": {
"main": [
[
{
"node": "Google Places Details",
"type": "main",
"index": 0
}
]
]
},
"Google Places Details": {
"main": [
[
{
"node": "Merge Places",
"type": "main",
"index": 0
}
]
]
},
"Merge Places": {
"main": [
[
{
"node": "Build Site URLs",
"type": "main",
"index": 0
}
]
]
},
"Build Site URLs": {
"main": [
[
{
"node": "Fetch Site 1",
"type": "main",
"index": 0
},
{
"node": "RDAP WHOIS",
"type": "main",
"index": 0
}
]
]
},
"Fetch Site 1": {
"main": [
[
{
"node": "Fetch Site 2",
"type": "main",
"index": 0
}
]
]
},
"Fetch Site 2": {
"main": [
[
{
"node": "Fetch Site 3",
"type": "main",
"index": 0
}
]
]
},
"Fetch Site 3": {
"main": [
[
{
"node": "Extract Emails/Names",
"type": "main",
"index": 0
}
]
]
},
"RDAP WHOIS": {
"main": [
[
{
"node": "Parse RDAP",
"type": "main",
"index": 0
}
]
]
},
"Extract Emails/Names": {
"main": [
[
{
"node": "Build Output Row",
"type": "main",
"index": 0
}
]
]
},
"Parse RDAP": {
"main": [
[
{
"node": "Build Output Row",
"type": "main",
"index": 0
}
]
]
},
"Build Output Row": {
"main": [
[
{
"node": "Write to Output",
"type": "main",
"index": 0
}
]
]
}
},
"meta": {
"version": "2.0",
"created": "2025-09-03T16:56:15.716067Z"
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
ASM Investigator Enrichment. Uses googleSheets, httpRequest. Manual trigger; 15 nodes.
Source: https://gist.github.com/jonah-ux/f7546eba9fa2b774357d97aefc5db34a — 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.
This workflow illustrates how to convert data from JSON to binary format and import JSON data or files into Google Sheets or local spreadsheets.
This n8n workflow automates bulk AI video generation using Freepik's Image-to-Video API powered by Minimax Hailuo-02-768p. It reads video prompts from a Google Sheet, generates multiple variations of
This workflow collects images from web search results on a specific query, analyzes the image for labels, formats the text, and adds the information in Google Sheets. HTTP Request node gets images fro
Import Json Data Into Google Sheets And Csv File. Uses httpRequest, googleSheets, spreadsheetFile, stickyNote. Manual trigger; 6 nodes.
This workflow gets data from an API and exports it into Google Sheets and a CSV file.