← Back to Blog
Tutorial14 min readJanuary 18, 2026

How to Build a Lead Enrichment Pipeline with APIs (2026)

Step-by-step guide to building an automated lead enrichment pipeline using APIs. From data collection to CRM integration, with code examples and best practices.

What is a Lead Enrichment Pipeline?

A lead enrichment pipeline is an automated system that takes basic lead information (like an email address or company domain) and enhances it with additional data points from external sources. Instead of manually researching each lead, the pipeline does it automatically in seconds.

A typical pipeline flow looks like this:

  1. Lead enters your system (form submission, CSV upload, API webhook)
  2. Pipeline validates and cleans the data
  3. Pipeline calls enrichment APIs to gather additional information
  4. Enriched data is scored and routed to the appropriate team
  5. Data is synced to your CRM with all new attributes

Architecture Overview

We'll build a pipeline with these components:

Pipeline Components

  • Input Layer: Webhook endpoint or queue to receive leads
  • Validation Layer: Email validation, deduplication, data cleaning
  • Enrichment Layer: API calls to gather profile and company data
  • Scoring Layer: Calculate lead score based on enriched attributes
  • Routing Layer: Assign leads to sales reps based on criteria
  • Output Layer: Sync to CRM, send notifications, trigger workflows

Step 1: Set Up the Input Layer

First, create an endpoint to receive leads. This could be a webhook from your website forms, a CSV upload handler, or a queue consumer.

Example: Express.js Webhook Endpoint

const express = require('express');
const app = express();

app.post('/webhooks/new-lead', async (req, res) => {
  const { email, name, company } = req.body;
  
  // Acknowledge receipt immediately
  res.status(200).json({ received: true });
  
  // Process asynchronously
  processLead({ email, name, company })
    .catch(err => console.error('Lead processing failed:', err));
});

async function processLead(lead) {
  // Validation, enrichment, and CRM sync happen here
  console.log('Processing lead:', lead.email);
}

app.listen(3000);

Key principle: Always respond to webhooks quickly (under 5 seconds). Process the lead asynchronously to avoid timeouts.

Step 2: Validate and Clean Data

Before enriching, validate that the data is usable. Check email deliverability, remove duplicates, and standardize formats.

Email Validation

async function validateEmail(email) {
  // Basic format check
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  if (!emailRegex.test(email)) {
    return { valid: false, reason: 'Invalid format' };
  }
  
  // Check against disposable email domains
  const disposableDomains = ['tempmail.com', '10minutemail.com'];
  const domain = email.split('@')[1];
  if (disposableDomains.includes(domain)) {
    return { valid: false, reason: 'Disposable email' };
  }
  
  // Optional: Use email validation API
  // const result = await emailValidationAPI.verify(email);
  
  return { valid: true };
}

Deduplication

async function checkDuplicate(email) {
  // Check if lead already exists in database
  const existing = await db.leads.findOne({ email });
  
  if (existing) {
    // Update last_seen timestamp
    await db.leads.update({ email }, { 
      last_seen: new Date(),
      submission_count: existing.submission_count + 1 
    });
    return { isDuplicate: true, leadId: existing.id };
  }
  
  return { isDuplicate: false };
}

Step 3: Enrich with Profile Data

Now the core enrichment step. Call APIs to gather professional profile data, company information, and social profiles.

Reverse Email Lookup

async function enrichProfile(email) {
  try {
    // Call Netrows reverse email lookup
    const response = await fetch(
      `https://api.netrows.com/api/v1/people/reverse-email?email=${email}`,
      {
        headers: {
          'Authorization': `Bearer ${process.env.NETROWS_API_KEY}`
        }
      }
    );
    
    const profile = await response.json();
    
    return {
      full_name: profile.full_name,
      job_title: profile.headline,
      company: profile.company,
      linkedin_url: profile.profile_url,
      location: profile.location,
      skills: profile.skills,
      experience_years: calculateExperience(profile.experience)
    };
  } catch (error) {
    console.error('Profile enrichment failed:', error);
    return null;
  }
}

Company Enrichment

