This workflow follows the HTTP Request → Postgres 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 →
{
"updatedAt": "2026-03-15T20:42:04.929Z",
"createdAt": "2026-03-09T21:09:54.399Z",
"id": "Wl4HBw8sGpfvTzta",
"name": "VenueDesk \u2014 Lead Discovery (Daily)",
"description": null,
"active": true,
"isArchived": false,
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 6 * * *"
}
]
}
},
"id": "sched1",
"name": "Daily at 6am",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
200,
300
]
},
{
"parameters": {
"path": "run-lead-discovery",
"httpMethod": "POST",
"responseMode": "onReceived",
"options": {}
},
"id": "wh_disc",
"name": "Manual Discovery Trigger",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
200,
480
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "DO $$ BEGIN\n IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='leads' AND column_name='county') THEN\n ALTER TABLE leads ADD COLUMN county text DEFAULT '';\n END IF;\n IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='leads' AND column_name='phone') THEN\n ALTER TABLE leads ADD COLUMN phone text DEFAULT '';\n END IF;\n IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='leads' AND column_name='source') THEN\n ALTER TABLE leads ADD COLUMN source text DEFAULT 'manual';\n END IF;\nEND $$; SELECT 1;",
"options": {}
},
"id": "ensure1",
"name": "Ensure DB Columns",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
420,
380
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const counties = [\n 'Greater London','Kent','Surrey','Hertfordshire','Essex',\n 'Suffolk','Norfolk','Leicestershire','Derbyshire','West Midlands',\n 'Worcestershire','West Yorkshire','North Yorkshire','Lancashire',\n 'Greater Manchester','Cheshire','County Durham','Northumberland','Dorset','Hampshire',\n 'Oxfordshire','Cambridgeshire','Buckinghamshire','Berkshire','Devon',\n 'Cornwall','Somerset','Gloucestershire','Wiltshire','Shropshire'\n];\nconst venueTypes = [\n {q:'village hall hire', t:'Village Hall'},\n {q:'community centre room hire', t:'Community Centre'},\n {q:'sports club function room', t:'Sports Club'},\n {q:'event venue hire', t:'Event Venue'},\n {q:'church hall hire', t:'Community Centre'},\n {q:'social club venue hire', t:'Sports Club'},\n {q:'hotel function room hire', t:'Hotel'},\n {q:'working mens club hire', t:'Sports Club'}\n];\n\n// Rotate county by day-of-month so each day targets a different area\nconst county = counties[new Date().getDate() % counties.length];\n\nreturn venueTypes.map(v => ({\n json: {\n query: v.q + ' ' + county,\n county: county,\n venue_type_label: v.t\n }\n}));"
},
"id": "gen1",
"name": "Generate Searches",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
640,
380
]
},
{
"parameters": {
"method": "POST",
"url": "https://places.googleapis.com/v1/places:searchText",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "X-Goog-Api-Key",
"value": "={{$env.GOOGLE_PLACES_API_KEY}}"
},
{
"name": "X-Goog-FieldMask",
"value": "places.displayName,places.formattedAddress,places.websiteUri,places.nationalPhoneNumber,places.id"
}
]
},
"sendBody": true,
"contentType": "json",
"bodyParameters": {
"parameters": [
{
"name": "textQuery",
"value": "={{ $json.query }}"
},
{
"name": "regionCode",
"value": "GB"
},
{
"name": "languageCode",
"value": "en-GB"
},
{
"name": "maxResultCount",
"value": "=20"
}
]
},
"options": {
"response": {
"response": {
"neverError": true
}
},
"timeout": 20000
}
},
"id": "ddg_fetch",
"name": "Search Google Maps",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
860,
380
]
},
{
"parameters": {
"jsCode": "const item = $input.item.json;\n\n// Get county and venue_type from upstream Generate Searches node\nlet genItem = {};\ntry { genItem = $('Generate Searches').item.json; } catch(e) {}\nconst county = String(genItem.county || item.county || 'England');\nconst vtype = String(genItem.venue_type_label || item.venue_type_label || 'Community Venue');\n\n// Google Places API returns: { places: [ { displayName, formattedAddress, websiteUri, nationalPhoneNumber, id } ] }\nconst places = Array.isArray(item.places) ? item.places : [];\n\nif (places.length === 0) {\n console.log('No places returned for query:', genItem.query || 'unknown');\n return [];\n}\n\n// Chains/irrelevant venues to skip\nconst SKIP = /tesco|asda|sainsbury|morrisons|costa|starbucks|mcdonalds|pizza|kfc|nando|greggs|boots|lidl|aldi|amazon/i;\n\nconst results = [];\nfor (const p of places) {\n const name = (p.displayName && p.displayName.text) ? p.displayName.text.trim() : '';\n const address = p.formattedAddress || '';\n const website = p.websiteUri || '';\n const phone = p.nationalPhoneNumber || '';\n const placeId = p.id || '';\n\n if (!name || name.length < 3) continue;\n if (SKIP.test(name)) continue;\n\n results.push({\n json: {\n venue_name: name.substring(0, 120),\n website_url: website,\n phone: phone,\n venue_type: vtype,\n county: county,\n notes: ('Google Maps \u00b7 ' + county + (address ? ' \u00b7 ' + address : '')).substring(0, 255),\n place_id: placeId,\n source: 'google_maps'\n }\n });\n}\n\nconsole.log('Parsed', results.length, 'venues from Google Maps for', vtype, 'in', county);\nreturn results;"
},
"id": "parse_ddg",
"name": "Parse Google Maps Results",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1080,
380
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "INSERT INTO leads (venue_name, email, website_url, phone, venue_type, status, county, source, notes)\nSELECT\n $${{ $json.venue_name }}$$::text,\n $$$$::text,\n $${{ $json.website_url }}$$::text,\n $${{ $json.phone }}$$::text,\n $${{ $json.venue_type }}$$::text,\n $$new$$::text,\n $${{ $json.county }}$$::text,\n $$google_maps$$::text,\n $${{ $json.notes }}$$::text\nWHERE NOT EXISTS (\n SELECT 1 FROM leads WHERE website_url = $${{ $json.website_url }}$$ AND $${{ $json.website_url }}$$ <> $$$$\n)\nAND NOT EXISTS (\n SELECT 1 FROM leads WHERE lower(venue_name) = lower($${{ $json.venue_name.substring(0,50) }}$$)\n);",
"options": {}
},
"id": "insert1",
"name": "Insert Lead",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
1300,
380
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
}
],
"connections": {
"Daily at 6am": {
"main": [
[
{
"node": "Ensure DB Columns",
"type": "main",
"index": 0
}
]
]
},
"Manual Discovery Trigger": {
"main": [
[
{
"node": "Ensure DB Columns",
"type": "main",
"index": 0
}
]
]
},
"Ensure DB Columns": {
"main": [
[
{
"node": "Generate Searches",
"type": "main",
"index": 0
}
]
]
},
"Generate Searches": {
"main": [
[
{
"node": "Search Google Maps",
"type": "main",
"index": 0
}
]
]
},
"Parse Google Maps Results": {
"main": [
[
{
"node": "Insert Lead",
"type": "main",
"index": 0
}
]
]
},
"Search Google Maps": {
"main": [
[
{
"node": "Parse Google Maps Results",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"callerPolicy": "workflowsFromSameOwner",
"availableInMCP": true
},
"staticData": {
"node:Daily at 6am": {
"recurrenceRules": []
}
},
"meta": null,
"versionId": "e80c5b06-9ee0-4608-9e23-b17f6634bd9d",
"activeVersionId": "e80c5b06-9ee0-4608-9e23-b17f6634bd9d",
"versionCounter": 70,
"triggerCount": 2,
"shared": [
{
"updatedAt": "2026-03-09T21:09:54.399Z",
"createdAt": "2026-03-09T21:09:54.399Z",
"role": "workflow:owner",
"workflowId": "Wl4HBw8sGpfvTzta",
"projectId": "xsdyFVsct988qLUy",
"project": {
"updatedAt": "2025-12-04T20:24:17.537Z",
"createdAt": "2025-12-04T19:47:48.685Z",
"id": "xsdyFVsct988qLUy",
"name": "andrew johnson <andrew.ralston.johnson@gmail.com>",
"type": "personal",
"icon": null,
"description": null,
"creatorId": "e2485274-7097-4eb5-8502-e39b2308096c"
}
}
],
"tags": [],
"activeVersion": {
"updatedAt": "2026-03-10T17:44:36.839Z",
"createdAt": "2026-03-10T17:44:36.839Z",
"versionId": "e80c5b06-9ee0-4608-9e23-b17f6634bd9d",
"workflowId": "Wl4HBw8sGpfvTzta",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 6 * * *"
}
]
}
},
"id": "sched1",
"name": "Daily at 6am",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
200,
300
]
},
{
"parameters": {
"path": "run-lead-discovery",
"httpMethod": "POST",
"responseMode": "onReceived",
"options": {}
},
"id": "wh_disc",
"name": "Manual Discovery Trigger",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
200,
480
],
"webhookId": "run-lead-discovery"
},
{
"parameters": {
"operation": "executeQuery",
"query": "DO $$ BEGIN\n IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='leads' AND column_name='county') THEN\n ALTER TABLE leads ADD COLUMN county text DEFAULT '';\n END IF;\n IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='leads' AND column_name='phone') THEN\n ALTER TABLE leads ADD COLUMN phone text DEFAULT '';\n END IF;\n IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='leads' AND column_name='source') THEN\n ALTER TABLE leads ADD COLUMN source text DEFAULT 'manual';\n END IF;\nEND $$; SELECT 1;",
"options": {}
},
"id": "ensure1",
"name": "Ensure DB Columns",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
420,
380
],
"credentials": {
"postgres": {
"id": "iEsRYyB7vjr5G7i8",
"name": "Postgres account 3"
}
}
},
{
"parameters": {
"jsCode": "const counties = [\n 'Greater London','Kent','Surrey','Hertfordshire','Essex',\n 'Suffolk','Norfolk','Leicestershire','Derbyshire','West Midlands',\n 'Worcestershire','West Yorkshire','North Yorkshire','Lancashire',\n 'Greater Manchester','Cheshire','County Durham','Northumberland','Dorset','Hampshire',\n 'Oxfordshire','Cambridgeshire','Buckinghamshire','Berkshire','Devon',\n 'Cornwall','Somerset','Gloucestershire','Wiltshire','Shropshire'\n];\nconst venueTypes = [\n {q:'village hall hire', t:'Village Hall'},\n {q:'community centre room hire', t:'Community Centre'},\n {q:'sports club function room', t:'Sports Club'},\n {q:'event venue hire', t:'Event Venue'},\n {q:'church hall hire', t:'Community Centre'},\n {q:'social club venue hire', t:'Sports Club'},\n {q:'hotel function room hire', t:'Hotel'},\n {q:'working mens club hire', t:'Sports Club'}\n];\n\n// Rotate county by day-of-month so each day targets a different area\nconst county = counties[new Date().getDate() % counties.length];\n\nreturn venueTypes.map(v => ({\n json: {\n query: v.q + ' ' + county,\n county: county,\n venue_type_label: v.t\n }\n}));"
},
"id": "gen1",
"name": "Generate Searches",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
640,
380
]
},
{
"parameters": {
"method": "POST",
"url": "https://places.googleapis.com/v1/places:searchText",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "X-Goog-Api-Key",
"value": "={{$env.GOOGLE_PLACES_API_KEY}}"
},
{
"name": "X-Goog-FieldMask",
"value": "places.displayName,places.formattedAddress,places.websiteUri,places.nationalPhoneNumber,places.id"
}
]
},
"sendBody": true,
"contentType": "json",
"bodyParameters": {
"parameters": [
{
"name": "textQuery",
"value": "={{ $json.query }}"
},
{
"name": "regionCode",
"value": "GB"
},
{
"name": "languageCode",
"value": "en-GB"
},
{
"name": "maxResultCount",
"value": "=20"
}
]
},
"options": {
"response": {
"response": {
"neverError": true
}
},
"timeout": 20000
}
},
"id": "ddg_fetch",
"name": "Search Google Maps",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
860,
380
]
},
{
"parameters": {
"jsCode": "const item = $input.item.json;\n\n// Get county and venue_type from upstream Generate Searches node\nlet genItem = {};\ntry { genItem = $('Generate Searches').item.json; } catch(e) {}\nconst county = String(genItem.county || item.county || 'England');\nconst vtype = String(genItem.venue_type_label || item.venue_type_label || 'Community Venue');\n\n// Google Places API returns: { places: [ { displayName, formattedAddress, websiteUri, nationalPhoneNumber, id } ] }\nconst places = Array.isArray(item.places) ? item.places : [];\n\nif (places.length === 0) {\n console.log('No places returned for query:', genItem.query || 'unknown');\n return [];\n}\n\n// Chains/irrelevant venues to skip\nconst SKIP = /tesco|asda|sainsbury|morrisons|costa|starbucks|mcdonalds|pizza|kfc|nando|greggs|boots|lidl|aldi|amazon/i;\n\nconst results = [];\nfor (const p of places) {\n const name = (p.displayName && p.displayName.text) ? p.displayName.text.trim() : '';\n const address = p.formattedAddress || '';\n const website = p.websiteUri || '';\n const phone = p.nationalPhoneNumber || '';\n const placeId = p.id || '';\n\n if (!name || name.length < 3) continue;\n if (SKIP.test(name)) continue;\n\n results.push({\n json: {\n venue_name: name.substring(0, 120),\n website_url: website,\n phone: phone,\n venue_type: vtype,\n county: county,\n notes: ('Google Maps \u00b7 ' + county + (address ? ' \u00b7 ' + address : '')).substring(0, 255),\n place_id: placeId,\n source: 'google_maps'\n }\n });\n}\n\nconsole.log('Parsed', results.length, 'venues from Google Maps for', vtype, 'in', county);\nreturn results;"
},
"id": "parse_ddg",
"name": "Parse Google Maps Results",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1080,
380
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "INSERT INTO leads (venue_name, email, website_url, phone, venue_type, status, county, source, notes)\nSELECT\n $${{ $json.venue_name }}$$::text,\n $$$$::text,\n $${{ $json.website_url }}$$::text,\n $${{ $json.phone }}$$::text,\n $${{ $json.venue_type }}$$::text,\n $$new$$::text,\n $${{ $json.county }}$$::text,\n $$google_maps$$::text,\n $${{ $json.notes }}$$::text\nWHERE NOT EXISTS (\n SELECT 1 FROM leads WHERE website_url = $${{ $json.website_url }}$$ AND $${{ $json.website_url }}$$ <> $$$$\n)\nAND NOT EXISTS (\n SELECT 1 FROM leads WHERE lower(venue_name) = lower($${{ $json.venue_name.substring(0,50) }}$$)\n);",
"options": {}
},
"id": "insert1",
"name": "Insert Lead",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
1300,
380
],
"credentials": {
"postgres": {
"id": "iEsRYyB7vjr5G7i8",
"name": "Postgres account 3"
}
}
}
],
"connections": {
"Daily at 6am": {
"main": [
[
{
"node": "Ensure DB Columns",
"type": "main",
"index": 0
}
]
]
},
"Manual Discovery Trigger": {
"main": [
[
{
"node": "Ensure DB Columns",
"type": "main",
"index": 0
}
]
]
},
"Ensure DB Columns": {
"main": [
[
{
"node": "Generate Searches",
"type": "main",
"index": 0
}
]
]
},
"Generate Searches": {
"main": [
[
{
"node": "Search Google Maps",
"type": "main",
"index": 0
}
]
]
},
"Parse Google Maps Results": {
"main": [
[
{
"node": "Insert Lead",
"type": "main",
"index": 0
}
]
]
},
"Search Google Maps": {
"main": [
[
{
"node": "Parse Google Maps Results",
"type": "main",
"index": 0
}
]
]
}
},
"authors": "andrew johnson",
"name": null,
"description": null,
"autosaved": false,
"workflowPublishHistory": [
{
"createdAt": "2026-03-15T20:42:04.955Z",
"id": 1396,
"workflowId": "Wl4HBw8sGpfvTzta",
"versionId": "e80c5b06-9ee0-4608-9e23-b17f6634bd9d",
"event": "deactivated",
"userId": "e2485274-7097-4eb5-8502-e39b2308096c"
},
{
"createdAt": "2026-03-15T20:42:04.971Z",
"id": 1397,
"workflowId": "Wl4HBw8sGpfvTzta",
"versionId": "e80c5b06-9ee0-4608-9e23-b17f6634bd9d",
"event": "activated",
"userId": "e2485274-7097-4eb5-8502-e39b2308096c"
},
{
"createdAt": "2026-03-10T17:44:36.868Z",
"id": 919,
"workflowId": "Wl4HBw8sGpfvTzta",
"versionId": "e80c5b06-9ee0-4608-9e23-b17f6634bd9d",
"event": "deactivated",
"userId": "e2485274-7097-4eb5-8502-e39b2308096c"
},
{
"createdAt": "2026-03-10T17:44:36.882Z",
"id": 920,
"workflowId": "Wl4HBw8sGpfvTzta",
"versionId": "e80c5b06-9ee0-4608-9e23-b17f6634bd9d",
"event": "activated",
"userId": "e2485274-7097-4eb5-8502-e39b2308096c"
}
]
}
}
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.
postgres
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
VenueDesk — Lead Discovery (Daily). Uses postgres, httpRequest. Scheduled trigger; 7 nodes.
Source: https://github.com/AndyJay72/VenueDesk/blob/2b877fc3d0dba284a404cc64fcc153b92c612ae3/n8n-workflows/Wl4HBw8sGpfvTzta.json — 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.
Workflow A — WhatsApp Lead Intake & Qualification. Uses postgres, httpRequest, errorTrigger. Scheduled trigger; 67 nodes.
This workflow fetches unqualified leads from Postgres at defined retry intervals, sends personalized WhatsApp template messages via Gallabox API, and logs message activity while updating lead status i
Build authentic Reddit presence and generate qualified leads through AI-powered community engagement that provides genuine value without spam or promotion.
This workflow automates bulk email campaigns with built-in validation, deliverability protection, and smart send-time optimization.
This workflow runs on scheduled weekly and monthly triggers to generate unified marketing performance reports. It processes multiple websites by collecting analytics data, paid ads performance, and CR