AutomationFlowsSlack & Telegram › Sync Website Visitors From Rb2b to Attio CRM via Slack with Deal Creation

Sync Website Visitors From Rb2b to Attio CRM via Slack with Deal Creation

ByAnna Bui @annabuiplayground on n8n.io

This n8n template automatically syncs website visitors identified by RB2B into your Attio CRM, creating comprehensive contact records and associated sales deals for immediate follow-up.

Event trigger★★★★☆ complexity16 nodesSlack TriggerHTTP Request
Slack & Telegram Trigger: Event Nodes: 16 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #6973 — we link there as the canonical source.

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 →

Download .json
{
  "nodes": [
    {
      "id": "8c33bf1c-c341-445c-b7cc-3537a59bae1c",
      "name": "RB2B New Message",
      "type": "n8n-nodes-base.slackTrigger",
      "position": [
        -120,
        180
      ],
      "parameters": {
        "options": {},
        "trigger": [
          "message"
        ],
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C0712SMCKRN",
          "cachedResultName": "website-visitors"
        }
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "a725c2de-1074-4b6b-8ff5-ba76b5a5dcbb",
      "name": "Cleanup Data",
      "type": "n8n-nodes-base.code",
      "position": [
        240,
        180
      ],
      "parameters": {
        "jsCode": "function splitName(fullName) {\n  if (!fullName || typeof fullName !== 'string') {\n    return { firstName: '', lastName: '' };\n  }\n  const parts = fullName.trim().split(/\\s+/);\n  if (parts.length === 0) {\n    return { firstName: '', lastName: '' };\n  }\n  const firstName = parts[0];\n  const lastName = parts.length > 1 ? parts.slice(1).join(' ') : '';\n  return { firstName, lastName, fullName };\n}\n\nfunction extractDataFromSlackMessage(messageData) {\n  // Get the message text\n  const messageText = messageData.text || '';\n  \n  // Initialize extracted data\n  let fullName = '';\n  let linkedin = '';\n  let title = '';\n  let company = '';\n  let email = '';\n  let location = '';\n  let urls = [];\n  \n  // Extract LinkedIn URL from rich text blocks if available\n  if (messageData.blocks && messageData.blocks.length > 0) {\n    for (const block of messageData.blocks) {\n      if (block.type === 'rich_text' && block.elements) {\n        for (const element of block.elements) {\n          if (element.type === 'rich_text_section' && element.elements) {\n            for (const richElement of element.elements) {\n              if (richElement.type === 'link' && richElement.url && richElement.url.includes('linkedin.com')) {\n                linkedin = richElement.url;\n                break;\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n  \n  // Helper function to clean up Slack link format and extract display text\n  function cleanSlackLinks(text) {\n    // Replace <URL|Display_Text> with just Display_Text\n    return text.replace(/<[^|>]+\\|([^>]+)>/g, '$1')\n               .replace(/&amp;/g, '&')\n               .trim();\n  }\n  \n  // Function to extract URLs from text\n  function extractUrlsFromText(text) {\n    const urls = [];\n    // Match URLs in Slack format <URL|Display_Text> or <URL>\n    const urlMatches = text.match(/<(https?:\\/\\/[^|>]+)/g);\n    if (urlMatches) {\n      urlMatches.forEach(match => {\n        try {\n          const url = match.replace('<', '');\n          if (!urls.includes(url)) {\n            urls.push(url);\n          }\n        } catch (e) {\n          // Invalid URL, skip\n        }\n      });\n    }\n    return urls;\n  }\n  \n  // Function to extract all URLs from rich text blocks\n  function extractUrlsFromBlocks(blocks) {\n    const allUrls = [];\n    \n    if (!blocks || !Array.isArray(blocks)) return allUrls;\n    \n    for (const block of blocks) {\n      if (block.type === 'rich_text' && block.elements) {\n        for (const element of block.elements) {\n          if (element.type === 'rich_text_section' && element.elements) {\n            for (const richElement of element.elements) {\n              if (richElement.type === 'link' && richElement.url) {\n                const url = richElement.url;\n                if (!allUrls.includes(url)) {\n                  allUrls.push(url);\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n    return allUrls;\n  }\n  \n  // Parse the text content using regex patterns\n  const nameMatch = messageText.match(/\\*Name\\*:\\s*([^\\n]+)/);\n  if (nameMatch) {\n    fullName = nameMatch[1].trim();\n  }\n  \n  const titleMatch = messageText.match(/\\*Title\\*:\\s*([^\\n]+)/);\n  if (titleMatch) {\n    title = cleanSlackLinks(titleMatch[1]);\n  }\n  \n  const companyMatch = messageText.match(/\\*Company\\*:\\s*([^\\n]+)/);\n  if (companyMatch) {\n    const rawCompany = companyMatch[1];\n    company = cleanSlackLinks(rawCompany);\n    // Extract URLs from company field\n    const textUrls = extractUrlsFromText(rawCompany);\n    urls = textUrls;\n  }\n  \n  // Extract location\n  const locationMatch = messageText.match(/\\*Location\\*:\\s*([^\\n]+)/);\n  if (locationMatch) {\n    location = cleanSlackLinks(locationMatch[1]);\n  }\n  \n  // Extract email address\n  const emailMatch = messageText.match(/\\*Email\\*:\\s*([^\\n\\s]+)/);\n  if (emailMatch) {\n    email = emailMatch[1].trim();\n  }\n  \n  // Alternative email extraction patterns\n  if (!email) {\n    // Try to find email in the general text\n    const generalEmailMatch = messageText.match(/([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})/);\n    if (generalEmailMatch) {\n      email = generalEmailMatch[1];\n    }\n  }\n  \n  // Extract all URLs from rich text blocks\n  const blockUrls = extractUrlsFromBlocks(messageData.blocks);\n  \n  // Combine all URLs\n  const allUrls = [...new Set([...urls, ...blockUrls])];\n  \n  // If LinkedIn not found in blocks, try to extract from text\n  if (!linkedin) {\n    const linkedinMatch = messageText.match(/\\*LinkedIn\\*:\\s*<([^>]+)>/);\n    if (linkedinMatch) {\n      linkedin = linkedinMatch[1];\n    }\n  }\n  \n  return {\n    fullName,\n    linkedin,\n    title,\n    company,\n    email,\n    location,\n    urls: allUrls\n  };\n}\n\nfunction extractDomainsFromUrls(urls) {\n  console.log('URLs received:', urls);\n  \n  if (!urls || !Array.isArray(urls)) {\n    console.log('No URLs or not an array');\n    return [];\n  }\n  \n  const domains = [];\n  \n  urls.forEach(url => {\n    console.log('Processing URL:', url);\n    \n    // Skip LinkedIn URLs\n    if (url.includes('linkedin.com')) {\n      console.log('Skipping LinkedIn URL:', url);\n      return;\n    }\n    \n    try {\n      // Simple regex approach as backup\n      const domainMatch = url.match(/https?:\\/\\/(www\\.)?([^\\/]+)/);\n      if (domainMatch && domainMatch[2]) {\n        let domain = domainMatch[2];\n        console.log('Domain extracted via regex:', domain);\n        \n        if (domain && !domains.includes(domain)) {\n          domains.push(domain);\n          console.log('Added domain:', domain);\n        }\n      } else {\n        console.log('No domain match found for:', url);\n      }\n    } catch (e) {\n      console.log(`Error processing URL: ${url}`, e.message);\n    }\n  });\n  \n  console.log('Final domains:', domains);\n  return domains;\n}\n\n// Main execution\nconst inputData = $input.first().json;\nconst extractedData = extractDataFromSlackMessage(inputData);\nconst names = splitName(extractedData.fullName);\n\n// Extract domains from URLs for Attio\nconst domains = extractDomainsFromUrls(extractedData.urls);\n\nreturn [\n  {\n    json: {\n      full_name: names.fullName,\n      first_name: names.firstName,\n      last_name: names.lastName,\n      linkedin: extractedData.linkedin,\n      title: extractedData.title,\n      company: extractedData.company,\n      email: extractedData.email,\n      location: extractedData.location,\n      urls: extractedData.urls,\n      domains: domains,\n      primary_domain: domains.length > 0 ? domains[0] : null\n    }\n  }\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "d69f2ede-b405-42ed-8075-805e14a0d1a3",
      "name": "Get Person",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        620,
        180
      ],
      "parameters": {
        "url": "https://api.attio.com/v2/objects/people/records/query",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"filter\": {\n    \"email_addresses\": {\n      \"$contains\": \"{{ $('Cleanup Data').first().json.email }}\"\n    }\n  },\n  \"page\": {\n    \"size\": 1\n  }\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType"
      },
      "credentials": {
        "httpCustomAuth": {
          "name": "<your credential>"
        },
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "5adc0ad5-b286-40ff-8dec-e52b3b3d71b4",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -900,
        -120
      ],
      "parameters": {
        "width": 640,
        "height": 900,
        "content": "## \ud83c\udfaf Sync RB2B Website Visitors to Attio CRM with Deal Creation\n\n### This n8n template automatically captures website visitors identified by RB2B and syncs them to your Attio CRM with intelligent deal management.\n\nPerfect for sales teams who want to turn anonymous website traffic into qualified leads without manual data entry!\n\n### How it works\n* RB2B sends website visitor notifications to your designated Slack channel\n* The workflow extracts visitor details (name, email, company, LinkedIn, location) from Slack messages\n* It checks if the person already exists in Attio CRM to prevent duplicates\n* Creates new contacts with complete visitor information or updates existing records\n* Automatically creates sales deals for new leads or updates existing deal stages\n* Tags all leads with \"RB2B Website Visitor\" source for easy identification\n\n### How to use\n* Configure RB2B to send visitor notifications to a dedicated Slack channel\n* Set up your Slack bot credentials and select the RB2B channel\n* Add your Attio API credentials with proper permissions\n* Customize deal stages, naming conventions, and field mappings as needed\n* The workflow handles both new and existing contacts intelligently\n\n### Requirements\n* RB2B account with Slack integration enabled\n* Attio CRM account with API access\n* Slack workspace with bot permissions\n\n### Good to know\n* The workflow prevents duplicate contacts by checking email addresses\n* Deal creation logic differs for new vs existing contacts\n* All RB2B leads are tagged with source tracking for follow-up\n\nHappy Lead Converting!"
      },
      "typeVersion": 1
    },
    {
      "id": "f58703c3-833d-4c70-b4ed-1b4d74700bbe",
      "name": "Create Person",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1300,
        380
      ],
      "parameters": {
        "url": "https://api.attio.com/v2/objects/people/records",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"data\": {\n    \"values\": {\n      \"name\": [\n        {\n          \"first_name\": \"{{ $('Cleanup Data').item.json.first_name }}\",\n          \"last_name\": \"{{ $('Cleanup Data').item.json.last_name }}\",\n          \"full_name\": \"{{ $('Cleanup Data').item.json.full_name }}\"\n        }\n      ],\n      \"email_addresses\": [\"{{ $('Cleanup Data').item.json.email }}\"]\n    }\n  }\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType"
      },
      "credentials": {
        "httpCustomAuth": {
          "name": "<your credential>"
        },
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "d4822bab-4756-4663-90af-2f8cbb6a5aef",
      "name": "Create Deal",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1520,
        380
      ],
      "parameters": {
        "url": "https://api.attio.com/v2/objects/deals/records",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"data\": {\n    \"values\": {\n      \"name\": \"Deal with {{ $json.data.values.name[0].full_name }}\",\n      \"stage\": \"RB2B Website Visitor\",\n      \"value\": 0,\n      \"owner\": \"yourname@emaildomain.com\",\n      \"associated_people\": [\n        {\n          \"target_object\": \"people\",\n          \"target_record_id\": \"{{ $json.data.id.record_id }}\"\n        }\n      ]\n    }\n  }\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "0c325d8d-5258-4d0e-8f78-74b9581e8ab2",
      "name": "Create Associated Deal",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1620,
        200
      ],
      "parameters": {
        "url": "https://api.attio.com/v2/objects/deals/records",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"data\": {\n    \"values\": {\n      \"name\": \"Deal with {{ $('Get Person').item.json.data[0].values.name[0].full_name }}\",\n      \"stage\": \"RB2B Website Visitor\",\n      \"value\": 0,\n      \"owner\": \"yourname@emaildomain.com\",\n      \"associated_people\": [\n        {\n          \"target_object\": \"people\",\n          \"target_record_id\": \"{{ $('Get Person').item.json.data[0].id.record_id }}\"\n        }\n      ]\n    }\n  }\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "c4f2d0b7-f32c-4f0d-9b55-7ca011b2e828",
      "name": "Update Deal Stage",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1480,
        0
      ],
      "parameters": {
        "url": "=https://api.attio.com/v2/objects/deals/records/{{ $('Get Person').item.json.data[0].values.associated_deals[0].target_record_id }}",
        "method": "PATCH",
        "options": {},
        "jsonBody": "{\n  \"data\": {\n    \"values\": {\n      \"stage\": \"RB2B Website Visitor\"\n    }\n  }\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "cf2fd591-a51b-4334-ade2-6dc5b10c8b6e",
      "name": "Person Exists?",
      "type": "n8n-nodes-base.if",
      "position": [
        940,
        180
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "d838154e-4857-4180-b2d4-30e021e95e39",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.data && $json.data.length > 0 }}",
              "rightValue": "0"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "4db29d8f-f712-4f9a-95b9-6396d05a4819",
      "name": "Switch",
      "type": "n8n-nodes-base.switch",
      "position": [
        1260,
        100
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "Already has associated deal",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "d99a0062-cfe0-45bf-9475-7d250786ac00",
                    "operator": {
                      "type": "boolean",
                      "operation": "true",
                      "singleValue": true
                    },
                    "leftValue": "={{ $json.data[0].values.associated_deals.length > 0 }}",
                    "rightValue": ""
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "No associated deal",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "07564302-add4-4389-9c8b-0a1d8e3b90a1",
                    "operator": {
                      "type": "boolean",
                      "operation": "true",
                      "singleValue": true
                    },
                    "leftValue": "={{ $json.data[0].values.associated_deals.length === 0 }}",
                    "rightValue": ""
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {},
        "looseTypeValidation": true
      },
      "typeVersion": 3.2
    },
    {
      "id": "7b9b7d43-ac35-40bd-b2e7-1db00250ce60",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -180,
        -100
      ],
      "parameters": {
        "color": 7,
        "width": 300,
        "height": 240,
        "content": "## 1. RB2B Slack Trigger\n[Read more about the Slack Trigger node](https://docs.n8n.io/integrations/builtin/trigger-nodes/n8n-nodes-base.slacktrigger)\n\nMonitors your designated Slack channel for new website visitor notifications from RB2B. Each notification contains structured visitor data including contact details and website activity."
      },
      "typeVersion": 1
    },
    {
      "id": "da56bb42-8627-48a2-9067-6c798f728b1e",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        180,
        -100
      ],
      "parameters": {
        "color": 7,
        "width": 300,
        "height": 240,
        "content": "## 2. Parse RB2B Visitor Data\n[Read more about the Code node](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.code)\n\nExtracts and structures visitor information from RB2B Slack messages. Handles name splitting, URL extraction, domain parsing, and data cleaning to prepare for CRM import."
      },
      "typeVersion": 1
    },
    {
      "id": "5a5d5ea1-4d5c-42c5-a5d9-1090e2769c93",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        540,
        -100
      ],
      "parameters": {
        "color": 7,
        "width": 300,
        "height": 240,
        "content": "## 3. Check if Person Exists in Attio\n[Read more about the HTTP Request node](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.httprequest)\n\nSearches Attio CRM using the visitor's email address to prevent duplicate contact creation. This ensures data integrity and proper lead management."
      },
      "typeVersion": 1
    },
    {
      "id": "66b73760-9085-49e1-b4f6-11397177ba2d",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        840,
        360
      ],
      "parameters": {
        "color": 7,
        "width": 300,
        "height": 300,
        "content": "## 4. Intelligent Deal Management\n[Read more about the Switch node](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.switch)\n\nSmart logic that handles deal creation differently based on contact status:\n- New contacts: Creates person record first, then associated deal\n- Existing contacts: Checks for existing deals and either updates or creates new ones"
      },
      "typeVersion": 1
    },
    {
      "id": "6eda464a-2a2f-49e5-91bd-a2abe92c537c",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1840,
        60
      ],
      "parameters": {
        "color": 7,
        "width": 440,
        "height": 180,
        "content": "## 5. Create/Update Attio Records\n[Read more about Attio API](https://developers.attio.com/)\n\nFinal step that creates new contact records and deals in Attio CRM. All RB2B leads are tagged with source information and assigned appropriate deal stages for sales follow-up."
      },
      "typeVersion": 1
    },
    {
      "id": "3192726d-1b78-4ab5-9193-06fb1a28e20f",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -140,
        440
      ],
      "parameters": {
        "color": 3,
        "width": 460,
        "height": 260,
        "content": "### Setup Required!\nBefore activating this workflow:\n1. Configure RB2B \u2192 Slack integration\n2. Add Slack bot credentials to trigger node\n3. Add Attio API credentials to all HTTP nodes\n4. Select correct Slack channel in trigger\n5. Test with sample RB2B notification\n\nMissing credentials will cause workflow failures!"
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Switch": {
      "main": [
        [
          {
            "node": "Update Deal Stage",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Create Associated Deal",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Person": {
      "main": [
        [
          {
            "node": "Person Exists?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cleanup Data": {
      "main": [
        [
          {
            "node": "Get Person",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Person": {
      "main": [
        [
          {
            "node": "Create Deal",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Person Exists?": {
      "main": [
        [
          {
            "node": "Switch",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Create Person",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "RB2B New Message": {
      "main": [
        [
          {
            "node": "Cleanup Data",
            "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.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This n8n template automatically syncs website visitors identified by RB2B into your Attio CRM, creating comprehensive contact records and associated sales deals for immediate follow-up.

Source: https://n8n.io/workflows/6973/ — original creator credit. Request a take-down →

More Slack & Telegram workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Slack & Telegram

Type in Slack. Walk away. Get a professional PDF report and a structured Excel fix sheet delivered to Google Drive and posted back in your Slack thread — fully automated, zero manual work.

Compression, HTTP Request, Google Drive +3
Slack & Telegram

Create-Issue-Linear-Slack. Uses linear, postgres, slackTrigger, httpRequest. Event-driven trigger; 33 nodes.

Linear, Postgres, Slack Trigger +2
Slack & Telegram

This workflow automates the full company enrichment pipeline: Simply import CSV company lists to Slack and save time on enrichment and CRM maintenance. It processes uploaded files, extracts company do

Slack Trigger, Slack, HTTP Request +3
Slack & Telegram

This workflow enables seamless, bidirectional communication between WhatsApp and Slack using n8n. It automates the reception, processing, and forwarding of messages (text, media, and documents) betwee

WhatsApp Trigger, Slack, Slack Trigger +2
Slack & Telegram

Automatically convert structured Slack messages into Jira issues with parsed titles, descriptions, and priorities. This workflow also downloads file attachments from Slack (e.g., screenshots, logs, or

Slack Trigger, Jira, Slack +1