async function enrichCompany(companyDomain) {
  try {
    const response = await fetch(
      `https://api.netrows.com/api/v1/companies/by-domain?domain=${companyDomain}`,
      {
        headers: {
          'Authorization': `Bearer ${process.env.NETROWS_API_KEY}`
        }
      }
    );
    
    const company = await response.json();
    
    return {
      company_name: company.name,
      company_size: company.employee_count,
      industry: company.industry,
      company_linkedin: company.linkedin_url,
      headquarters: company.headquarters,
      founded_year: company.founded_year,
      website: company.website
    };
  } catch (error) {
    console.error('Company enrichment failed:', error);
    return null;
  }
}

Step 4: Calculate Lead Score

Use the enriched data to calculate a lead score. This helps prioritize which leads sales should contact first.

function calculateLeadScore(enrichedLead) {
  let score = 0;
  
  // Job title scoring
  const seniorTitles = ['VP', 'Director', 'Head of', 'Chief', 'C-level'];
  if (seniorTitles.some(title => enrichedLead.job_title?.includes(title))) {
    score += 30;
  }
  
  // Company size scoring
  if (enrichedLead.company_size >= 100 && enrichedLead.company_size <= 1000) {
    score += 25; // Sweet spot for mid-market
  } else if (enrichedLead.company_size > 1000) {
    score += 20; // Enterprise
  }
  
  // Industry scoring
  const targetIndustries = ['Technology', 'SaaS', 'Financial Services'];
  if (targetIndustries.includes(enrichedLead.industry)) {
    score += 20;
  }
  
  // Engagement scoring
  if (enrichedLead.submission_count > 1) {
    score += 15; // Returning visitor
  }
  
  // LinkedIn profile completeness
  if (enrichedLead.linkedin_url && enrichedLead.skills?.length > 5) {
    score += 10;
  }
  
  return Math.min(score, 100); // Cap at 100
}

Scoring tiers:

  • 80-100: Hot lead - Immediate follow-up
  • 60-79: Warm lead - Follow-up within 24 hours
  • 40-59: Qualified lead - Add to nurture campaign
  • 0-39: Cold lead - Long-term nurture or disqualify

Step 5: Route to Sales Team

Based on the lead score and attributes, route to the appropriate sales rep or team.

async function routeLead(enrichedLead) {
  let assignedTo = null;
  
  // Route by company size
  if (enrichedLead.company_size > 1000) {
    assignedTo = await getAvailableRep('enterprise');
  } else if (enrichedLead.company_size >= 100) {
    assignedTo = await getAvailableRep('mid-market');
  } else {
    assignedTo = await getAvailableRep('smb');
  }
  
  // Route by geography
  if (enrichedLead.location?.includes('EMEA')) {
    assignedTo = await getAvailableRep('emea');
  }
  
  // Hot leads go to senior reps
  if (enrichedLead.lead_score >= 80) {
    assignedTo = await getAvailableRep('senior');
  }
  
  return assignedTo;
}

async function getAvailableRep(segment) {
  // Round-robin assignment within segment
  const reps = await db.salesReps.find({ 
    segment, 
    active: true 
  });
  
  // Find rep with fewest active leads
  const repWithLeastLeads = reps.sort((a, b) => 
    a.active_leads - b.active_leads
  )[0];
  
  return repWithLeastLeads;
}

Step 6: Sync to CRM

Finally, push the enriched lead to your CRM with all the new data points.

Salesforce Example

async function syncToSalesforce(enrichedLead) {
  const sfClient = new SalesforceClient({
    clientId: process.env.SF_CLIENT_ID,
    clientSecret: process.env.SF_CLIENT_SECRET
  });
  
  await sfClient.createLead({
    FirstName: enrichedLead.first_name,
    LastName: enrichedLead.last_name,
    Email: enrichedLead.email,
    Title: enrichedLead.job_title,
    Company: enrichedLead.company_name,
    LinkedIn__c: enrichedLead.linkedin_url,
    LeadScore__c: enrichedLead.lead_score,
    CompanySize__c: enrichedLead.company_size,
    Industry: enrichedLead.industry,
    OwnerId: enrichedLead.assigned_to,
    LeadSource: 'Website',
    Status: enrichedLead.lead_score >= 60 ? 'Working' : 'New'
  });
}

HubSpot Example

