AutomationFlowsMarketing & Ads › Automate Business Lead Generation with Serpapi and Google Sheets

Automate Business Lead Generation with Serpapi and Google Sheets

ByMuhammad Shaheer Awan @shaheer03 on n8n.io

This workflow helps you automatically collect verified business leads from Google Search using SerpAPI — no coding required. It extracts company names, websites, emails, and phone numbers directly from search results and saves them into Google Sheets for easy follow-up or CRM…

Event trigger★★★★☆ complexity9 nodesHTTP RequestGoogle Sheets
Marketing & Ads Trigger: Event Nodes: 9 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #10657 — 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 →

Download .json
{
  "id": "OOBsbQF1kAFsaXgs",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "LEAD GEN",
  "tags": [],
  "nodes": [
    {
      "id": "7fd7c5a4-fb26-4f15-a87c-1c20da9cee77",
      "name": "When clicking \u2018Test workflow\u2019",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        0,
        0
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "61a49dcf-c81d-40bd-8ddc-35b6d1c1719d",
      "name": "SERPAPI- GOOGE SEARCH",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        220,
        0
      ],
      "parameters": {
        "url": "https://serpapi.com/search",
        "options": {
          "timeout": 30000,
          "response": {
            "response": {}
          }
        },
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "engine",
              "value": "google"
            },
            {
              "name": "q",
              "value": "plumbers in New York"
            },
            {
              "name": "api_key",
              "value": "YOUR-API-KEY"
            },
            {
              "name": "num",
              "value": "20"
            },
            {
              "name": "type",
              "value": "search"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "72440529-a439-4e7d-8a14-1def14609858",
      "name": "Split Out",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        440,
        0
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "organic_results"
      },
      "typeVersion": 1
    },
    {
      "id": "915c2553-aea1-4c52-a07e-8e637729342a",
      "name": "HTTP Request",
      "type": "n8n-nodes-base.httpRequest",
      "maxTries": 2,
      "position": [
        660,
        0
      ],
      "parameters": {
        "url": "={{ $json.link }}",
        "options": {
          "redirect": {
            "redirect": {}
          },
          "allowUnauthorizedCerts": false
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "User-Agent",
              "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/0.0.0.0 Safari/537.36"
            },
            {
              "name": "Accept",
              "value": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"
            },
            {
              "name": "Accept-Language",
              "value": "en-US,en;q=0.9"
            },
            {
              "name": "Accept-Encoding",
              "value": "gzip, deflate, br"
            },
            {
              "name": "Referer",
              "value": " https://www.google.com/"
            },
            {
              "name": "DNT",
              "value": "2"
            },
            {
              "name": "Sec-Fetch-Mode",
              "value": "navigate"
            },
            {
              "name": "Sec-Fetch-Site",
              "value": "cross-site"
            },
            {
              "name": "Connection",
              "value": "keep-alive"
            }
          ]
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.2,
      "waitBetweenTries": 3000
    },
    {
      "id": "619a41f3-a5a3-43d6-9b8c-9093033a3482",
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "position": [
        920,
        0
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineByPosition"
      },
      "typeVersion": 3.1
    },
    {
      "id": "35c55e45-a4dd-412a-90f1-b0fb998d4251",
      "name": "Code",
      "type": "n8n-nodes-base.code",
      "position": [
        1140,
        0
      ],
      "parameters": {
        "jsCode": "const items = [];\nconst doScrapeFallback = true; // set false to skip scraping fallback\n\nconst today = () => new Date().toISOString().split('T')[0];\n\nfunction normalizeSiteUrl(u){\n  if(!u || typeof u !== 'string') return '';\n  u = u.trim();\n  // strip trackers/params\n  u = u.replace(/\\?.*$/,'').replace(/\\/$/,'');\n  if(!/^https?:\\/\\//i && /^[\\w.-]+\\.[a-z]{2,}$/i.test(u)) u = 'https://' + u;\n  return u;\n}\n\nfunction normalizeEmail(e){\n  if(!e) return '';\n  e = e.toLowerCase().trim();\n  // reject placeholders\n  const bad = ['your@email.com','user@example.com','user@example.com','user@example.com','user@example.com','user@example.com'];\n  if(bad.includes(e)) return '';\n  if(!/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}/.test(e)) return '';\n  return e;\n}\n\nfunction normalizePhone(p){\n  if(!p) return '';\n  // keep + then digits\n  const plus = p.trim().startsWith('+') ? '+' : '';\n  const digits = p.replace(/[^\\d]/g,'');\n  if(digits.length < 7) return '';\n  if(/^0+$/.test(digits)) return '';\n  if(/^(\\d)\\1{5,}$/.test(digits)) return '';\n  return plus + digits;\n}\n\nfunction findEmailsInText(text){\n  if(!text) return [];\n  const r = text.match(/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}/g) || [];\n  return Array.from(new Set(r.map(e => normalizeEmail(e)).filter(Boolean)));\n}\n\nfunction findPhonesInText(text){\n  if(!text) return [];\n  const r = text.match(/(?:\\+?\\d{1,3}[-.\\s]?)?(?:\\(?\\d{2,4}\\)?[-.\\s]?)?(?:\\d{2,4}[-.\\s]?){1,4}\\d{2,4}/g) || [];\n  const norm = Array.from(new Set(r.map(p => normalizePhone(p)).filter(Boolean)));\n  return norm;\n}\n\nfunction stripHtml(html){\n  if(!html) return '';\n  return html.replace(/<script[\\s\\S]*?>[\\s\\S]*?<\\/script>/gi,' ')\n             .replace(/<style[\\s\\S]*?>[\\s\\S]*?<\\/style>/gi,' ')\n             .replace(/<\\/?[^>]+(>|$)/g,' ')\n             .replace(/\\s{2,}/g,' ').trim();\n}\n\n// Heuristic: prefer email matching website domain\nfunction scoreEmailAgainstSite(email, site){\n  if(!email || !site) return 0;\n  try {\n    const ed = email.split('@').pop();\n    const sd = (new URL(normalizeSiteUrl(site))).hostname.replace(/^www\\./i,'');\n    if(ed && sd && ed.includes(sd)) return 5;\n  } catch(e){}\n  return 1;\n}\n\nfor(const it of $input.all()){\n  const j = it.json || {};\n\n  // Detect if the item is from serpapi local_results place\n  const isLocalPlace = !!(j.phone || (j.links && j.links.website) || j.title);\n\n  // Candidate fields (prefer structured SerpAPI fields)\n  let business_name = (j.title || j.name || j.business_name || j.place_name || j.company) || '';\n  let website = (j.links && (j.links.website || j.links.website)) || j.website || j.displayed_link || j.link || '';\n  let snippet = (j.snippet || j.description || j.snippet_highlighted_words?.join(' ') || j.rich_snippet?.top?.extensions?.join(' ') || '') || '';\n  let email = '';\n  let phone = '';\n\n  // If SerpAPI local_results has explicit phone or website\n  if(j.phone) phone = normalizePhone(j.phone);\n  if(j.links && j.links.website) website = j.links.website;\n\n  // If structured contact fields exist\n  if(j.email) email = normalizeEmail(j.email);\n  if(j.contact_email) email = normalizeEmail(j.contact_email);\n\n  // If the item includes a 'snippet' or 'description', try to extract emails/phones from it\n  const candidateText = [\n    JSON.stringify(j),\n    j.snippet || '',\n    j.description || '',\n    j.rich_snippet && JSON.stringify(j.rich_snippet) || ''\n  ].join(' ');\n\n  if(!email){\n    const emailsFound = findEmailsInText(candidateText);\n    if(emailsFound.length) {\n      // prefer email matching website domain\n      emailsFound.sort((a,b) => scoreEmailAgainstSite(b,website) - scoreEmailAgainstSite(a,website));\n      email = emailsFound[0] || '';\n    }\n  }\n\n  if(!phone){\n    const phonesFound = findPhonesInText(candidateText);\n    phone = phonesFound[0] || '';\n  }\n\n  // If still missing and Scrape fallback allowed, try to parse item.json.data (if Scrape Website ran before)\n  // Note: when this node runs BEFORE Scrape Website, item.json.data likely not present.\n  if(doScrapeFallback && (!email || !phone) && j.data && typeof j.data === 'string'){\n    const html = j.data;\n    if(!email){\n      const e2 = findEmailsInText(html);\n      if(e2.length) email = e2[0];\n    }\n    if(!phone){\n      const p2 = findPhonesInText(html);\n      if(p2.length) phone = p2[0];\n    }\n  }\n\n  // Final normalization\n  website = normalizeSiteUrl(website);\n  business_name = (business_name && business_name.toString().trim()) || '';\n  snippet = stripHtml(snippet).slice(0,1000);\n\n  // If both business_name and website empty, try to derive name from displayed_link or domain\n  if(!business_name && website){\n    try {\n      business_name = (new URL(website)).hostname.replace(/^www\\./,'');\n    } catch(e){}\n  }\n\n  // Decide if useful (avoid placeholder-only results)\n  const useful = business_name.length > 1 || website.length > 5 || email.length > 4 || phone.replace(/[^\\d]/g,'').length >= 7 || snippet.length > 20;\n  if(!useful) continue;\n\n  items.push({\n    json: {\n      business_name: business_name || 'N/A',\n      website: website || 'N/A',\n      snippet: snippet || 'N/A',\n      email: email || 'Not found',\n      phone: phone || 'Not found',\n      position: j.position || j.rank || 'N/A',\n      scraped_date: today()\n    }\n  });\n}\n\nreturn items;"
      },
      "typeVersion": 2
    },
    {
      "id": "de54e4b6-7ca7-4897-b769-2d18a34e9f09",
      "name": "Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1360,
        0
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [
            {
              "id": "business_name",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "business_name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "website",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "website",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "snippet",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "snippet",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "email",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "email",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "phone",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "phone",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "position",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "position",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "scraped_date",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "scraped_date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
          "cachedResultName": "LEADS"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "117bMXKXElY_5ican1lkpZ-y_-hDRHz3_Bjr-oN2IhyU",
          "cachedResultUrl": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
          "cachedResultName": "LEAD AUTOMATION"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "5b453487-8d98-4572-9bdf-ad6cc3917109",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -60,
        -120
      ],
      "parameters": {
        "color": 4,
        "width": 1640,
        "height": 380,
        "content": "# LEAD GENERATION USING SERP-API\n"
      },
      "typeVersion": 1
    },
    {
      "id": "ca9d18fc-9abd-434e-8582-0bc87ac81eb3",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -600,
        -380
      ],
      "parameters": {
        "width": 520,
        "height": 1120,
        "content": "# \ud83e\uddf2 Lead Generation Automation (n8n + SerpAPI)\n\n# \ud83c\udfa5 **Watch the Tutorial**\n[![Watch on YouTube](https://img.youtube.com/vi/gr9_wEMc9sM/hqdefault.jpg)](https://youtu.be/gr9_wEMc9sM?si=YB-TEjdGgu5oC_Z5)\n\nGenerate verified business leads automatically from Google Search using **SerpAPI** and **n8n**.  \nThis workflow scrapes search results, extracts **emails, websites, phone numbers, and company names**, and saves everything directly into **Google Sheets**.\n\n### \u2699\ufe0f How It Works\n1. Searches Google using SerpAPI  \n2. Extracts contact details via regex and validation  \n3. Stores structured leads in Google Sheets  \n\n### \ud83d\udcbc Use Cases\n- Local business prospecting  \n- Email outreach campaigns  \n- CRM data enrichment  \n\nQuick setup in under 10 minutes \u2014 just add your **SerpAPI key** and **Google Sheets connection**.\n\n---\n\n\ud83d\udd17 **Created by:** Muhammad Shaheer  \n\ud83d\udca1 **YouTube Channel:** [https://www.youtube.com/@BOTNEXA](https://www.youtube.com/@ShaheerAutomation)  \n\ud83d\udce7 **For collaborations:** shaheerawan001@gmail.com  \n\ud83d\udd17 **LinkedIn:** www.linkedin.com/in/muhammad-shaheer-898513192\n"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "864a4e02-d823-4846-bb13-9051420aba8e",
  "connections": {
    "Code": {
      "main": [
        [
          {
            "node": "Google Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Out": {
      "main": [
        [
          {
            "node": "HTTP Request",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "SERPAPI- GOOGE SEARCH": {
      "main": [
        [
          {
            "node": "Split Out",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking \u2018Test workflow\u2019": {
      "main": [
        [
          {
            "node": "SERPAPI- GOOGE SEARCH",
            "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.

Pro

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

About this workflow

This workflow helps you automatically collect verified business leads from Google Search using SerpAPI — no coding required. It extracts company names, websites, emails, and phone numbers directly from search results and saves them into Google Sheets for easy follow-up or CRM…

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

More Marketing & Ads workflows → · Browse all categories →

Related workflows

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

Marketing & Ads

This template automates the enrichment of business leads from a Google Sheet by: Triggering when a row is activated Searching for company information with Serper.dev Generating and validating potentia

HTTP Request, Google Sheets Trigger, Google Sheets
Marketing & Ads

The automation starts by retreiving the unused queries from a sheet, executes queries in the web using Serper API and extracts linkedin profiles of decision makers.

Google Sheets, HTTP Request, Stop And Error
Marketing & Ads

This workflow automates comprehensive SEO reporting by: Extracting keyword rankings and page performance from Google Search Console. Gathering organic reach metrics from Google Analytics. Analyzing in

HTTP Request, Google Sheets, Google Analytics +1
Marketing & Ads

SEO agencies doing competitor analysis for clients Content teams planning content strategies Marketing teams tracking competitive performance SEO professionals measuring AI search visibility

@Seranking/N8N Nodes Seranking, Google Sheets
Marketing & Ads

Stop losing warm leads in the noise. This automation analyzes your lead engagement data, calculates priority scores based on activity and last contact date, and automatically queues your top 10 leads

Google Sheets, ClickUp