1 line
13 KiB
JSON
1 line
13 KiB
JSON
{"name":"Lead Enrich Core","nodes":[{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","name":"Webhook","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[0,0],"parameters":{"path":"lead-enrich","httpMethod":"POST","responseMode":"lastNode","options":{}},"webhookId":"lead-enrich"},{"id":"b2c3d4e5-f6a7-8901-bcde-f12345678901","name":"Input Mapper","type":"n8n-nodes-base.set","typeVersion":3.4,"position":[300,0],"parameters":{"mode":"manual","assignments":{"assignments":[{"id":"c3d4e5f6-a7b8-9012-cdef-123456789012","name":"lead.email","value":"={{ $json.body?.email || $json.email || '' }}","type":"string"},{"id":"d4e5f6a7-b8c9-0123-defa-234567890123","name":"lead.first_name","value":"={{ $json.body?.first_name || $json.first_name || '' }}","type":"string"},{"id":"e5f6a7b8-c9d0-1234-efab-345678901234","name":"lead.last_name","value":"={{ $json.body?.last_name || $json.last_name || '' }}","type":"string"},{"id":"f6a7b8c9-d0e1-2345-fabc-456789012345","name":"lead.company_name","value":"={{ $json.body?.company_name || $json.company_name || '' }}","type":"string"},{"id":"a7b8c9d0-e1f2-3456-abcd-567890123456","name":"lead.source","value":"={{ $json.body?.source || $json.source || 'webhook' }}","type":"string"},{"id":"b8c9d0e1-f2a3-4567-bcde-678901234567","name":"lead.submitted_at","value":"={{ $now.toISO() }}","type":"string"},{"id":"c9d0e1f2-a3b4-5678-cdef-789012345678","name":"lead.raw_payload","value":"={{ $json }}","type":"object"}]},"options":{}}},{"id":"d0e1f2a3-b4c5-6789-defa-890123456789","name":"Clean & Validate","type":"n8n-nodes-base.code","typeVersion":2,"position":[600,0],"parameters":{"mode":"runOnceForAllItems","jsCode":"const items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n const lead = item.json.lead;\n\n // Trim all string fields\n for (const key of Object.keys(lead)) {\n if (typeof lead[key] === 'string') {\n lead[key] = lead[key].trim();\n }\n }\n\n // Lowercase email\n lead.email = (lead.email || '').toLowerCase();\n\n // Validate email format\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n if (!lead.email || !emailRegex.test(lead.email)) {\n item.json._validation_error = true;\n item.json._error_reason = !lead.email\n ? 'Email is missing or empty'\n : `Invalid email format: ${lead.email}`;\n } else {\n item.json._validation_error = false;\n }\n\n // Ensure submitted_at exists\n if (!lead.submitted_at) {\n lead.submitted_at = new Date().toISOString();\n }\n\n results.push(item);\n}\n\nreturn results;"}},{"id":"e1f2a3b4-c5d6-7890-efab-901234567890","name":"Validation Router","type":"n8n-nodes-base.if","typeVersion":2,"position":[900,0],"parameters":{"conditions":{"options":{"caseSensitive":true,"leftValue":"","rightValue":""},"conditions":[{"id":"f2a3b4c5-d6e7-8901-fabc-012345678901","leftValue":"={{ $json._validation_error }}","rightValue":true,"operator":{"type":"boolean","operation":"equals"}}],"combinator":"and"},"options":{}}},{"id":"a3b4c5d6-e7f8-9012-abcd-123456789012","name":"Dead Letter","type":"n8n-nodes-base.set","typeVersion":3.4,"position":[1200,-200],"parameters":{"mode":"manual","assignments":{"assignments":[{"id":"b4c5d6e7-f8a9-0123-bcde-234567890123","name":"error","value":true,"type":"boolean"},{"id":"c5d6e7f8-a9b0-1234-cdef-345678901234","name":"error_reason","value":"={{ $json._error_reason }}","type":"string"},{"id":"d6e7f8a9-b0c1-2345-defa-456789012345","name":"lead","value":"={{ $json.lead }}","type":"object"},{"id":"e7f8a9b0-c1d2-3456-efab-567890123456","name":"timestamp","value":"={{ $now.toISO() }}","type":"string"}]},"options":{}}},{"id":"f8a9b0c1-d2e3-4567-fabc-678901234567","name":"Dead Letter Response","type":"n8n-nodes-base.respondToWebhook","typeVersion":1.1,"position":[1500,-200],"parameters":{"respondWith":"json","responseBody":"={{ JSON.stringify({ error: true, error_reason: $json.error_reason, lead: $json.lead, timestamp: $json.timestamp }) }}","options":{"responseCode":422}}},{"id":"a9b0c1d2-e3f4-5678-abcd-789012345678","name":"Test Mode Check","type":"n8n-nodes-base.if","typeVersion":2,"position":[1200,200],"parameters":{"conditions":{"options":{"caseSensitive":true,"leftValue":"","rightValue":""},"conditions":[{"id":"b0c1d2e3-f4a5-6789-bcde-890123456789","leftValue":"={{ $env.TEST_MODE }}","rightValue":"true","operator":{"type":"string","operation":"equals"}}],"combinator":"and"},"options":{}}},{"id":"c1d2e3f4-a5b6-7890-cdef-901234567890","name":"Mock Enrichment","type":"n8n-nodes-base.set","typeVersion":3.4,"position":[1500,100],"parameters":{"mode":"manual","assignments":{"assignments":[{"id":"d2e3f4a5-b6c7-8901-defa-012345678901","name":"lead","value":"={{ $json.lead }}","type":"object"},{"id":"e3f4a5b6-c7d8-9012-efab-123456789012","name":"enrichment.provider","value":"mock","type":"string"},{"id":"f4a5b6c7-d8e9-0123-fabc-234567890123","name":"enrichment.person.title","value":"Head of Marketing","type":"string"},{"id":"a5b6c7d8-e9f0-1234-abcd-345678901234","name":"enrichment.person.linkedin_url","value":"https://linkedin.com/in/test-person","type":"string"},{"id":"b6c7d8e9-f0a1-2345-bcde-456789012345","name":"enrichment.person.city","value":"London","type":"string"},{"id":"c7d8e9f0-a1b2-3456-cdef-567890123456","name":"enrichment.person.state","value":"England","type":"string"},{"id":"d8e9f0a1-b2c3-4567-defa-678901234567","name":"enrichment.person.country","value":"United Kingdom","type":"string"},{"id":"e9f0a1b2-c3d4-5678-efab-789012345678","name":"enrichment.person.seniority","value":"Director","type":"string"},{"id":"f0a1b2c3-d4e5-6789-fabc-890123456789","name":"enrichment.organisation.name","value":"Acme Corp","type":"string"},{"id":"a1b2c3d4-e5f6-7890-abcd-901234567890","name":"enrichment.organisation.domain","value":"acmecorp.com","type":"string"},{"id":"b2c3d4e5-f6a7-8901-bcde-012345678901","name":"enrichment.organisation.industry","value":"Software","type":"string"},{"id":"c3d4e5f6-a7b8-9012-cdef-123456789013","name":"enrichment.organisation.estimated_employees","value":250,"type":"number"},{"id":"d4e5f6a7-b8c9-0123-defa-234567890124","name":"enrichment.organisation.annual_revenue","value":5000000,"type":"number"},{"id":"e5f6a7b8-c9d0-1234-efab-345678901235","name":"enrichment.organisation.linkedin_url","value":"https://linkedin.com/company/acmecorp","type":"string"},{"id":"f6a7b8c9-d0e1-2345-fabc-456789012346","name":"enrichment.organisation.website_url","value":"https://acmecorp.com","type":"string"},{"id":"a7b8c9d0-e1f2-3456-abcd-567890123457","name":"enrichment.enrichment_error","value":false,"type":"boolean"},{"id":"b8c9d0e1-f2a3-4567-bcde-678901234568","name":"enrichment.credits_consumed","value":0,"type":"number"},{"id":"c9d0e1f2-a3b4-5678-cdef-789012345679","name":"enrichment.rate_limit_remaining","value":null,"type":"null"},{"id":"d0e1f2a3-b4c5-6789-defa-890123456780","name":"enrichment.credits_low","value":false,"type":"boolean"},{"id":"e1f2a3b4-c5d6-7890-efab-901234567891","name":"grading.profile_grade","value":"Full Profile","type":"string"},{"id":"f2a3b4c5-d6e7-8901-fabc-012345678902","name":"grading.fields_populated","value":13,"type":"number"},{"id":"a3b4c5d6-e7f8-9012-abcd-123456789013","name":"grading.graded_at","value":"={{ $now.toISO() }}","type":"string"}]},"options":{}}},{"id":"b4c5d6e7-f8a9-0123-bcde-234567890124","name":"Apollo HTTP Request","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[1500,350],"parameters":{"method":"POST","url":"https://api.apollo.io/api/v1/people/match","sendHeaders":true,"headerParameters":{"parameters":[{"name":"Content-Type","value":"application/json"},{"name":"x-api-key","value":"={{ $env.APOLLO_API_KEY }}"}]},"sendBody":true,"specifyBody":"json","jsonBody":"={{ JSON.stringify({ email: $json.lead.email, first_name: $json.lead.first_name, last_name: $json.lead.last_name, organization_name: $json.lead.company_name }) }}","options":{"response":{"response":{"fullResponse":true}},"retry":{"maxTries":3,"waitBetweenTries":5000},"timeout":30000},"onError":"continueRegularOutput"}},{"id":"c5d6e7f8-a9b0-1234-cdef-345678901235","name":"Enrichment Mapper & Grader","type":"n8n-nodes-base.code","typeVersion":2,"position":[1800,350],"parameters":{"mode":"runOnceForAllItems","jsCode":"const items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n const response = item.json;\n const lead = response.lead || item.json.lead;\n\n // Initialise enrichment structure\n const enrichment = {\n provider: 'apollo',\n person: {\n title: null,\n linkedin_url: null,\n city: null,\n state: null,\n country: null,\n seniority: null\n },\n organisation: {\n name: null,\n domain: null,\n industry: null,\n estimated_employees: null,\n annual_revenue: null,\n linkedin_url: null,\n website_url: null\n },\n enrichment_error: false,\n credits_consumed: 0,\n rate_limit_remaining: null,\n credits_low: false\n };\n\n // Check for HTTP errors via the response metadata\n const statusCode = response.statusCode || response.$response?.statusCode || 200;\n\n // Extract rate limit headers if available\n const headers = response.headers || response.$response?.headers || {};\n const rateRemaining = headers['x-minute-requests-left'];\n if (rateRemaining !== undefined) {\n enrichment.rate_limit_remaining = parseInt(rateRemaining, 10);\n enrichment.credits_low = enrichment.rate_limit_remaining < 50;\n }\n\n // Handle response based on status\n if (statusCode === 429) {\n enrichment.enrichment_error = true;\n } else if (statusCode >= 400) {\n enrichment.enrichment_error = true;\n } else {\n // Look for person data in the response body\n const body = response.body || response;\n const person = body.person || null;\n\n if (person && person !== null) {\n const org = person.organization || {};\n\n enrichment.person.title = person.title || null;\n enrichment.person.linkedin_url = person.linkedin_url || null;\n enrichment.person.city = person.city || null;\n enrichment.person.state = person.state || null;\n enrichment.person.country = person.country || null;\n enrichment.person.seniority = person.seniority || null;\n\n enrichment.organisation.name = org.name || null;\n enrichment.organisation.domain = org.primary_domain || null;\n enrichment.organisation.industry = org.industry || null;\n enrichment.organisation.estimated_employees = org.estimated_num_employees || null;\n enrichment.organisation.annual_revenue = org.annual_revenue || null;\n enrichment.organisation.linkedin_url = org.linkedin_url || null;\n enrichment.organisation.website_url = org.website_url || null;\n\n enrichment.credits_consumed = body.credits_consumed || 1;\n }\n // else: person is null — no match, all fields stay null, no error, no credits\n }\n\n // Grade the profile\n const hasCompany = enrichment.organisation.name !== null;\n const hasTitle = enrichment.person.title !== null;\n const hasEmployees = enrichment.organisation.estimated_employees !== null;\n\n let profileGrade;\n if (enrichment.enrichment_error || !hasCompany) {\n profileGrade = 'Limited';\n } else if (hasCompany && hasTitle && hasEmployees) {\n profileGrade = 'Full Profile';\n } else {\n profileGrade = 'Partial Profile';\n }\n\n // Count populated fields\n const personFields = Object.values(enrichment.person).filter(v => v !== null).length;\n const orgFields = Object.values(enrichment.organisation).filter(v => v !== null).length;\n\n const grading = {\n profile_grade: profileGrade,\n fields_populated: personFields + orgFields,\n graded_at: new Date().toISOString()\n };\n\n results.push({\n json: {\n lead,\n enrichment,\n grading\n }\n });\n}\n\nreturn results;"}},{"id":"d6e7f8a9-b0c1-2345-defa-456789012346","name":"Success Response","type":"n8n-nodes-base.respondToWebhook","typeVersion":1.1,"position":[2100,200],"parameters":{"respondWith":"json","responseBody":"={{ JSON.stringify($json) }}","options":{"responseCode":200}}}],"connections":{"Webhook":{"main":[[{"node":"Input Mapper","type":"main","index":0}]]},"Input Mapper":{"main":[[{"node":"Clean & Validate","type":"main","index":0}]]},"Clean & Validate":{"main":[[{"node":"Validation Router","type":"main","index":0}]]},"Validation Router":{"main":[[{"node":"Dead Letter","type":"main","index":0}],[{"node":"Test Mode Check","type":"main","index":0}]]},"Dead Letter":{"main":[[{"node":"Dead Letter Response","type":"main","index":0}]]},"Test Mode Check":{"main":[[{"node":"Mock Enrichment","type":"main","index":0}],[{"node":"Apollo HTTP Request","type":"main","index":0}]]},"Mock Enrichment":{"main":[[{"node":"Success Response","type":"main","index":0}]]},"Apollo HTTP Request":{"main":[[{"node":"Enrichment Mapper & Grader","type":"main","index":0}]]},"Enrichment Mapper & Grader":{"main":[[{"node":"Success Response","type":"main","index":0}]]}},"settings":{"executionOrder":"v1","callerPolicy":"workflowsFromSameOwner","availableInMCP":false},"active":false,"versionId":"38989a79-82f9-4e79-bc14-9e64fca988a9","id":"sVEZDlrPOdjpvsMO","description":null,"staticData":null,"meta":null,"pinData":null,"activeVersionId":null,"updatedAt":"2026-03-27T23:06:40.776Z","createdAt":"2026-03-27T23:06:40.776Z","isArchived":false,"versionCounter":1,"triggerCount":0} |