AutomationFlowsSocial Media › Enrich LinkedIn Profiles with Apollo

Enrich LinkedIn Profiles with Apollo

Original n8n title: Enrich Linkedin Profiles with Apollo and Display Candidate Pages in the Browser

ByRahul Joshi @rahul08 on n8n.io

Automate LinkedIn profile enrichment and transform raw URLs into beautifully formatted candidate profile pages using n8n and the Apollo.io API 🔍. This workflow receives a LinkedIn URL via webhook, fetches enriched person and company data, normalizes the response, and generates a…

Webhook trigger★★★★☆ complexity10 nodesHTTP Request
Social Media Trigger: Webhook Nodes: 10 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #13793 — 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
{
  "id": "a2zGiFAlPVt1SNgz",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Apollo LinkedIn Profile Viewer",
  "tags": [],
  "nodes": [
    {
      "id": "694b75fc-cb9f-45d6-b1a7-6153ddb57bdd",
      "name": "Receive LinkedIn URL via Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -848,
        512
      ],
      "parameters": {
        "path": "compose-workflow",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "lastNode"
      },
      "typeVersion": 1
    },
    {
      "id": "24433e6b-4ac3-4848-90a8-5f3af0752e61",
      "name": "Fetch Profile from Apollo API",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -576,
        512
      ],
      "parameters": {
        "url": "=https://api.apollo.io/api/v1/people/match?reveal_personal_emails=false&reveal_phone_number=false' \\",
        "method": "POST",
        "options": {},
        "sendQuery": true,
        "sendHeaders": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "linkedin_url",
              "value": "={{ $json.body.linkedin_url }}"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "x-api-key",
              "value": "hGMCJPhktjuW1qIAxHQesw"
            },
            {
              "name": "accept",
              "value": "application/json"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "4781e833-0d78-410f-8f5c-d010933e7329",
      "name": "Normalize & Extract Profile Fields",
      "type": "n8n-nodes-base.code",
      "onError": "continueRegularOutput",
      "position": [
        -208,
        512
      ],
      "parameters": {
        "jsCode": "const person = $json.person;\n\nif (!person) {\n  return [];\n}\n\n// Get current job\nconst currentJob = (person.employment_history || []).find(job => job.current === true) || {};\n\n// Build clean output\nreturn [\n  {\n    full_name: person.name || null,\n    first_name: person.first_name || null,\n    last_name: person.last_name || null,\n    linkedin_url: person.linkedin_url || null,\n    title: person.title || null,\n    headline: person.headline || null,\n    email: person.email || null,\n    email_status: person.email_status || null,\n    city: person.city || null,\n    state: person.state || null,\n    country: person.country || null,\n    seniority: person.seniority || null,\n\n    current_company: currentJob.organization_name || null,\n    current_role: currentJob.title || null,\n    current_role_start_date: currentJob.start_date || null,\n\n    total_experience_count: (person.employment_history || []).length,\n\n    organization_name: person.organization?.name || null,\n    organization_industry: person.organization?.industry || null,\n    organization_employee_count: person.organization?.estimated_num_employees || null,\n    organization_website: person.organization?.website_url || null,\n    organization_linkedin: person.organization?.linkedin_url || null,\n\n    organization_technologies: person.organization?.technology_names || []\n  }\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "40e8cb6b-ef61-4f22-87cc-4184ca9af63b",
      "name": "Build HTML Profile Cards",
      "type": "n8n-nodes-base.code",
      "position": [
        128,
        512
      ],
      "parameters": {
        "jsCode": "// Collect all profiles dynamically\nconst profiles = items.map(item => item.json);\n\n// Safe value helper\nconst safe = (val) => {\n  if (!val) return \"N/A\";\n  return val;\n};\n\n// Generate dynamic cards\nconst cards = profiles.map(profile => {\n\n  const techStack = (profile.organization_technologies || [])\n    .map(tech => `<span class=\"tech\">${tech}</span>`)\n    .join(\"\");\n\n  return `\n    <div class=\"card\">\n      <div class=\"header\">\n        <div class=\"name\">${safe(profile.full_name)}</div>\n        <div class=\"role\">${safe(profile.title)}</div>\n        <div class=\"headline\">${safe(profile.headline)}</div>\n      </div>\n\n      <div class=\"section\">\n        <div><strong>Email:</strong> ${safe(profile.email)} (${safe(profile.email_status)})</div>\n        <div><strong>Location:</strong> ${safe(profile.city)}, ${safe(profile.state)}, ${safe(profile.country)}</div>\n        <div><strong>Seniority:</strong> ${safe(profile.seniority)}</div>\n        <div><strong>Total Experience:</strong> ${safe(profile.total_experience_count)} roles</div>\n      </div>\n\n      <div class=\"section\">\n        <div><strong>Current Company:</strong> ${safe(profile.current_company)}</div>\n        <div><strong>Current Role:</strong> ${safe(profile.current_role)}</div>\n        <div><strong>Start Date:</strong> ${safe(profile.current_role_start_date)}</div>\n      </div>\n\n      <div class=\"section\">\n        <div><strong>Industry:</strong> ${safe(profile.organization_industry)}</div>\n        <div><strong>Employees:</strong> ${safe(profile.organization_employee_count)}</div>\n        <div><strong>Website:</strong> <a href=\"${safe(profile.organization_website)}\" target=\"_blank\">${safe(profile.organization_website)}</a></div>\n        <div><strong>LinkedIn:</strong> <a href=\"${safe(profile.linkedin_url)}\" target=\"_blank\">View Profile</a></div>\n      </div>\n\n      <div class=\"section\">\n        <strong>Technology Stack:</strong>\n        <div class=\"tech-container\">\n          ${techStack || \"N/A\"}\n        </div>\n      </div>\n    </div>\n  `;\n}).join(\"\");\n\n\n// Final HTML page\nconst html = `\n<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"UTF-8\">\n<title>Candidate Profiles</title>\n<style>\nbody {\n  font-family: 'Segoe UI', sans-serif;\n  background: #f4f6f9;\n  padding: 40px;\n}\n.card {\n  background: #ffffff;\n  padding: 25px;\n  margin-bottom: 30px;\n  border-radius: 14px;\n  box-shadow: 0 6px 18px rgba(0,0,0,0.08);\n}\n.header {\n  margin-bottom: 15px;\n}\n.name {\n  font-size: 22px;\n  font-weight: 600;\n}\n.role {\n  color: #444;\n  margin-top: 4px;\n}\n.headline {\n  font-size: 14px;\n  color: #777;\n  margin-top: 6px;\n}\n.section {\n  margin-top: 18px;\n  font-size: 14px;\n}\n.tech-container {\n  margin-top: 10px;\n}\n.tech {\n  display: inline-block;\n  background: #eef2ff;\n  padding: 6px 10px;\n  margin: 4px;\n  border-radius: 6px;\n  font-size: 12px;\n}\na {\n  color: #3b82f6;\n  text-decoration: none;\n}\na:hover {\n  text-decoration: underline;\n}\n</style>\n</head>\n<body>\n<h2>Candidate Profiles</h2>\n${cards}\n</body>\n</html>\n`;\n\nreturn [\n  {\n    json: {\n      html: html\n    }\n  }\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "3ad6eded-fcba-4305-bf5e-5f0ee789fb4c",
      "name": "Return HTML Profile Page",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        336,
        512
      ],
      "parameters": {
        "options": {},
        "respondWith": "text",
        "responseBody": "={{$node['Build HTML Profile Cards'].json['html']}}"
      },
      "typeVersion": 1.5
    },
    {
      "id": "30e3b485-ed0b-48e8-aebb-6886be7f4cae",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1520,
        208
      ],
      "parameters": {
        "color": 3,
        "width": 520,
        "height": 804,
        "content": "## \ud83d\udd0d Apollo LinkedIn Profile Viewer\n\n### How it works\nThis workflow accepts a LinkedIn profile URL via a POST webhook request and returns a fully rendered HTML page displaying enriched candidate profile data sourced from the Apollo.io API.\n\n1. **Receive LinkedIn URL via Webhook** \u2014 A POST request is sent to the webhook endpoint with a `linkedin_url` in the request body.\n2. **Fetch Profile from Apollo API** \u2014 The LinkedIn URL is passed to Apollo's `/people/match` endpoint to retrieve enriched person and organization data.\n3. **Normalize & Extract Profile Fields** \u2014 Raw Apollo response is cleaned and structured into flat, usable fields (name, title, email, company, tech stack, etc.).\n4. **Build HTML Profile Cards** \u2014 Structured profile data is rendered into a styled HTML card layout.\n5. **Return HTML Profile Page** \u2014 The final HTML is sent back as the webhook response, which can be rendered directly in a browser.\n\n### Setup steps\n1. Copy the webhook URL from the **Receive LinkedIn URL via Webhook** node and use it as your POST endpoint.\n2. Replace the hardcoded `x-api-key` value in **Fetch Profile from Apollo API** with your own Apollo.io API key.\n3. Activate the workflow.\n4. Send a POST request with body `{ \"linkedin_url\": \"https://linkedin.com/in/example\" }` to receive the HTML response.\n\n### Customization\n- Modify the HTML/CSS inside **Build HTML Profile Cards** to match your brand or layout preferences.\n- Add additional Apollo fields by extending the mapping in **Normalize & Extract Profile Fields**."
      },
      "typeVersion": 1
    },
    {
      "id": "f334da27-2eed-40dd-a826-b52b46086fce",
      "name": "Section \u2013 Webhook Entry",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -944,
        288
      ],
      "parameters": {
        "width": 260,
        "height": 200,
        "content": "## \ud83d\udce5 Webhook Entry Point\nReceives a POST request containing a `linkedin_url` in the body. This is the sole trigger for the workflow \u2014 no scheduler or manual trigger is used."
      },
      "typeVersion": 1
    },
    {
      "id": "1a7cf214-3ba9-4866-9615-ae88544a2e12",
      "name": "Warning \u2013 Apollo API Key",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -656,
        736
      ],
      "parameters": {
        "color": 2,
        "width": 280,
        "height": 180,
        "content": "\u26a0\ufe0f **Apollo API Key Required**\nReplace the hardcoded `x-api-key` header value with your own Apollo.io API key. Using an invalid or expired key will cause this node to fail silently or return empty data. Monitor your Apollo plan's rate limits to avoid unexpected quota exhaustion."
      },
      "typeVersion": 1
    },
    {
      "id": "b583ab77-39cf-4a8a-85b7-10cc0deb9cc5",
      "name": "Section \u2013 Data Processing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -512,
        288
      ],
      "parameters": {
        "width": 420,
        "height": 200,
        "content": "## \ud83d\udd04 Data Processing\nNormalizes the raw Apollo API response and maps person and organization fields into a clean flat object. Errors are suppressed \u2014 if no `person` object is returned, the node outputs nothing and the workflow stops gracefully."
      },
      "typeVersion": 1
    },
    {
      "id": "fd12f228-2c47-4a5b-b949-b15cbff81292",
      "name": "Section \u2013 HTML Output",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        16,
        288
      ],
      "parameters": {
        "width": 420,
        "height": 200,
        "content": "## \ud83d\uddbc\ufe0f HTML Rendering & Response\nGenerates a styled multi-card HTML page from the normalized profile data, then returns it directly as the webhook HTTP response. The output can be opened in any browser."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "568161c8-6d2d-4b61-a49b-cad70cd5da45",
  "connections": {
    "Build HTML Profile Cards": {
      "main": [
        [
          {
            "node": "Return HTML Profile Page",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Profile from Apollo API": {
      "main": [
        [
          {
            "node": "Normalize & Extract Profile Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Receive LinkedIn URL via Webhook": {
      "main": [
        [
          {
            "node": "Fetch Profile from Apollo API",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize & Extract Profile Fields": {
      "main": [
        [
          {
            "node": "Build HTML Profile Cards",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Pro

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

About this workflow

Automate LinkedIn profile enrichment and transform raw URLs into beautifully formatted candidate profile pages using n8n and the Apollo.io API 🔍. This workflow receives a LinkedIn URL via webhook, fetches enriched person and company data, normalizes the response, and generates a…

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

More Social Media workflows → · Browse all categories →

Related workflows

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

Social Media

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

HTTP Request, N8N Nodes Uploadtourl, Slack
Social Media

This workflow leverages n8n to automate LinkedIn content creation from start to finish. Upload an image and quote through a web form, and get a professionally designed post with AI-generated captions,

HTTP Request, LinkedIn, Edit Image
Social Media

📦 Automated Instagram Product Drop via uploadtourl

HTTP Request, Airtable, Slack +1
Social Media

Automate the most popular growth hack on Instagram: "Comment 'DM' to get the link!"

HTTP Request
Social Media

This workflow contains a webhook that receives updates from Reply.io when one of your connections sends you a message via either Email or LinkedIn. The workflow also includes utility nodes to create t

HTTP Request, Telegram