async function syncToHubSpot(enrichedLead) {
  const hubspot = require('@hubspot/api-client');
  const client = new hubspot.Client({ 
    accessToken: process.env.HUBSPOT_TOKEN 
  });
  
  await client.crm.contacts.basicApi.create({
    properties: {
      email: enrichedLead.email,
      firstname: enrichedLead.first_name,
      lastname: enrichedLead.last_name,
      jobtitle: enrichedLead.job_title,
      company: enrichedLead.company_name,
      linkedin_url: enrichedLead.linkedin_url,
      hs_lead_status: enrichedLead.lead_score >= 60 ? 'OPEN' : 'NEW',
      lead_score: enrichedLead.lead_score,
      company_size: enrichedLead.company_size,
      industry: enrichedLead.industry
    }
  });
}

Step 7: Error Handling & Retries

APIs can fail. Build robust error handling and retry logic.

async function enrichWithRetry(email, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const profile = await enrichProfile(email);
      return profile;
    } catch (error) {
      console.error(`Enrichment attempt ${attempt} failed:`, error);
      
      if (attempt === maxRetries) {
        // Log to error tracking service
        await logEnrichmentFailure(email, error);
        return null; // Give up after max retries
      }
      
      // Exponential backoff
      await sleep(Math.pow(2, attempt) * 1000);
    }
  }
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

Complete Pipeline Example

Putting it all together:

async function processLead(rawLead) {
  try {
    // Step 1: Validate
    const emailCheck = await validateEmail(rawLead.email);
    if (!emailCheck.valid) {
      console.log('Invalid email:', emailCheck.reason);
      return;
    }
    
    // Step 2: Check duplicates
    const dupCheck = await checkDuplicate(rawLead.email);
    if (dupCheck.isDuplicate) {
      console.log('Duplicate lead:', dupCheck.leadId);
      return;
    }
    
    // Step 3: Enrich profile
    const profile = await enrichWithRetry(rawLead.email);
    if (!profile) {
      console.log('Enrichment failed, saving basic lead');
      await db.leads.create(rawLead);
      return;
    }
    
    // Step 4: Enrich company
    const companyDomain = extractDomain(rawLead.email);
    const company = await enrichCompany(companyDomain);
    
    // Step 5: Combine data
    const enrichedLead = {
      ...rawLead,
      ...profile,
      ...company,
      enriched_at: new Date()
    };
    
    // Step 6: Calculate score
    enrichedLead.lead_score = calculateLeadScore(enrichedLead);
    
    // Step 7: Route to sales
    enrichedLead.assigned_to = await routeLead(enrichedLead);
    
    // Step 8: Save to database
    await db.leads.create(enrichedLead);
    
    // Step 9: Sync to CRM
    await syncToSalesforce(enrichedLead);
    
    // Step 10: Notify sales rep if hot lead
    if (enrichedLead.lead_score >= 80) {
      await notifySalesRep(enrichedLead);
    }
    
    console.log('Lead processed successfully:', enrichedLead.email);
  } catch (error) {
    console.error('Pipeline error:', error);
    await logPipelineFailure(rawLead, error);
  }
}

Best Practices

1. Process Asynchronously

Use queues (Redis, RabbitMQ, AWS SQS) to process leads in the background. This prevents timeouts and allows you to handle spikes in volume.

2. Cache Enrichment Results

If the same email or company appears multiple times, cache the enrichment results for 30 days to save API costs.

3. Monitor Pipeline Health

Track metrics like:

  • Enrichment success rate
  • Average processing time
  • API error rates
  • Lead score distribution
  • CRM sync failures

4. Implement Rate Limiting

Respect API rate limits. Use a rate limiter library to avoid hitting limits and getting blocked.

5. Log Everything

Comprehensive logging helps debug issues and understand pipeline performance. Log each step with timestamps and relevant context.

Deployment Options

Serverless (AWS Lambda, Vercel Functions)

Pros: Auto-scaling, pay-per-use, no server management

Cons: Cold starts, execution time limits

Container-Based (Docker, Kubernetes)

Pros: Full control, consistent environment, good for high volume

Cons: More complex, requires infrastructure management

Background Workers (Node.js, Python)

Pros: Simple, flexible, easy to debug

Cons: Need to manage scaling manually

Build Your Pipeline with Netrows

Netrows provides all the APIs you need for lead enrichment: LinkedIn profiles, company data, reverse email lookup, and more. Get started with 100 free credits and build your pipeline today.

View API Docs