Hi folks
I’m migrating from the old Places Text Search (Place Search) to Places API v1.
Flow:
-
Set
→ city/category/radius/minRating/limit -
Geocode city
-
POST
to places:searchText (v1) with a circlelocationBias
andmaxResultCount
-
Code node “Normalize Places limit” reduces to top N, and when a place has no website I set
needs_details = true
and keepv1_name
(e.g.places/ChIJ…
). -
If
needs_details
→ HTTP Request “GetPlace” to fetchwebsiteUri
-
Then I continue with my enrichment pipeline
What works:
The legacy flow (old text search + place/details
) works and everything downstream gets items.
Ask:
Could someone sanity-check my approach and the GetPlace
node?
– Is the name
vs id
mapping right?
– Is this the right FieldMask for just the website?
– Any n8n gotcha with the ={{ ... }}
expression on the URL field?
Minimal reproduction:
{
“nodes”: [
{
“parameters”: {},
“id”: “dce18f7d-a525-4a80-a2ac-eb61fbc84895”,
“name”: “Start (Manual)”,
“type”: “n8n-nodes-base.manualTrigger”,
“typeVersion”: 1,
“position”: [
-672,
736
]
},
{
“parameters”: {
“url”: “=https://maps.googleapis.com/maps/api/place/textsearch/json”,
“sendQuery”: true,
“queryParameters”: {
“parameters”: [
{
“name”: “query”,
“value”: “={{$json.category}} in {{$json.city}}”
},
{
“name”: “radius”,
“value”: “={{$json.radius_m}}”
},
{
“name”: “key”,
“value”: “YOUR_GOOGLE_API_KEY”
}
]
},
“options”: {
“allowUnauthorizedCerts”: false,
“response”: {
“response”: {
“responseFormat”: “json”
}
}
}
},
“type”: “n8n-nodes-base.httpRequest”,
“typeVersion”: 4.2,
“position”: [
-336,
720
],
“id”: “387c38b9-0f88-4448-987f-dc768b532280”,
“name”: “Google Maps Suche”
},
{
“parameters”: {
“assignments”: {
“assignments”: [
{
“id”: “aac75a9a-3347-4777-9990-ba7177f158c8”,
“name”: “city”,
“value”: “Ulm, Germany”,
“type”: “string”
},
{
“id”: “b4bc0cb6-adb6-4295-a306-0b54b2ca1e2c”,
“name”: “category”,
“value”: “Immobilienmakler”,
“type”: “string”
},
{
“id”: “e6ca9dd5-9b1d-4697-9527-c9b33026cf63”,
“name”: “radius_m”,
“value”: 500,
“type”: “number”
},
{
“id”: “9586091b-c4ad-453e-a813-392763223357”,
“name”: “minRating”,
“value”: 3.8,
“type”: “number”
},
{
“id”: “e344c998-07ed-4847-b499-44ba8d7db1d3”,
“name”: “limit “,
“value”: “3”,
“type”: “string”
}
]
},
“options”: {}
},
“type”: “n8n-nodes-base.set”,
“typeVersion”: 3.4,
“position”: [
-496,
720
],
“id”: “39b91308-badf-4b3f-b8f2-d4ef2655ad72”,
“name”: “Set: Suchparameter”
},
{
“parameters”: {
“jsCode”: “// Wir holen die Mindestbewertung aus dem Set-Node:\nconst minRating = $json.minRating ?? 0;\n\n// Der HTTP-Node “Google Maps Suche” liefert sein Ergebnis in items()[0].json.results\nconst results = $items(“Google Maps Suche”)[0].json.results || ;\n\n// Filter + Normalisierung:\n// - nimm nur Einträge mit rating >= minRating\n// - gib nur die Felder weiter, die wir downstream brauchen\nreturn results\n .filter(p => (p.rating ?? 0) >= minRating)\n .map(p => ({\n json: {\n name: p.name,\n place_id: p.place_id,\n address: p.formatted_address,\n rating: p.rating || null,\n user_ratings_total: p.user_ratings_total || 0\n }\n }));\n”
},
“type”: “n8n-nodes-base.code”,
“typeVersion”: 2,
“position”: [
-192,
720
],
“id”: “6819b04b-9d14-4fb9-99c6-552e8bb3d536”,
“name”: “Normalize Places”
},
{
“parameters”: {
“url”: “https://maps.googleapis.com/maps/api/place/details/json”,
“sendQuery”: true,
“queryParameters”: {
“parameters”: [
{
“name”: “place_id”,
“value”: “={{$json.place_id}}”
},
{
“name”: “fields”,
“value”: “website,formatted_phone_number,name,geometry”
},
{
“name”: “key”,
“value”: “YOUR_GOOGLE_API_KEY”
}
]
},
“options”: {}
},
“type”: “n8n-nodes-base.httpRequest”,
“typeVersion”: 4.2,
“position”: [
-48,
720
],
“id”: “aa266f8c-aa63-4339-bd5f-110901e85c55”,
“name”: “Place Details”
},
{
“parameters”: {
“batchSize”: 5,
“options”: {}
},
“type”: “n8n-nodes-base.splitInBatches”,
“typeVersion”: 3,
“position”: [
1088,
704
],
“id”: “32dda2c2-e7eb-4855-b330-a9a0b598d728”,
“name”: “Loop Over Items”
},
{
“parameters”: {
“httpMethod”: “POST”,
“path”: “fc-completed”,
“responseMode”: “responseNode”,
“options”: {}
},
“type”: “n8n-nodes-base.webhook”,
“typeVersion”: 2.1,
“position”: [
-560,
1008
],
“id”: “1941d1aa-61a7-4044-bde7-26a30d8d7316”,
“name”: “Webhook”,
“webhookId”: “10890a40-11ea-405e-ac4e-a404fab20b4f”,
“alwaysOutputData”: false
},
{
“parameters”: {
“method”: “POST”,
“url”: “https://crawl.alphamindhub.com/v2/crawl”,
“sendBody”: true,
“specifyBody”: “json”,
“jsonBody”: “={{ $json.body }}”,
“options”: {}
},
“type”: “n8n-nodes-base.httpRequest”,
“typeVersion”: 4.2,
“position”: [
1504,
704
],
“id”: “3baf89e0-15e7-4e2d-846a-e445975955c3”,
“name”: “POST /v2/crawl1”,
“onError”: “continueErrorOutput”
},
{
“parameters”: {
“assignments”: {
“assignments”: [
{
“id”: “0e70f2f1-3cef-41ca-8ab2-2e392772381e”,
“name”: “body”,
“value”: “={}”,
“type”: “object”
},
{
“id”: “f84cc5ea-8d04-41fc-88bf-ac64acb565bc”,
“name”: “=body.url”,
“value”: “={{$json.website_normalized}}”,
“type”: “string”
},
{
“id”: “1ea47572-419e-489f-95f5-15ed876018c4”,
“name”: “body.sitemap”,
“value”: “include”,
“type”: “string”
},
{
“id”: “16239090-84c1-4502-ac12-a46fe01fbc04”,
“name”: “body.crawlEntireDomain”,
“value”: false,
“type”: “boolean”
},
{
“id”: “b27c0db2-8dc4-43d2-8bb2-ef1df2c69cea”,
“name”: “body.maxDiscoveryDepth”,
“value”: 2,
“type”: “number”
},
{
“id”: “8095cae6-2663-4668-a422-722463db1786”,
“name”: “body.ignoreQueryParameters”,
“value”: true,
“type”: “boolean”
},
{
“id”: “86241877-4f7c-465d-a3e7-7dfa31be9ba3”,
“name”: “body.limit”,
“value”: 200,
“type”: “number”
},
{
“id”: “373314d4-da4a-4534-8d87-9ab91462690d”,
“name”: “body.excludePaths”,
“value”: “[”\\.pdf$”,“^/fileadmin/.“,”^/downloads?/.”]”,
“type”: “array”
},
{
“id”: “fc8027e5-9066-4699-980e-2fb52b1966ee”,
“name”: “scrapeOptions”,
“value”: “={}”,
“type”: “object”
},
{
“id”: “81a6b9c8-92d4-415e-8322-4bddbc5147cb”,
“name”: “=body.scrapeOptions.formats”,
“value”: “[“markdown”,“html”,“links”]”,
“type”: “array”
},
{
“id”: “2c10be40-0f32-4a6f-a8d6-735ce3add744”,
“name”: “body.scrapeOptions.onlyMainContent”,
“value”: true,
“type”: “boolean”
},
{
“id”: “8868e48e-5b72-4e7e-8176-178cc3870a85”,
“name”: “body.scrapeOptions.includeTags”,
“value”: “[“main”,“article”,“h1”,“h2”,“p”,“li”,“a”,“address”,“span”]”,
“type”: “array”
},
{
“id”: “f3876320-8d90-4c64-98d7-c473c5590576”,
“name”: “body.scrapeOptions.excludeTags”,
“value”: “[“nav”,“footer”,“script”,“style”]”,
“type”: “array”
},
{
“id”: “dd010ced-2457-410c-9c1c-0d72ea2cf8b6”,
“name”: “body.scrapeOptions.waitFor”,
“value”: 0,
“type”: “number”
},
{
“id”: “e5a9dbb1-a630-4893-8e2f-733f0c63cdd3”,
“name”: “body.scrapeOptions.timeout”,
“value”: 30000,
“type”: “number”
},
{
“id”: “22f71160-d71c-4fed-8b14-903ad5e54570”,
“name”: “body.scrapeOptions.storeInCache”,
“value”: true,
“type”: “boolean”
},
{
“id”: “cf13e93e-8e07-4fe9-a735-40dde16a7133”,
“name”: “webhook”,
“value”: “={}”,
“type”: “object”
},
{
“id”: “24b63f5e-e170-4c74-a877-d92f139c1481”,
“name”: “body.webhook.url”,
“value”: “https://n8napp.alphamindhub.com/webhook/fc-completed”,
“type”: “string”
},
{
“id”: “8f24a0a0-a5dc-4563-9c3f-1f19ecc93752”,
“name”: “body.webhook.events”,
“value”: “[“completed”,“failed”]”,
“type”: “array”
},
{
“id”: “e8135b94-6087-41cb-b874-014ccd09a496”,
“name”: “body.webhook.headers.x-source”,
“value”: “firecrawl”,
“type”: “string”
},
{
“id”: “b30c9073-7492-43ce-99e6-053a8817a8e2”,
“name”: “=body.webhook.metadata.site”,
“value”: “={{ $json.website_normalized }}”,
“type”: “string”
}
]
},
“includeOtherFields”: true,
“options”: {}
},
“type”: “n8n-nodes-base.set”,
“typeVersion”: 3.4,
“position”: [
1344,
704
],
“id”: “c912020c-6423-432e-a014-d165a798d3b0”,
“name”: “Edit Fields1”
},
{
“parameters”: {
“respondWith”: “json”,
“responseBody”: “{“received”:true}\n”,
“options”: {}
},
“type”: “n8n-nodes-base.respondToWebhook”,
“typeVersion”: 1.4,
“position”: [
-352,
1184
],
“id”: “2b718d30-6946-4ae0-b774-0d0a58c93dd5”,
“name”: “Respond to Webhook1”
},
{
“parameters”: {
“mode”: “runOnceForEachItem”,
“jsCode”: “// — Code – Validate (Webhook) —\n// Mode: Run once for each item\n\n// Defensive unpack\nconst raw = ($json && typeof $json === ‘object’) ? $json : {};\nconst body = (raw.body && typeof raw.body === ‘object’) ? raw.body : {};\n\nconst hasId = !!body.id;\n\nconst out = {\n ok: hasId,\n reason: hasId ? undefined : ‘missing_id’,\n crawl_id: hasId ? body.id : null,\n url: hasId ? (body.url ||https://crawl.alphamindhub.com/v2/crawl/${body.id}
) : null,\n meta: body.metadata || {},\n raw\n};\n\n// Duplicate guard (global memory across runs)\nconst g = $getWorkflowStaticData(‘global’);\nif (!g.seenCrawls) g.seenCrawls = {};\n\nif (hasId) {\n if (g.seenCrawls[body.id]) {\n // Schon verarbeitet → hart stoppen, damit nix weiterläuft\n return { json: { ok: false, reason: ‘duplicate_crawl_ignored’, crawl_id: body.id } };\n }\n g.seenCrawls[body.id] = Date.now();\n}\n\n// genau EIN Item zurückgeben\nreturn { json: out };”
},
“type”: “n8n-nodes-base.code”,
“typeVersion”: 2,
“position”: [
-352,
1008
],
“id”: “14a4ea92-6f79-447f-b432-56d79ad258f7”,
“name”: “Code – Validate”
},
{
“parameters”: {
“jsCode”: “// Input: HTTP GET /v2/crawl/{id} response (kommt direkt aus “GET results”)\nconst res = $json;\nconst pages = res.results || res.data || res.pages || ;\n\n// Metadaten & ID aus dem Webhook-Node holen (erstes Item des Nodes)\nconst webhookItem = ($items(‘Webhook’)[0] && $items(‘Webhook’)[0].json) ? $items(‘Webhook’)[0].json : {};\nconst metaFromWebhook = webhookItem.body?.metadata || {};\nconst crawlId = webhookItem.body?.id || res.id || null;\n\nfunction uniq(arr) {\n return Array.from(new Set((arr || ).filter(Boolean).map(v => String(v).trim())));\n}\nfunction extractEmails(text) {\n const re = /[A-Z0-9._%±]+@[A-Z0-9.-]+\.[A-Z]{2,}/ig;\n return uniq((String(text || ‘’).match(re) || ));\n}\nfunction extractPhones(text) {\n const re = /(?:\+?\d{1,3}[\s.-]?)?(?:\(?\d{2,4}\)?[\s.-]?)?\d{3,4}[\s.-]?\d{3,4}/g;\n return uniq((String(text || ‘’).match(re) || ).filter(x => x.replace(/\D/g,‘’).length >= 7));\n}\nfunction extractLinks(p) {\n let links = ;\n if (Array.isArray(p.links)) links = links.concat(p.links);\n const md = p.markdown || p.content?.markdown || “”;\n const html = p.html || p.content?.html || “”;\n const mdLinks = […md.matchAll(/\[.?\]\((https?:\/\/[^\s)]+)\)/g)].map(m => m[1]);\n const hrefs = […html.matchAll(/href=\“(https?:\/\/[^”]+)\“/g)].map(m => m[1]);\n return uniq(links.concat(mdLinks, hrefs));\n}\nfunction plainText(p) {\n return p.text || p.content?.text || p.markdown || p.content?.markdown || p.html || p.content?.html || “”;\n}\n\nconst out = [];\nfor (const p of pages) {\n const title = p.title || p.meta?.title || p.metadata?.title || null;\n const desc = p.meta?.description || p.metadata?.description || p.metaDescription || null;\n const text = plainText(p);\n\n out.push({\n crawl_id: crawlId,\n source_site: metaFromWebhook.site || null,\n page_url: p.url || p.link || null,\n title,\n meta_description: desc,\n emails: extractEmails(text).slice(0, 50),\n phones: extractPhones(text).slice(0, 50),\n links: extractLinks(p).slice(0, 200),\n snippet: (String(text).replace(/\s+/g,’ ‘).trim()).slice(0, 400),\n timestamp: new Date().toISOString()\n });\n}\n\n// Run-Once-For-All => Array von Items im { json: … }-Format\nreturn out.length\n ? out.map(o => ({ json: o }))\n : [{ json: { crawl_id: crawlId, source_site: metaFromWebhook.site || null, note: “no_pages_returned”, raw: res } }];\n”
},
“type”: “n8n-nodes-base.code”,
“typeVersion”: 2,
“position”: [
240,
992
],
“id”: “80999679-ada7-4ee0-928a-88444ed09f98”,
“name”: “Normalize & Extract”
},
{
“parameters”: {
“url”: “=https://crawl.alphamindhub.com/v2/crawl/{{$json.crawl_id}}\n”,
“options”: {}
},
“type”: “n8n-nodes-base.httpRequest”,
“typeVersion”: 4.2,
“position”: [
64,
992
],
“id”: “c8f9d67c-6f27-4e8f-a5ef-bb64bd829f7c”,
“name”: “GET results”,
“retryOnFail”: true,
“waitBetweenTries”: 5000
},
{
“parameters”: {
“conditions”: {
“options”: {
“caseSensitive”: true,
“leftValue”: “”,
“typeValidation”: “strict”,
“version”: 2
},
“conditions”: [
{
“id”: “7b8181de-c3be-4574-bcdf-203393e87803”,
“leftValue”: “={{$json.ok}}”,
“rightValue”: “true”,
“operator”: {
“type”: “boolean”,
“operation”: “true”,
“singleValue”: true
}
},
{
“id”: “786c8fdf-51e6-4fe7-9183-35a0b8eafdbb”,
“leftValue”: “={{ $json.crawl_id || $json.url }}\n”,
“rightValue”: “”,
“operator”: {
“type”: “string”,
“operation”: “notEmpty”,
“singleValue”: true
}
}
],
“combinator”: “and”
},
“options”: {}
},
“type”: “n8n-nodes-base.if”,
“typeVersion”: 2.2,
“position”: [
-176,
1008
],
“id”: “0fac7325-b228-4821-8beb-7b7b999803f7”,
“name”: “If”
},
{
“parameters”: {
“jsCode”: “// Aggregator: sammelt Seiten + Emails + (optional) crawl_id/source_site\nconst items = $input.all();\n\nconst pages = [];\nconst emails = new Set();\nlet crawl_id = null;\nlet source_site = null;\n\nfor (const it of items) {\n const j = it.json || {};\n\n if (!crawl_id && j.crawl_id) crawl_id = j.crawl_id;\n if (!source_site && j.source_site) source_site = j.source_site;\n\n pages.push({\n url: j.page_url || j.metadata?.url || null,\n title: j.title || j.metadata?.title || null,\n text: (j.snippet || j.markdown || j.html || j.text || “”)\n .replace(/\s+/g, " “)\n .slice(0, 1500),\n });\n\n (j.emails || []).forEach(e => {\n if (typeof e === ‘string’) emails.add(e.trim().toLowerCase());\n });\n}\n\nreturn [{\n json: {\n crawl_id,\n source_site,\n pages: pages.slice(0, 12),\n emails: Array.from(emails).slice(0, 50)\n }\n}];\n”
},
“type”: “n8n-nodes-base.code”,
“typeVersion”: 2,
“position”: [
448,
992
],
“id”: “4387f8b1-651c-48d7-99fe-21e90d3d3879”,
“name”: “Seiten vorbereiten”
},
{
“parameters”: {
“modelId”: {
“__rl”: true,
“value”: “gpt-4o-mini”,
“mode”: “list”,
“cachedResultName”: “GPT-4O-MINI”
},
“messages”: {
“values”: [
{
“content”: “You are a cold email copywriter.\n\nConstraints:\n- Output language = {{ $items(“Sprache erkennen”)[0].json.lang || “de” }}\n- Return STRICT JSON ONLY:\n { “icebreaker”: string, “supporting_facts”: string[], “best_email”: string|null, “why_this_email”: string }\n- 1–3 sentences, max 320 chars for “icebreaker”.\n- Be concrete & non-obvious (use details from “signals” or “abstract”).\n- Do not invent emails or facts. No emojis. No extra keys.\n”,
“role”: “system”
},
{
“content”: “==Context (JSON):\n{{ JSON.stringify({\n abstract: $json.abstract,\n signals: $json.signals,\n guesses: $json.guesses,\n evidence_urls: $json.evidence_urls,\n emails: ($items(“Seiten vorbereiten”)[0].json.emails || []),\n category: ($json.category || $items(“Seiten vorbereiten”)[0].json.category || “”)\n}) }}\n\nTASK\n1) Write ONE opener following the JSON schema above, using only facts from Context.\n2) Pick best_email ONLY from “emails” (prefer owner/ceo/principal). If none fits, set best_email = null and give a one-line reason in “why_this_email”.\n3) Add a short bridge line tailored to the category and language:\n\nIf language is “de”:\n- Immobilienmakler → „… generiert kontinuierlich qualifizierte Käufer-/Verkäufer-Leads für Makler.“\n- Physiotherapie → „… bringt lokal Patienten-Anfragen.“\n- Coaching → „… bringt qualifizierte Beratungs-Leads.“\n- Sonstiges/leer → „… bringt gezielte B2B-Leads.“\n\nIf language is “en”:\n- Real estate agents → “… generates qualified buyer/seller leads for agents.”\n- Physiotherapy → “… brings local patient inquiries.”\n- Coaching/Consulting → “… brings qualified consulting leads.”\n- Otherwise → “… generates targeted B2B leads.”\n\nStyle examples (do not copy literally):\n\nDE:\n"Hey {Vorname/Team}. {nicht-offensichtliches Detail} ist mir aufgefallen. Kurz: Ich nutze ein KI-gestütztes Outreach-System, das {{category||‘B2B’}}-Leads findet und personalisiert anspricht—passt zu eurem Fokus.”\n\nEN:\n"Hey {name}. Noticed {non-obvious detail}. Quick one: I use an AI outreach system that finds {{category||‘B2B’}} leads and personalizes the opener—feels aligned with your focus.“\n”
}
]
},
“jsonOutput”: true,
“options”: {
“temperature”: 0.2
}
},
“type”: “@n8n/n8n-nodes-langchain.openAi”,
“typeVersion”: 1.8,
“position”: [
1184,
992
],
“id”: “8fdb6735-1f7d-419b-98d5-a0d39cae20cb”,
“name”: “Icebreaker generieren”,
“credentials”: {
“openAiApi”: {
“id”: “S4T1oBdhXVrYRUDA”,
“name”: “OpenAi account”
}
}
},
{
“parameters”: {
“modelId”: {
“__rl”: true,
“value”: “gpt-4o-mini”,
“mode”: “list”,
“cachedResultName”: “GPT-4O-MINI”
},
“messages”: {
“values”: [
{
“content”: “You are a precise research assistant. Summarize multiple web pages of one company into a compact abstract we can use for personalization later.\nReturn STRICT JSON:\n{\n “abstract”: string[], // 3–6 knappe Bullets (je 1 Satz)\n “signals”: string[], // 3–6 konkrete Hinweise (z.B. Produkte, Cases, Tonalität, Besonderheiten)\n “guesses”: { “industry”: string|null, “location”: string|null },\n “evidence_urls”: string[] // max. 5 aussagekräftige Seiten-URLs\n}\nNo extra keys, no prose outside JSON. Only use facts present in pages.\n”,
“role”: “system”
},
{
“content”: “=Here are the pages (JSON):\n\n{{ JSON.stringify($json.pages) }}\n\nIf helpful, consider these emails found on-site:\n{{ JSON.stringify($json.emails || []) }}\n”
}
]
},
“jsonOutput”: true,
“options”: {
“temperature”: 0.2
}
},
“type”: “@n8n/n8n-nodes-langchain.openAi”,
“typeVersion”: 1.8,
“position”: [
608,
992
],
“id”: “a67b8723-5764-447e-82ad-14454c712321”,
“name”: “Zusammenfassung”,
“credentials”: {
“openAiApi”: {
“id”: “S4T1oBdhXVrYRUDA”,
“name”: “OpenAi account”
}
}
},
{
“parameters”: {
“jsCode”: “// Eingabe: Zusammenfassung (vorheriger Node)\nconst text = [\n …($json.abstract ?? []),\n …($json.signals ?? []),\n $json.guesses?.location ?? “”,\n ($items(“Seiten vorbereiten”)[0]?.json?.source_site ?? “”)\n].join(” ").toLowerCase();\n\nlet lang = “en”;\nconst hasUmlaut = /[äöüß]/.test(text);\nconst deWords = /\b(und|für|mit|über|leistung|impressum|datenschutzerklärung|kontakt)\b/.test(text);\nconst deTld = /\.de\b/.test(text);\nconst deLoc = /\b(de|germany|deutschland|österreich|austria|schweiz|switzerland)\b/.test(text);\n\nif (hasUmlaut || deWords || deTld || deLoc) lang = “de”;\n\nreturn [{ json: { lang } }];\n”
},
“type”: “n8n-nodes-base.code”,
“typeVersion”: 2,
“position”: [
960,
992
],
“id”: “4b54e2ef-e156-45af-a88a-0e78747226fa”,
“name”: “Sprache erkennen”
},
{
“parameters”: {
“operation”: “upsert”,
“base”: {
“__rl”: true,
“value”: “app9YBBqD9iemLncP”,
“mode”: “list”,
“cachedResultName”: “Cold Outreach CRM”,
“cachedResultUrl”: “https://airtable.com/app9YBBqD9iemLncP”
},
“table”: {
“__rl”: true,
“value”: “tblr0qy5gwFQ4vV5z”,
“mode”: “list”,
“cachedResultName”: “Leads”,
“cachedResultUrl”: “https://airtable.com/app9YBBqD9iemLncP/tblr0qy5gwFQ4vV5z”
},
“columns”: {
“mappingMode”: “defineBelow”,
“value”: {
“id”: "={{ ($json.source_site || $json.website || ‘’).replace(/^https?:\/\/(www\.)?/,’').replace(/\/.$/,‘’) }}”,
“Website”: “={{ $json.source_site || $json.website || ‘’ }}”,
“Company Name”: “={{ $json.company_name || $json.company || $json.name || ‘’ }}”,
“City”: “={{ $json.city || ($json.guesses && $json.guesses.location) || ‘’ }}”,
“Category”: “={{ $json.category || $items(‘Set: Suchparameter’)[0].json.category || ‘Sonstiges’ }}”,
“Best Email”: “={{ $json.best_email || ‘’ }}”,
“First Name”: “={{ $json.first_name || ‘’ }}”,
“Last Name”: “={{ $json.last_name || ‘’ }}”,
“Language”: “={{ $items(‘Sprache erkennen’)[0].json.lang || ‘de’ }}”,
“Icebreaker”: “={{ $json.icebreaker || ‘’ }}”,
“Abstract”: “={{ Array.isArray($json.abstract) ? $json.abstract.join(‘\n’) : ($json.abstract || ‘’) }}”,
“Signals”: “={{ Array.isArray($json.signals) ? $json.signals.join(’ | ') : ($json.signals || ‘’) }}”,
“Evidence URLs”: “={{ Array.isArray($json.evidence_urls) ? $json.evidence_urls.join(‘,’) : ($json.evidence_urls || ‘’) }}”,
“Crawl ID”: “={{ $json.crawl_id || ‘’ }}”,
“Status”: “={{ $json.best_email ? ‘Enriched’ : ‘New’ }}”,
“Created At”: “={{ $json.created_at || new Date().toISOString() }}”,
“Updated At”: “={{ new Date().toISOString() }}”,
“Pages”: “={{ $json.pages || }}”,
“Events”: “={{ $json.events || }}”,
“Campaigns”: “={{ $json.campaigns || }}”,
“Lead Summary (AI)”: “={{ $json.lead_summary || ‘’ }}”,
“Personalization Angle (AI)”: “={{ $json.personalization_angle || ‘’ }}”
},
“matchingColumns”: [
“id”
],
“schema”: [
{ “id”: “id”, “displayName”: “id”, “required”: false, “defaultMatch”: true, “display”: true, “type”: “string”, “readOnly”: true, “removed”: false },
{ “id”: “Website”, “displayName”: “Website”, “required”: false, “defaultMatch”: false, “canBeUsedToMatch”: true, “display”: true, “type”: “string”, “readOnly”: false, “removed”: false },
{ “id”: “Company Name”, “displayName”: “Company Name”, “required”: false, “defaultMatch”: false, “canBeUsedToMatch”: true, “display”: true, “type”: “string”, “readOnly”: false, “removed”: false },
{ “id”: “City”, “displayName”: “City”, “required”: false, “defaultMatch”: false, “canBeUsedToMatch”: true, “display”: true, “type”: “string”, “readOnly”: false, “removed”: false },
{ “id”: “Category”, “displayName”: “Category”, “required”: false, “defaultMatch”: false, “canBeUsedToMatch”: true, “display”: true, “type”: “options”, “options”: [ { “name”: “Physiotherapie”, “value”: “Physiotherapie” }, { “name”: “Immobilienmakler”, “value”: “Immobilienmakler” }, { “name”: “Coaching”, “value”: “Coaching” }, { “name”: “SaaS”, “value”: “SaaS” }, { “name”: “Sonstiges”, “value”: “Sonstiges” } ], “readOnly”: false, “removed”: false },
{ “id”: “Best Email”, “displayName”: “Best Email”, “required”: false, “defaultMatch”: false, “canBeUsedToMatch”: true, “display”: true, “type”: “string”, “readOnly”: false, “removed”: false },
{ “id”: “First Name”, “displayName”: “First Name”, “required”: false, “defaultMatch”: false, “canBeUsedToMatch”: true, “display”: true, “type”: “string”, “readOnly”: false, “removed”: false },
{ “id”: “Last Name”, “displayName”: “Last Name”, “required”: false, “defaultMatch”: false, “canBeUsedToMatch”: true, “display”: true, “type”: “string”, “readOnly”: false, “removed”: false },
{ “id”: “Language”, “displayName”: “Language”, “required”: false, “defaultMatch”: false, “canBeUsedToMatch”: true, “display”: true, “type”: “options”, “options”: [ { “name”: “de”, “value”: “de” }, { “name”: “en”, “value”: “en” } ], “readOnly”: false, “removed”: false },
{ “id”: “Icebreaker”, “displayName”: “Icebreaker”, “required”: false, “defaultMatch”: false, “canBeUsedToMatch”: true, “display”: true, “type”: “string”, “readOnly”: false, “removed”: false },
{ “id”: “Abstract”, “displayName”: “Abstract”, “required”: false, “defaultMatch”: false, “canBeUsedToMatch”: true, “display”: true, “type”: “string”, “readOnly”: false, “removed”: false },
{ “id”: “Signals”, “displayName”: “Signals”, “required”: false, “defaultMatch”: false, “canBeUsedToMatch”: true, “display”: true, “type”: “string”, “readOnly”: false, “removed”: false },
{ “id”: “Evidence URLs”, “displayName”: “Evidence URLs”, “required”: false, “defaultMatch”: false, “canBeUsedToMatch”: true, “display”: true, “type”: “string”, “readOnly”: false, “removed”: false },
{ “id”: “Crawl ID”, “displayName”: “Crawl ID”, “required”: false, “defaultMatch”: false, “canBeUsedToMatch”: true, “display”: true, “type”: “string”, “readOnly”: false, “removed”: false },
{ “id”: “Status”, “displayName”: “Status”, “required”: false, “defaultMatch”: false, “canBeUsedToMatch”: true, “display”: true, “type”: “options”, “options”: [ { “name”: “New”, “value”: “New” }, { “name”: “Enriched”, “value”: “Enriched” }, { “name”: “Queued”, “value”: “Queued” }, { “name”: “Sent”, “value”: “Sent” }, { “name”: “Replied”, “value”: “Replied” }, { “name”: “Bad Email”, “value”: “Bad Email” }, { “name”: “Duplicate”, “value”: “Duplicate” } ], “readOnly”: false, “removed”: false },
{ “id”: “Created At”, “displayName”: “Created At”, “required”: false, “defaultMatch”: false, “canBeUsedToMatch”: true, “display”: true, “type”: “dateTime”, “readOnly”: false, “removed”: false },
{ “id”: “Updated At”, “displayName”: “Updated At”, “required”: false, “defaultMatch”: false, “canBeUsedToMatch”: true, “display”: true, “type”: “dateTime”, “readOnly”: false, “removed”: false },
{ “id”: “Pages”, “displayName”: “Pages”, “required”: false, “defaultMatch”: false, “canBeUsedToMatch”: true, “display”: true, “type”: “array”, “readOnly”: false, “removed”: false },
{ “id”: “Events”, “displayName”: “Events”, “required”: false, “defaultMatch”: false, “canBeUsedToMatch”: true, “display”: true, “type”: “array”, “readOnly”: false, “removed”: false },
{ “id”: “Campaigns”, “displayName”: “Campaigns”, “required”: false, “defaultMatch”: false, “canBeUsedToMatch”: true, “display”: true, “type”: “array”, “readOnly”: false, “removed”: false }
],
“attemptToConvertTypes”: false,
“convertFieldsToString”: false
},
“options”: {}
},
“type”: “n8n-nodes-base.airtable”,
“typeVersion”: 2.1,
“position”: [
1488,
992
],
“id”: “3c9361a8-c40c-404a-8a50-7b8b3ec7fe61”,
“name”: “Create or update a record”,
“credentials”: {
“airtableTokenApi”: {
“id”: “vkVqY26cWYx5y20f”,
“name”: “Airtable Personal Access Token account”
}
}
},
{
“parameters”: {
“resource”: “lead”,
“operation”: “addToCampaign”,
“campaign”: {
“__rl”: true,
“value”: “d9baedbb-804d-48ed-8b91-39fe2e6108c8”,
“mode”: “list”,
“cachedResultName”: “IT-Zeitarbeit DACH”
},
“email”: “={{ $json.best_email }}”,
“firstName”: “={{ $json.first_name || “” }}”,
“lastName”: “={{ $json.last_name || “” }}”,
“customFields”: {
“customFieldValues”: {
“field”: [
{
“key”: “=”
}
]
}
}
},
“type”: “n8n-nodes-instantly.instantly”,
“typeVersion”: 1,
“position”: [
1664,
992
],
“id”: “2108af0c-0a39-471d-a898-c2214dc9e85c”,
“name”: “Add lead to campaign”,
“credentials”: {
“instantlyApi”: {
“id”: “vOCa8fFZfgd8lD6G”,
“name”: “Instantly account”
}
}
},
{
“parameters”: {
“conditions”: {
“options”: {
“caseSensitive”: true,
“leftValue”: “”,
“typeValidation”: “strict”,
“version”: 2
},
“conditions”: [
{
“id”: “67436df1-93b3-465e-b61b-a520ec909689”,
“leftValue”: “={{$json.result.website}}”,
“rightValue”: “”,
“operator”: {
“type”: “string”,
“operation”: “exists”,
“singleValue”: true
}
}
],
“combinator”: “and”
},
“options”: {}
},
“type”: “n8n-nodes-base.if”,
“typeVersion”: 2.2,
“position”: [
640,
720
],
“id”: “ff6025d4-1782-48ba-af93-aaa382580cb0”,
“name”: “IF has website1”
},
{
“parameters”: {
“mode”: “runOnceForEachItem”,
“jsCode”: “// Pro Item: $json.result.website kommt aus “Place Details”\nconst rawInput = ($json.result && $json.result.website) ? String($json.result.website) : “”;\n\nlet website_raw = rawInput;\nlet website_normalized = null;\nlet note = null;\n\ntry {\n // Steuerzeichen + Whitespace raus\n const cleaned = rawInput\n .replace(/[\u0000-\u001F\u007F-\u009F]/g, ‘’)\n .replace(/\s+/g, ’ ')\n .trim();\n if (!cleaned) throw new Error(‘empty string’);\n\n // Falls Schema fehlt → https:// ergänzen\n let withScheme = /[1][a-zA-Z0-9+.-]*:\/\//.test(cleaned) ? cleaned : ‘https://’ + cleaned;\n\n // Protokoll + Host via Regex (kein URL-Objekt)\n const m = withScheme.match(/^(https?:)\/\/([^\/?#]+)(?:[\/?#]|$)/i);\n if (!m) throw new Error(‘cannot extract host’);\n\n // https erzwingen + Host sanity-check\n const protocol = ‘https:’;\n let host = m[2].toLowerCase();\n\n // evtl. trailing Punkt entfernen (z. B. “example.de.”)\n host = host.replace(/\.$/, ‘’);\n\n if (!/[2]+\.[a-z]{2,}$/i.test(host)) {\n throw new Error('host looks invalid: ’ + host);\n }\n\n website_normalized =${protocol}//${host}
;\n} catch (e) {\n note =Invalid URL parsed from Google: \"${rawInput}\" → ${e.message}
;\n}\n\nreturn { json: { website_raw, website_normalized, note } };\n”
},
“type”: “n8n-nodes-base.code”,
“typeVersion”: 2,
“position”: [
848,
704
],
“id”: “0045f2a9-7858-4992-9625-6d74765970fd”,
“name”: “Normalize Website1”
}
],
“connections”: {
“Start (Manual)”: {
“main”: [
[
{
“node”: “Set: Suchparameter”,
“type”: “main”,
“index”: 0
}
]
]
},
“Google Maps Suche”: {
“main”: [
[
{
“node”: “Normalize Places”,
“type”: “main”,
“index”: 0
}
]
]
},
“Set: Suchparameter”: {
“main”: [
[
{
“node”: “Google Maps Suche”,
“type”: “main”,
“index”: 0
}
]
]
},
“Normalize Places”: {
“main”: [
[
{
“node”: “Place Details”,
“type”: “main”,
“index”: 0
}
]
]
},
“Place Details”: {
“main”: [
[
{
“node”: “IF has website1”,
“type”: “main”,
“index”: 0
}
]
]
},
“Loop Over Items”: {
“main”: [
[
{
“node”: “Edit Fields1”,
“type”: “main”,
“index”: 0
}
],
[
{
“node”: “Loop Over Items”,
“type”: “main”,
“index”: 0
}
]
]
},
“Webhook”: {
“main”: [
[
{
“node”: “Code – Validate”,
“type”: “main”,
“index”: 0
},
{
“node”: “Respond to Webhook1”,
“type”: “main”,
“index”: 0
}
]
]
},
“Edit Fields1”: {
“main”: [
[
{
“node”: “POST /v2/crawl1”,
“type”: “main”,
“index”: 0
}
]
]
},
“Code – Validate”: {
“main”: [
[
{
“node”: “If”,
“type”: “main”,
“index”: 0
}
]
]
},
“Normalize & Extract”: {
“main”: [
[
{
“node”: “Seiten vorbereiten”,
“type”: “main”,
“index”: 0
}
]
]
},
“GET results”: {
“main”: [
[
{
“node”: “Normalize & Extract”,
“type”: “main”,
“index”: 0
}
]
]
},
“If”: {
“main”: [
[
{
“node”: “GET results”,
“type”: “main”,
“index”: 0
}
]
]
},
“Seiten vorbereiten”: {
“main”: [
[
{
“node”: “Zusammenfassung”,
“type”: “main”,
“index”: 0
}
]
]
},
“Icebreaker generieren”: {
“main”: [
[
{
“node”: “Create or update a record”,
“type”: “main”,
“index”: 0
}
]
]
},
“Zusammenfassung”: {
“main”: [
[