This workflow corresponds to n8n.io template #14344 — we link there as the canonical source.
This workflow follows the Airtable → 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 →
{
"meta": {
"templateCredsSetupCompleted": false
},
"name": "Scrape WooCommerce store leads and sync verified contacts into Airtable",
"nodes": [
{
"id": "cb976b5c-b073-44a2-a24f-45129b414bad",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-400,
-176
],
"parameters": {
"width": 480,
"height": 896,
"content": "## Scrape WooCommerce store leads and sync verified contacts into Airtable\n\n### How it works\n\n1. The workflow is triggered manually and configured with scrape parameters (platform, country, lead count, email collection).\n2. A scrape job is submitted to the ScraperCity API and the returned run ID is stored.\n3. The workflow polls the ScraperCity API at intervals, retrying every 60 seconds until the scrape job is marked complete.\n4. Once complete, the scraped CSV results are downloaded and parsed into structured records.\n5. Contacts are filtered to retain only those with an email or phone number, and duplicates are removed.\n6. Verified, deduplicated leads are upserted into an Airtable base for ongoing outreach.\n\n### Setup steps\n\n- [ ] Create a ScraperCity account and obtain your API key at app.scrapercity.com\n- [ ] Add your ScraperCity API key as an n8n credential (HTTP Header Auth) and attach it to the HTTP Request nodes\n- [ ] Set up an Airtable base with fields matching the scraped lead data (e.g. email, phone, store URL) and obtain your Airtable API key\n- [ ] Configure the Airtable node with your base ID, table name, and API credential\n- [ ] In 'Configure Scrape Parameters', set your desired platform, countryCode, totalLeads, and collectEmails values\n- [ ] Adjust the wait durations in 'Wait Before First Poll' and 'Wait 60 Seconds Before Retry' to match expected scrape times\n\n### Customization\n\nIncrease 'totalLeads' in the scrape parameters to collect larger datasets. Adjust the polling interval (Wait 60 Seconds Before Retry) if scrapes consistently take longer or shorter. Add a Slack or email notification node after the Airtable upsert to alert the team when new leads are synced. The filter node can be extended to require both email AND phone if stricter lead quality is needed."
},
"typeVersion": 1
},
{
"id": "0d9231a1-bf3a-4846-8bea-fca652d0bb27",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
160,
176
],
"parameters": {
"color": 7,
"width": 960,
"height": 304,
"content": "## Trigger and scrape setup\n\nManually triggers the workflow, defines scrape parameters, submits the scrape job to ScraperCity, and stores the returned run ID for polling."
},
"typeVersion": 1
},
{
"id": "ce0ed25d-348e-4aa5-b659-db4b4447399e",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
1200,
176
],
"parameters": {
"color": 7,
"width": 1232,
"height": 768,
"content": "## Scrape status polling loop\n\nWaits before the first poll, checks the scrape job status, and routes to either download (if complete) or a retry loop that waits 60 seconds and re-polls."
},
"typeVersion": 1
},
{
"id": "cbca461d-c3ba-4689-9789-be676d20fdc3",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1968,
-176
],
"parameters": {
"color": 7,
"width": 464,
"height": 320,
"content": "## Results download and parsing\n\nDownloads the completed scrape as a CSV file from ScraperCity and parses and cleans it into structured data items ready for filtering."
},
"typeVersion": 1
},
{
"id": "5d01a0eb-fd6e-4cd7-9e2a-b7ca4fa2ec1c",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
2496,
144
],
"parameters": {
"color": 7,
"width": 448,
"height": 320,
"content": "## Contact filtering and deduplication\n\nRetains only contacts that have a valid email or phone number, then removes any duplicate email entries to ensure clean lead data."
},
"typeVersion": 1
},
{
"id": "d012a7b7-6537-4832-8c4c-aee2793ebeb7",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
3008,
96
],
"parameters": {
"color": 7,
"width": 240,
"height": 368,
"content": "## Airtable lead sync\n\nUpserts each verified, deduplicated lead record into the Airtable base, creating new entries or updating existing ones."
},
"typeVersion": 1
},
{
"id": "a1000000-0000-0000-0000-000000000001",
"name": "When clicking 'Execute workflow'",
"type": "n8n-nodes-base.manualTrigger",
"position": [
208,
304
],
"parameters": {},
"typeVersion": 1
},
{
"id": "a1000000-0000-0000-0000-000000000002",
"name": "Configure Scrape Parameters",
"type": "n8n-nodes-base.set",
"position": [
464,
304
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "cfg-001",
"name": "platform",
"type": "string",
"value": "woocommerce"
},
{
"id": "cfg-002",
"name": "countryCode",
"type": "string",
"value": "US"
},
{
"id": "cfg-003",
"name": "totalLeads",
"type": "number",
"value": 500
},
{
"id": "cfg-004",
"name": "collectEmails",
"type": "boolean",
"value": true
},
{
"id": "cfg-005",
"name": "collectPhones",
"type": "boolean",
"value": true
},
{
"id": "cfg-006",
"name": "airtableBaseId",
"type": "string",
"value": "appYOUR_BASE_ID"
},
{
"id": "cfg-007",
"name": "airtableTableName",
"type": "string",
"value": "WooCommerce Leads"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "a1000000-0000-0000-0000-000000000003",
"name": "Start WooCommerce Lead Scrape",
"type": "n8n-nodes-base.httpRequest",
"position": [
720,
304
],
"parameters": {
"url": "https://app.scrapercity.com/api/v1/scrape/store-leads",
"method": "POST",
"options": {},
"jsonBody": "={\n \"platform\": \"{{ $json.platform }}\",\n \"countryCode\": \"{{ $json.countryCode }}\",\n \"emails\": {{ $json.collectEmails }},\n \"phones\": {{ $json.collectPhones }},\n \"totalLeads\": {{ $json.totalLeads }}\n}",
"sendBody": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "a1000000-0000-0000-0000-000000000004",
"name": "Store Run ID",
"type": "n8n-nodes-base.set",
"position": [
976,
304
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "rid-001",
"name": "runId",
"type": "string",
"value": "={{ $json.runId }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "a1000000-0000-0000-0000-000000000005",
"name": "Wait Before First Poll",
"type": "n8n-nodes-base.wait",
"position": [
1248,
304
],
"parameters": {
"amount": 60
},
"typeVersion": 1.1
},
{
"id": "a1000000-0000-0000-0000-000000000006",
"name": "Poll Scrape Status",
"type": "n8n-nodes-base.httpRequest",
"position": [
1504,
304
],
"parameters": {
"url": "=https://app.scrapercity.com/api/v1/scrape/status/{{ $json.runId }}",
"method": "GET",
"options": {},
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "a1000000-0000-0000-0000-000000000007",
"name": "Is Scrape Complete?",
"type": "n8n-nodes-base.if",
"position": [
1760,
304
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "cond-001",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.status }}",
"rightValue": "SUCCEEDED"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "a1000000-0000-0000-0000-000000000008",
"name": "Wait 60 Seconds Before Retry",
"type": "n8n-nodes-base.wait",
"position": [
2016,
768
],
"parameters": {
"amount": 60
},
"typeVersion": 1.1
},
{
"id": "a1000000-0000-0000-0000-000000000009",
"name": "Preserve Run ID for Retry",
"type": "n8n-nodes-base.set",
"position": [
2288,
528
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "prid-001",
"name": "runId",
"type": "string",
"value": "={{ $json.runId }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "a1000000-0000-0000-0000-000000000010",
"name": "Download Scraped Results",
"type": "n8n-nodes-base.httpRequest",
"position": [
2016,
-16
],
"parameters": {
"url": "=https://app.scrapercity.com/api/downloads/{{ $json.runId }}",
"method": "GET",
"options": {
"response": {
"response": {
"responseFormat": "text"
}
}
},
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "a1000000-0000-0000-0000-000000000011",
"name": "Parse and Clean CSV Results",
"type": "n8n-nodes-base.code",
"position": [
2288,
-16
],
"parameters": {
"jsCode": "// Parse CSV text returned by ScraperCity download endpoint\n// Expected columns: name, website, email, phone, instagram, facebook, country\n\nconst csvText = items[0].json.data || items[0].json.body || '';\n\nif (!csvText || csvText.trim().length === 0) {\n return [];\n}\n\nconst lines = csvText.trim().split('\\n');\nif (lines.length < 2) return [];\n\n// Parse header row - handle quoted headers\nconst parseCSVLine = (line) => {\n const result = [];\n let current = '';\n let inQuotes = false;\n for (let i = 0; i < line.length; i++) {\n const ch = line[i];\n if (ch === '\"') {\n inQuotes = !inQuotes;\n } else if (ch === ',' && !inQuotes) {\n result.push(current.trim());\n current = '';\n } else {\n current += ch;\n }\n }\n result.push(current.trim());\n return result;\n};\n\nconst headers = parseCSVLine(lines[0]).map(h => h.toLowerCase().replace(/\\s+/g, '_'));\n\nconst seen = new Set();\nconst output = [];\n\nfor (let i = 1; i < lines.length; i++) {\n if (!lines[i].trim()) continue;\n const values = parseCSVLine(lines[i]);\n const record = {};\n headers.forEach((h, idx) => {\n record[h] = values[idx] || '';\n });\n\n // Normalize email\n const email = (record.email || '').trim().toLowerCase();\n\n // Deduplicate by email; allow blank emails but only one blank\n const dedupeKey = email || ('no-email-' + i);\n if (seen.has(dedupeKey)) continue;\n seen.add(dedupeKey);\n\n output.push({ json: record });\n}\n\nreturn output;"
},
"typeVersion": 2
},
{
"id": "a1000000-0000-0000-0000-000000000012",
"name": "Filter Contacts with Email or Phone",
"type": "n8n-nodes-base.filter",
"position": [
2544,
304
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"caseSensitive": false,
"typeValidation": "loose"
},
"combinator": "or",
"conditions": [
{
"id": "flt-001",
"operator": {
"type": "string",
"operation": "isNotEmpty"
},
"leftValue": "={{ $json.email }}",
"rightValue": ""
},
{
"id": "flt-002",
"operator": {
"type": "string",
"operation": "isNotEmpty"
},
"leftValue": "={{ $json.phone }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "a1000000-0000-0000-0000-000000000013",
"name": "Remove Duplicate Emails",
"type": "n8n-nodes-base.removeDuplicates",
"position": [
2800,
304
],
"parameters": {
"options": {},
"fieldsToCompare": {
"fields": [
{
"fieldName": "email"
}
]
}
},
"typeVersion": 2
},
{
"id": "a1000000-0000-0000-0000-000000000014",
"name": "Upsert Lead into Airtable",
"type": "n8n-nodes-base.airtable",
"position": [
3056,
304
],
"parameters": {
"table": {
"__rl": true,
"mode": "name",
"value": "={{ $('Configure Scrape Parameters').item.json.airtableTableName }}"
},
"columns": {
"value": {
"Name": "={{ $json.name }}",
"Email": "={{ $json.email }}",
"Phone": "={{ $json.phone }}",
"Country": "={{ $json.country }}",
"Website": "={{ $json.website }}",
"Facebook": "={{ $json.facebook }}",
"Instagram": "={{ $json.instagram }}"
},
"mappingMode": "defineBelow"
},
"options": {},
"operation": "upsert",
"application": {
"__rl": true,
"mode": "id",
"value": "={{ $('Configure Scrape Parameters').item.json.airtableBaseId }}"
},
"fieldsToMatchOn": {
"fields": [
"Email"
]
}
},
"credentials": {
"airtableTokenApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
}
],
"settings": {
"executionOrder": "v1"
},
"connections": {
"Store Run ID": {
"main": [
[
{
"node": "Wait Before First Poll",
"type": "main",
"index": 0
}
]
]
},
"Poll Scrape Status": {
"main": [
[
{
"node": "Is Scrape Complete?",
"type": "main",
"index": 0
}
]
]
},
"Is Scrape Complete?": {
"main": [
[
{
"node": "Download Scraped Results",
"type": "main",
"index": 0
}
],
[
{
"node": "Wait 60 Seconds Before Retry",
"type": "main",
"index": 0
}
]
]
},
"Wait Before First Poll": {
"main": [
[
{
"node": "Poll Scrape Status",
"type": "main",
"index": 0
}
]
]
},
"Remove Duplicate Emails": {
"main": [
[
{
"node": "Upsert Lead into Airtable",
"type": "main",
"index": 0
}
]
]
},
"Download Scraped Results": {
"main": [
[
{
"node": "Parse and Clean CSV Results",
"type": "main",
"index": 0
}
]
]
},
"Preserve Run ID for Retry": {
"main": [
[
{
"node": "Poll Scrape Status",
"type": "main",
"index": 0
}
]
]
},
"Configure Scrape Parameters": {
"main": [
[
{
"node": "Start WooCommerce Lead Scrape",
"type": "main",
"index": 0
}
]
]
},
"Parse and Clean CSV Results": {
"main": [
[
{
"node": "Filter Contacts with Email or Phone",
"type": "main",
"index": 0
}
]
]
},
"Wait 60 Seconds Before Retry": {
"main": [
[
{
"node": "Preserve Run ID for Retry",
"type": "main",
"index": 0
}
]
]
},
"Start WooCommerce Lead Scrape": {
"main": [
[
{
"node": "Store Run ID",
"type": "main",
"index": 0
}
]
]
},
"When clicking 'Execute workflow'": {
"main": [
[
{
"node": "Configure Scrape Parameters",
"type": "main",
"index": 0
}
]
]
},
"Filter Contacts with Email or Phone": {
"main": [
[
{
"node": "Remove Duplicate Emails",
"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.
airtableTokenApihttpHeaderAuth
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This template is for sales teams, lead generation agencies, and growth marketers who want to build targeted lists of WooCommerce store owners -- complete with verified emails, phone numbers, and social profiles -- and have those contacts automatically land in Airtable for…
Source: https://n8n.io/workflows/14344/ — 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.
Sales teams, lead generation agencies, and growth marketers who want to pull targeted B2B contacts from Apollo.io, enrich them with verified emails and mobile numbers, and push clean records directly
Why spend $1,000s on lead gen when your perfect leads are already waiting in Apollo? You’ve already filtered the ideal prospects. You know who they are, where they work, and what they do.
Automatically identify and qualify ideal customer prospects from LinkedIn post reactions using AI-powered profile analysis and intelligent data enrichment.
This n8n workflow automates the process of finding ecommerce seller leads, enriching them with product and business details, discovering company websites, and extracting contact information such as em
This template is for B2B sales teams, SDRs, growth marketers, and founders who maintain a spreadsheet of prospects and need verified contact details -- emails and mobile numbers -- without manual rese