This workflow corresponds to n8n.io template #17193 — we link there as the canonical source.
This workflow follows the HTTP Request → Slack 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 →
{
"id": "tqW6fqm049FBLk47",
"meta": {
"builderVariant": "mcp",
"aiBuilderAssisted": true
},
"name": "Draft personalized LinkedIn outreach from buying signals with Twain",
"tags": [],
"nodes": [
{
"id": "5e7ff7cd-5089-4ecb-87fe-2273f338aece",
"name": "Signal Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
240,
528
],
"parameters": {
"path": "linkedin-signal",
"options": {},
"httpMethod": "POST"
},
"typeVersion": 2.1
},
{
"id": "6c4d49cd-5da2-4688-ab0b-d6301010eb01",
"name": "LinkedIn Settings",
"type": "n8n-nodes-base.set",
"position": [
1008,
528
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "campaign_id",
"name": "campaign_id",
"type": "string",
"value": "REPLACE_WITH_LINKEDIN_CAMPAIGN_ID"
},
{
"id": "heyreach_campaign_id",
"name": "heyreach_campaign_id",
"type": "string",
"value": "REPLACE_WITH_HEYREACH_CAMPAIGN_ID"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "3ef77793-fff5-4674-8b95-c4335165692e",
"name": "Normalize LinkedIn Lead",
"type": "n8n-nodes-base.code",
"position": [
1392,
528
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const src = $json.body ?? $json ?? {};\nconst PERSONAL = [\"gmail.com\",\"googlemail.com\",\"outlook.com\",\"hotmail.com\",\"live.com\",\"msn.com\",\"yahoo.com\",\"ymail.com\",\"icloud.com\",\"me.com\",\"mac.com\",\"proton.me\",\"protonmail.com\",\"aol.com\",\"gmx.com\",\"gmx.net\",\"zoho.com\",\"pm.me\",\"mail.com\",\"yandex.com\"];\nconst str = (v) => (v === undefined || v === null) ? \"\" : String(v);\nconst firstNonEmpty = (...vals) => { for (const v of vals) { const s = str(v).trim(); if (s) return s; } return \"\"; };\nconst linkedin_url = firstNonEmpty(src.linkedin_url, src.linkedin_profile_url, src.linkedin);\nconst name = firstNonEmpty(src.name, src.full_name, src.displayName);\nconst email = firstNonEmpty(src.email, src.work_email).toLowerCase();\nconst company = firstNonEmpty(src.company, src.company_name);\nconst company_domain = firstNonEmpty(src.company_domain, src.companyDomain);\nconst signal = firstNonEmpty(src.signal, src.signal_context, src.reason, src.context);\nconst email_domain = (email.split(\"@\")[1] || \"\").toLowerCase();\nconst is_personal_email = PERSONAL.includes(email_domain);\nconst has_work_email = Boolean(email) && !is_personal_email;\nconst has_linkedin_url = Boolean(linkedin_url);\nconst uid = firstNonEmpty(src.uid, linkedin_url, email, name);\nconst campaign_id = ($('LinkedIn Settings').first().json.campaign_id || \"REPLACE_WITH_LINKEDIN_CAMPAIGN_ID\");\nreturn {\n uid: uid,\n linkedin_url: linkedin_url,\n name: name,\n email: email,\n company: company,\n company_domain: company_domain,\n signal: signal,\n email_domain: email_domain,\n is_personal_email: is_personal_email,\n has_work_email: has_work_email,\n has_linkedin_url: has_linkedin_url,\n campaign_id: campaign_id\n};"
},
"typeVersion": 2
},
{
"id": "14e82d39-0a91-4be7-9164-2185a1551fbb",
"name": "Dedupe Leads",
"type": "n8n-nodes-base.removeDuplicates",
"position": [
1760,
528
],
"parameters": {
"options": {
"scope": "node",
"historySize": 100000
},
"operation": "removeItemsSeenInPreviousExecutions",
"dedupeValue": "={{ $json.uid }}"
},
"typeVersion": 2
},
{
"id": "3391849c-e04c-488a-9cd8-00f63eadc98d",
"name": "Has LinkedIn URL?",
"type": "n8n-nodes-base.if",
"position": [
3664,
528
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "has-li-1",
"operator": {
"type": "boolean",
"operation": "true"
},
"leftValue": "={{ $json.has_linkedin_url }}",
"rightValue": ""
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.3
},
{
"id": "0057d90d-80a6-4bf4-afed-137f847858c6",
"name": "Twain Generate Sequence",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueErrorOutput",
"maxTries": 3,
"position": [
4048,
528
],
"parameters": {
"url": "https://public.api.twain.ai/v2/Generate/Sequence",
"method": "POST",
"options": {
"timeout": 180000
},
"jsonBody": "={{ {\n campaign_id: $('Normalize LinkedIn Lead').item.json.campaign_id,\n contact: {\n linkedin_profile_url: $json.linkedin_url,\n ...($('Normalize LinkedIn Lead').item.json.has_work_email ? { work_email: $('Normalize LinkedIn Lead').item.json.email } : {}),\n },\n add_contact_to_campaign: true,\n} }}",
"sendBody": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"typeVersion": 4.4,
"waitBetweenTries": 5000
},
{
"id": "02b1b346-a8df-4eec-ab71-59efa404b28a",
"name": "Build LinkedIn Outreach",
"type": "n8n-nodes-base.code",
"position": [
4432,
528
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const api = $json;\nconst norm = $(\"Normalize LinkedIn Lead\").item.json;\nconst messages = Array.isArray(api.messages) ? api.messages : [];\nconst first = messages[0] || {};\nconst research = api.research || {};\nconst person = research.person || {};\nconst company = research.company || {};\nconst warnings = api.warnings || {};\nconst categories = Array.isArray(warnings.categories) ? warnings.categories : [];\nconst str = (v) => (v === undefined || v === null) ? \"\" : String(v);\nconst linkedin_url = str((person && person.linkedin_profile_url) || norm.linkedin_url || \"\");\nconst linkedin_line = linkedin_url ? (\":link: <\" + linkedin_url + \"|LinkedIn profile>\") : \"\";\nconst handleName = () => {\n const m = linkedin_url.match(/linkedin\\.com\\/in\\/([^/?#]+)/i);\n if (!m) return \"\";\n let h = decodeURIComponent(m[1]).replace(/-[0-9a-f]{6,}$/i, \"\").replace(/[-_]+/g, \" \").trim();\n if (!h) return \"\";\n return h.split(\" \").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(\" \");\n};\nconst person_name = str(person.name) || str(norm.name) || handleName() || \"there\";\nconst intro_text = str(first.message);\nconst sequence_preview = messages.map((m) => str(m.message)).join(\"\\n\\n--------\\n\\n\");\nconst followups = messages.slice(1);\nconst followups_preview = followups.map((m) => str(m.message)).join(\"\\n\\n--------\\n\\n\");\nconst warning_line =\n warnings && warnings.has_warnings\n ? \":warning: *Warnings:* \" +\n ((categories || []).join(\", \") || \"flagged\") +\n (warnings.summary ? \" \u2014 \" + warnings.summary : \"\")\n : \"\";\nreturn {\n intro_text: intro_text,\n sequence_preview: sequence_preview || \"No messages returned.\",\n followups_preview: followups_preview,\n followups_count: followups.length,\n step_count: messages.length,\n person_name: person_name,\n person_title: str(person.title),\n company_name: str(company.name) || str(norm.company),\n linkedin_url: linkedin_url,\n linkedin_line: linkedin_line,\n signal: str(norm.signal),\n link: str(api.link),\n warning_line: warning_line,\n warnings_summary: str(warnings.summary)\n};"
},
"typeVersion": 2
},
{
"id": "bf4d6fc4-7785-489c-9d5c-a3881fae4e90",
"name": "Slack: LinkedIn outreach ready",
"type": "n8n-nodes-base.slack",
"onError": "continueRegularOutput",
"position": [
4800,
528
],
"parameters": {
"text": "=*LinkedIn outreach ready \u2014 send manually*\n*{{ $('Build LinkedIn Outreach').item.json.person_name }}*{{ $('Build LinkedIn Outreach').item.json.person_title ? ' \u00b7 ' + $('Build LinkedIn Outreach').item.json.person_title : '' }}{{ $('Build LinkedIn Outreach').item.json.company_name ? ' \u00b7 ' + $('Build LinkedIn Outreach').item.json.company_name : '' }}\n{{ $('Build LinkedIn Outreach').item.json.linkedin_url ? '<' + $('Build LinkedIn Outreach').item.json.linkedin_url + '|LinkedIn profile>\\n' : '' }}{{ $('Build LinkedIn Outreach').item.json.signal ? $('Build LinkedIn Outreach').item.json.signal + '\\n' : '' }}{{ $('Build LinkedIn Outreach').item.json.warning_line ? $('Build LinkedIn Outreach').item.json.warning_line + '\\n' : '' }}```\n{{ $('Build LinkedIn Outreach').item.json.intro_text }}\n```{{ $('Build LinkedIn Outreach').item.json.followups_count ? '\\n*Follow-ups (' + $('Build LinkedIn Outreach').item.json.followups_count + ')*\\n```\\n' + $('Build LinkedIn Outreach').item.json.followups_preview + '\\n```' : '' }}\n<{{ $('Build LinkedIn Outreach').item.json.link }}|Open lead in Twain>",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "name",
"value": "n8n-workflows"
},
"otherOptions": {
"unfurl_links": false,
"unfurl_media": false,
"includeLinkToWorkflow": false
}
},
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.5
},
{
"id": "d23352c8-d2ea-4489-92af-2681f47bdfda",
"name": "Slack: Twain error",
"type": "n8n-nodes-base.slack",
"onError": "continueRegularOutput",
"position": [
4432,
752
],
"parameters": {
"text": "=*Twain sequence generation failed*\n*{{ $('Normalize LinkedIn Lead').item.json.name || $('Normalize LinkedIn Lead').item.json.email || 'Unknown lead' }}*{{ $('Normalize LinkedIn Lead').item.json.company ? ' \u00b7 ' + $('Normalize LinkedIn Lead').item.json.company : '' }}\n{{ String($json.error?.message ?? $json.message ?? 'Unknown error').replace(/^\\s*\\d{3}\\s*-\\s*/, '').trim() }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "name",
"value": "n8n-workflows"
},
"otherOptions": {
"unfurl_links": false,
"unfurl_media": false,
"includeLinkToWorkflow": false
}
},
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.5
},
{
"id": "d7eb0d22-6868-4e0c-a69c-50f8f91552dc",
"name": "Slack: Skipped (no LinkedIn)",
"type": "n8n-nodes-base.slack",
"onError": "continueRegularOutput",
"position": [
4048,
752
],
"parameters": {
"text": "=*Skipped \u2014 no LinkedIn URL*\n*{{ $('Normalize LinkedIn Lead').item.json.name || $('Normalize LinkedIn Lead').item.json.email || 'Unknown lead' }}*{{ $('Normalize LinkedIn Lead').item.json.company ? ' \u00b7 ' + $('Normalize LinkedIn Lead').item.json.company : '' }}\nNo profile URL resolved \u2014 add one upstream or enable a lookup provider.",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "name",
"value": "n8n-workflows"
},
"otherOptions": {
"unfurl_links": false,
"unfurl_media": false,
"includeLinkToWorkflow": false
}
},
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.5
},
{
"id": "d736051c-3928-4862-9eb4-bfef7bdce616",
"name": "Schedule: HubSpot segment poll",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
240,
704
],
"parameters": {
"rule": {
"interval": [
{
"field": "minutes",
"minutesInterval": 15
}
]
}
},
"typeVersion": 1.3
},
{
"id": "a8dc0e7a-744b-405b-b7ac-f416f90aea39",
"name": "HubSpot: List memberships",
"type": "n8n-nodes-base.httpRequest",
"position": [
448,
704
],
"parameters": {
"url": "https://api.hubapi.com/crm/v3/lists/REPLACE_WITH_HUBSPOT_LIST_ID/memberships?limit=100",
"options": {
"timeout": 30000
},
"authentication": "genericCredentialType",
"genericAuthType": "httpBearerAuth"
},
"credentials": {
"hubspotApi": {
"name": "<your credential>"
},
"httpBearerAuth": {
"name": "<your credential>"
},
"hubspotOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.4
},
{
"id": "3be7b848-7a0c-47e7-9570-3c893c0b4e23",
"name": "Push to HeyReach",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"position": [
4800,
752
],
"parameters": {
"url": "https://api.heyreach.io/api/public/campaign/AddLeadsToCampaignV2",
"method": "POST",
"options": {
"timeout": 60000
},
"jsonBody": "={{ {\n campaignId: Number($('LinkedIn Settings').first().json.heyreach_campaign_id) || $('LinkedIn Settings').first().json.heyreach_campaign_id,\n accountLeadPairs: [{\n lead: {\n profileUrl: $('Build LinkedIn Outreach').item.json.linkedin_url,\n firstName: ($('Build LinkedIn Outreach').item.json.person_name || \"\").trim().split(\" \")[0] || \"\",\n lastName: ($('Build LinkedIn Outreach').item.json.person_name || \"\").trim().split(\" \").slice(1).join(\" \") || \"\",\n companyName: $('Build LinkedIn Outreach').item.json.company_name || \"\",\n customUserFields: [{ name: \"twain_message\", value: $('Build LinkedIn Outreach').item.json.intro_text || \"\" }]\n }\n }]\n} }}",
"sendBody": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.4
},
{
"id": "7256dddd-4562-47d9-810c-7ad8df577294",
"name": "Needs LinkedIn Lookup?",
"type": "n8n-nodes-base.if",
"position": [
2144,
528
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "needs-li-1",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ !$('Normalize LinkedIn Lead').item.json.has_linkedin_url && Boolean($('Normalize LinkedIn Lead').item.json.email) }}",
"rightValue": ""
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.3
},
{
"id": "3ff02443-b2b6-47eb-aa02-8a3c86fd7d0a",
"name": "LinkedIn Lookup \u2014 Findymail",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"position": [
2528,
304
],
"parameters": {
"url": "https://app.findymail.com/api/search/reverse-email",
"method": "POST",
"options": {
"timeout": 30000
},
"jsonBody": "={{ { email: $('Normalize LinkedIn Lead').item.json.email, with_profile: true } }}",
"sendBody": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpBearerAuth"
},
"credentials": {
"httpBearerAuth": {
"name": "<your credential>"
},
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.4
},
{
"id": "a988ae64-30cc-4efd-bc4a-a7281a8759d2",
"name": "Apply Resolved LinkedIn",
"type": "n8n-nodes-base.code",
"position": [
2912,
528
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// Resolve LinkedIn URL from the provider response.\n// Adjust the field below to match YOUR provider's response shape.\nconst resp = $json || {};\nconst linkedin_url = resp.linkedin_url ?? resp.linkedin_profile_url ?? resp.url ?? resp.contact?.linkedin_url ?? resp.profile?.linkedin_url ?? resp.person?.linkedin_url ?? resp.data?.linkedin_url ?? resp.response?.linkedin_url ?? \"\";\n\n// Carry forward all normalized fields, overriding only the LinkedIn ones.\nconst n = $('Normalize LinkedIn Lead').item.json;\nreturn {\n ...n,\n linkedin_url: linkedin_url,\n has_linkedin_url: Boolean(linkedin_url)\n};\n"
},
"typeVersion": 2
},
{
"id": "c5b04acf-05bf-4f0a-aa5b-812a78becbbe",
"name": "Resolved Contact",
"type": "n8n-nodes-base.merge",
"position": [
3280,
528
],
"parameters": {},
"typeVersion": 3.2
},
{
"id": "d7e43ed3-d177-496b-9ca6-4b24bc250746",
"name": "Sticky: 3 \u00b7 Resolve LinkedIn (optional)",
"type": "n8n-nodes-base.stickyNote",
"position": [
2736,
-368
],
"parameters": {
"color": 7,
"width": 644,
"height": 390,
"content": "## LinkedIn lookup (optional)\nResolves a LinkedIn URL from the email when a lead has none, then feeds it to Twain. The whole block is DISABLED by default.\nTo turn it on: pick ONE provider node below, add its API-key credential (Header Auth), adjust the response field in `Apply Resolved LinkedIn` if needed, and ENABLE that one node.\nNodes are pre-configured with each provider's API URL/auth/body:\n\u2022 Findymail (default) \u2014 POST reverse-email \u00b7 `Authorization: Bearer <API key>`\n\u2022 Apollo \u2014 POST people/match \u00b7 `X-Api-Key`\n\u2022 People Data Labs \u2014 GET person/enrich \u00b7 `X-Api-Key`\n\u2022 Proxycurl \u2014 GET resolve/email \u00b7 `Authorization: Bearer`\nTwain also resolves LinkedIn from the email during research; this is for resolving it BEFORE Twain or with your own provider."
},
"typeVersion": 1
},
{
"id": "339779b1-085e-4767-8545-17232ddf0725",
"name": "LinkedIn Lookup \u2014 Apollo",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"disabled": true,
"position": [
2528,
80
],
"parameters": {
"url": "https://api.apollo.io/api/v1/people/match",
"method": "POST",
"options": {
"timeout": 30000
},
"jsonBody": "={{ { email: $('Normalize LinkedIn Lead').item.json.email } }}",
"sendBody": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"typeVersion": 4.4
},
{
"id": "83174fca-b188-40dd-ab0d-f1582fccce83",
"name": "LinkedIn Lookup \u2014 People Data Labs",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"disabled": true,
"position": [
2528,
-144
],
"parameters": {
"url": "=https://api.peopledatalabs.com/v5/person/enrich?email={{ encodeURIComponent($('Normalize LinkedIn Lead').item.json.email) }}",
"options": {
"timeout": 30000
},
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"typeVersion": 4.4
},
{
"id": "7aaf56e4-c6ef-4028-adae-021f5e34f123",
"name": "LinkedIn Lookup \u2014 Proxycurl",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"disabled": true,
"position": [
2528,
-368
],
"parameters": {
"url": "=https://nubela.co/proxycurl/api/linkedin/profile/resolve/email?email={{ encodeURIComponent($('Normalize LinkedIn Lead').item.json.email) }}",
"options": {
"timeout": 30000
},
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"typeVersion": 4.4
},
{
"id": "b8aa2230-ed8f-4441-bce2-f636bc359068",
"name": "HubSpot: Read contacts",
"type": "n8n-nodes-base.httpRequest",
"position": [
688,
704
],
"parameters": {
"url": "https://api.hubapi.com/crm/v3/objects/contacts/batch/read",
"method": "POST",
"options": {
"timeout": 30000
},
"jsonBody": "={{ { properties: [\"email\",\"firstname\",\"lastname\",\"company\",\"jobtitle\",\"hs_linkedin_url\"], inputs: ($('HubSpot: List memberships').first().json.results || []).map(r => ({ id: r.recordId })) } }}",
"sendBody": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpBearerAuth"
},
"credentials": {
"httpBearerAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.4
},
{
"id": "38241ddc-4462-45d7-ab6e-968fa2117c54",
"name": "Map HubSpot Contacts",
"type": "n8n-nodes-base.code",
"position": [
928,
704
],
"parameters": {
"jsCode": "const res = $json || {};\nconst results = Array.isArray(res.results) ? res.results : [];\nreturn results.map((r) => {\n const p = r.properties || {};\n const name = [p.firstname, p.lastname].filter(Boolean).join(' ').trim();\n return { json: {\n email: (p.email || '').toLowerCase(),\n name: name,\n company: p.company || '',\n linkedin_url: p.hs_linkedin_url || '',\n signal: 'Entered HubSpot list',\n hubspot_id: r.id\n } };\n});"
},
"typeVersion": 2
},
{
"id": "83b57b7b-d651-4bd4-9414-23a5fa2a041f",
"name": "Sticky: What this does",
"type": "n8n-nodes-base.stickyNote",
"position": [
192,
-432
],
"parameters": {
"width": 848,
"height": 692,
"content": "## LinkedIn Signal Outreach \u2014 a researched, personalized draft the moment a lead is in-market\n\nWhen a buying signal fires (a form, a HubSpot list, a webhook), this workflow turns it into a **ready-to-send LinkedIn intro + follow-ups** in your Slack about 20 seconds later \u2014 you review and send from your own profile. It never auto-sends.\n\n### Why Twain\nThe research is what makes outreach land and what doesn't scale \u2014 10+ hours of profile-reading for a week of leads, so it gets skipped and the copy gets generic. Twain's campaign **Agent** reads each person and their company and writes the message on a real angle, from a brief you set once in plain language. Same research and personalization a good rep would do by hand, in seconds, at list scale.\n\n### What it does\n\u2022 Normalizes the signal, dedupes, and (optionally) resolves a LinkedIn URL from the email\n\u2022 Calls Twain to research the lead and draft the LinkedIn intro + follow-ups\n\u2022 Posts the draft to Slack for a human to send \u2014 optionally stages it in a HeyReach campaign\n\n### How to set up\n1. Twain account + a LinkedIn intro/follow-up campaign at **https://www.twain.ai** \u2014 its Agent writes the copy.\n2. Paste the `campaign_id` into **LinkedIn Settings**; add the Twain (Header Auth `X-Api-Key`) + Slack credentials.\n3. Point your signal source at the webhook. Generate/Sequence costs 1 credit per lead.\n\nBuilt with Twain \u2014 https://www.twain.ai"
},
"typeVersion": 1
},
{
"id": "090049c4-31b3-417b-accc-7a5db9c8c10d",
"name": "Sticky: 1 \u00b7 Signal intake & setup",
"type": "n8n-nodes-base.stickyNote",
"position": [
192,
288
],
"parameters": {
"color": 7,
"width": 1150,
"height": 612,
"content": "### 1 \u00b7 Signal intake & setup\nA buying signal hits the **webhook** (a form, a HubSpot list, any tool that means \"reach out now\"). Set your Twain campaign in **LinkedIn Settings** (`campaign_id`) \u2014 its Agent writes the copy.\n**Wire:** Twain Header Auth (`X-Api-Key`) + Slack. Webhook auth starts at `none`; add a shared secret before going live. Generate/Sequence = 1 credit per lead."
},
"typeVersion": 1
},
{
"id": "301e45e5-a258-443b-980e-44bd68a84656",
"name": "Sticky: 2 \u00b7 Normalize & dedupe",
"type": "n8n-nodes-base.stickyNote",
"position": [
1360,
288
],
"parameters": {
"color": 7,
"width": 560,
"height": 596,
"content": "### 2 \u00b7 Normalize & dedupe\nParse the signal into a clean lead, then skip anyone already processed (dedupe by a stable id). Reruns are safe."
},
"typeVersion": 1
},
{
"id": "5c3dc3e0-c2bb-4d7f-ad44-0a4432776c29",
"name": "Sticky: 4 \u00b7 Generate with Twain",
"type": "n8n-nodes-base.stickyNote",
"position": [
3552,
256
],
"parameters": {
"color": 7,
"width": 700,
"height": 692,
"content": "### 4 \u00b7 Generate with Twain\nTwain researches the lead and drafts the LinkedIn intro + follow-ups from your campaign's Agent. Retries handle 429s. For research-heavy volume, switch to `POST /v2/Generate/Contact/Async` with a callback so a cold-lead generation can't hit the 60s gateway timeout."
},
"typeVersion": 1
},
{
"id": "9210a4b4-0ad9-4fa6-bada-b615efe631b6",
"name": "Sticky: 5 \u00b7 Review & route",
"type": "n8n-nodes-base.stickyNote",
"position": [
4304,
208
],
"parameters": {
"color": 7,
"width": 820,
"height": 744,
"content": "### 5 \u00b7 Review & route\nThe draft is posted to **Slack for a human to send** \u2014 this template never auto-sends.\n\u2022 Free LinkedIn: message 1st-degree connections + limited invites. Cold non-connections need InMail (Sales Navigator).\n\u2022 n8n has no native LinkedIn DM node, so sending stays manual.\n\nOptional (disabled): **Push to HeyReach** stages the copy in a HeyReach campaign \u2014 set `heyreach_campaign_id` in *LinkedIn Settings*. Runs through your LinkedIn account; ToS/ban risk, Sales Navigator recommended."
},
"typeVersion": 1
},
{
"id": "b2a8dabc-7564-4e06-8376-ddd7361a8693",
"name": "Sticky: HubSpot segment source (optional)",
"type": "n8n-nodes-base.stickyNote",
"position": [
192,
912
],
"parameters": {
"color": 7,
"width": 980,
"height": 300,
"content": "### HubSpot segment source (optional \u00b7 disabled)\nDrive this from HubSpot instead of the webhook: a contact entering a dynamic list is the signal. Enable *Schedule \u2192 List memberships (v3) \u2192 Read contacts \u2192 Map \u2192 LinkedIn Settings*.\nAuth: a HubSpot **Service Key** / private-app token in a **Bearer Auth** credential \u2014 not OAuth2 (n8n blocks it in HTTP nodes). Scopes: `crm.lists.read` + `crm.objects.contacts.read`. Set your list id (replace `REPLACE_WITH_HUBSPOT_LIST_ID`). Keep disabled until configured."
},
"typeVersion": 1
}
],
"active": true,
"settings": {
"binaryMode": "separate",
"availableInMCP": true,
"executionOrder": "v1"
},
"versionId": "27de602a-24cc-4439-ad1d-a7bfc0f56216",
"nodeGroups": [],
"connections": {
"Dedupe Leads": {
"main": [
[
{
"node": "Needs LinkedIn Lookup?",
"type": "main",
"index": 0
}
]
]
},
"Signal Webhook": {
"main": [
[
{
"node": "LinkedIn Settings",
"type": "main",
"index": 0
}
]
]
},
"Resolved Contact": {
"main": [
[
{
"node": "Has LinkedIn URL?",
"type": "main",
"index": 0
}
]
]
},
"Has LinkedIn URL?": {
"main": [
[
{
"node": "Twain Generate Sequence",
"type": "main",
"index": 0
}
],
[
{
"node": "Slack: Skipped (no LinkedIn)",
"type": "main",
"index": 0
}
]
]
},
"LinkedIn Settings": {
"main": [
[
{
"node": "Normalize LinkedIn Lead",
"type": "main",
"index": 0
}
]
]
},
"Map HubSpot Contacts": {
"main": [
[
{
"node": "LinkedIn Settings",
"type": "main",
"index": 0
}
]
]
},
"HubSpot: Read contacts": {
"main": [
[
{
"node": "Map HubSpot Contacts",
"type": "main",
"index": 0
}
]
]
},
"Needs LinkedIn Lookup?": {
"main": [
[
{
"node": "LinkedIn Lookup \u2014 Findymail",
"type": "main",
"index": 0
}
],
[
{
"node": "Resolved Contact",
"type": "main",
"index": 1
}
]
]
},
"Apply Resolved LinkedIn": {
"main": [
[
{
"node": "Resolved Contact",
"type": "main",
"index": 0
}
]
]
},
"Build LinkedIn Outreach": {
"main": [
[
{
"node": "Slack: LinkedIn outreach ready",
"type": "main",
"index": 0
},
{
"node": "Push to HeyReach",
"type": "main",
"index": 0
}
]
]
},
"Normalize LinkedIn Lead": {
"main": [
[
{
"node": "Dedupe Leads",
"type": "main",
"index": 0
}
]
]
},
"Twain Generate Sequence": {
"main": [
[
{
"node": "Build LinkedIn Outreach",
"type": "main",
"index": 0
}
],
[
{
"node": "Slack: Twain error",
"type": "main",
"index": 0
}
]
]
},
"HubSpot: List memberships": {
"main": [
[
{
"node": "HubSpot: Read contacts",
"type": "main",
"index": 0
}
]
]
},
"LinkedIn Lookup \u2014 Findymail": {
"main": [
[
{
"node": "Apply Resolved LinkedIn",
"type": "main",
"index": 0
}
]
]
},
"Schedule: HubSpot segment poll": {
"main": [
[
{
"node": "HubSpot: List memberships",
"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.
httpBearerAuthhttpHeaderAuthhubspotApihubspotOAuth2ApislackApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow captures buying-signal leads via a webhook (or optionally polls a HubSpot list), generates a personalized LinkedIn intro and follow-up sequence with Twain, and posts the ready-to-send draft to Slack, with an optional step to stage the lead in a HeyReach campaign.…
Source: https://n8n.io/workflows/17193/ — 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.
Automate your entire Instagram carousel publishing pipeline from a single webhook call. This workflow receives a product collection payload, loops through each slide image, uploads every asset via Upl
📦 Automated Instagram Product Drop via uploadtourl
This workflow automates the post-publish process for YouTube videos, combining advanced SEO optimization, cross-platform promotion, and analytics reporting. It is designed for creators, marketers, and
generación_publicaciones (workflow 3) webhook all platforms decrypt publicar git. Uses dataTable, linkedIn, httpRequest. Webhook trigger; 50 nodes.
This template is ideal for sales teams, recruiters, business development professionals, and relationship managers who need to monitor changes in their network's LinkedIn profiles. Perfect for agencies