Skip to main content
Automation July 27, 2026 · 13 min read

How to Build a Lead Scoring Model in n8n (With Real Criteria)

Build a weighted lead scoring model in n8n that scores leads by source and engagement. I spent 3 months tuning the criteria across 8 client pipelines.

Edward Chalupa

Edward Chalupa

Founder, Whtnxt · Dallas, TX

How to Build a Lead Scoring Model in n8n (With Real Criteria)

Most lead scoring content is theory dressed as advice or vendor copy. HubSpot tells you to score by “fit and intent” without giving weights. Marketo publishes ideal customer profile white papers that take a consultant two months to implement.

I spent three months tuning a lead scoring system in n8n across eight client pipelines. Two home services companies. One B2B SaaS. Three professional services firms. One e-commerce operation. One legal practice. Each needed a different model, but the architecture underneath is the same. If you need a primer on n8n{target=“_blank”}, their docs walk through the node basics in 20 minutes.

This post walks through that architecture: how to build a weighted lead scoring model in n8n, what criteria actually matter, how to assign weights, and how to avoid the false positive trap that makes most scoring systems useless.

Info: This system assumes you already have n8n running and a data store (NocoDB, Twenty CRM, or Google Sheets) to receive scored leads. If you need to set up n8n first, I wrote about building an n8n marketing automation engine that covers base infrastructure.

What You Are Building

A three-stage lead scoring pipeline in n8n:

  1. Capture and enrich — Web form or API lead data gets enriched with firmographic info.
  2. Score against weighted criteria — Enriched data passes through a scoring function across 5 dimensions: source quality, engagement depth, company fit, role authority, and timing signal.
  3. Route by threshold — Scores above 75 go to sales (hot), 40-75 go to nurture (warm), below 40 go to batch outreach (cold).

The scoring model itself is a JSON config object that lives in your n8n workflow as a node parameter. You can adjust weights without rebuilding the workflow.

What You Will Need

  • A running n8n instance (self-hosted or cloud)
  • A lead source connected via webhook, form platform, or CRM trigger
  • A destination for scored leads (Twenty CRM, NocoDB, or Google Sheets)
  • An enrichment source (Clearbit, Hunter.io, or manual firmographic lookup)
  • 90 minutes to build and 2 weeks to tune the weights

The Scoring Model (The Core)

Here is the model I use as a starting point for every client. It scores leads out of a possible 100 points:

CriteriaMax PointsWeightWhy It Matters
Source quality200.20Organic search traffic converts at 4x the rate of display ads
Engagement depth250.25Pages visited and time on site indicate real interest
Company fit200.20Industry, employee count, and revenue alignment
Role authority200.20Decision-maker titles score higher than researchers
Timing signal150.15”Contact us” clicks, demo requests, pricing page visits

Warning: Do not use all five dimensions on day one. Start with source quality and engagement depth (45 points) and add the others after you have 200+ scored leads to calibrate against. Adding too many criteria early creates noise, not signal.

Step 1: Capture and Enrich the Lead

The workflow starts with a Webhook node that receives incoming lead data. Most of my clients use a Typeform, Gravity Forms, or a custom form that POSTs JSON to n8n. I covered the broader strategy for three automations every marketing team needs in a separate post, and this is the third one in that stack implemented in detail.

The raw payload typically contains: name, email, company, phone, and a message field. That is not enough to score against.

I send the email domain through a Hunter.io{target=“_blank”} enrichment node to pull company size, industry, and role. If Hunter returns nothing, the workflow falls back to a lookup table I maintain in NocoDB{target=“_blank”} that maps known domains to firmographic data.

{
  "name": "{{ $json.name }}",
  "email": "{{ $json.email }}",
  "domain": "{{ $json.email.split('@')[1] }}",
  "message": "{{ $json.message }}",
  "source": "{{ $json.source || 'direct' }}"
}

The enrichment output merges with the raw payload so you have every field available for scoring.

Step 2: Assign Source Quality Points

Not all lead sources deliver the same quality. Here are the point values I use:

SourcePointsRationale
Organic search (long-tail)20Intent-driven, high conversion rate
Direct referral18Trust transfer, warm lead
Organic search (branded)16Knows the company, researching options
Webinar or event signup14Educated prospect, moderate intent
Paid search (non-branded)12Intent present, but competitive overlap
Social media organic8Low intent, top-of-funnel
Email campaign click6Existing touchpoint, re-engagement
Display or retargeting4Lowest intent, high noise

I track the source in a utm_source hidden field on every form. If the form does not pass a source, the workflow checks the HTTP referrer header and maps known domains (google.com, facebook.com, linkedin.com) to their source category.

// sourceAssigner node
const sourceMap = {
  'google': { source: 'organic_nonbrand', points: 20 },
  'linkedin': { source: 'social_organic', points: 8 },
  'facebook': { source: 'social_organic', points: 8 },
  'mail.google.com': { source: 'email_click', points: 6 },
  't.co': { source: 'social_organic', points: 8 },
  'direct': { source: 'direct', points: 18 }
};

const referrer = $json.referrer || 'direct';
const domain = new URL(referrer).hostname.replace('www.', '');
const match = sourceMap[domain] || sourceMap['direct'];
return { source: match.source, sourcePoints: match.points };

Info: If you run paid search and organic alongside each other, tag your Ad URLs with a gclid parameter and check for it before checking the referrer. Paid clicks from Google also arrive with a google.com referrer, and you want to score them at 12 points, not 20.

Step 3: Score Engagement Depth

Engagement depth requires data beyond the first form submission. If your n8n instance has access to a session tracking tool (Plausible, Umami, Fathom) or your site pushes behavioral data to a webhook, you can score on actual page visits.

If you do not have that data, the message field is your best signal. I use a keyword scoring function:

// engagementScorer node
const message = ($json.message || '').toLowerCase();
const engagementKeywords = {
  'pricing': 10,
  'cost': 8,
  'demo': 15,
  'consultation': 12,
  'case study': 8,
  'integration': 6,
  'api': 5,
  'support': 3,
  'how much': 8,
  'compare': 7,
  'alternative': 6,
  'competitor': 4,
  'implement': 5,
  'timeline': 7,
  'contract': 9
};

let engagementPoints = 0;
for (const [keyword, points] of Object.entries(engagementKeywords)) {
  if (message.includes(keyword)) {
    engagementPoints = Math.max(engagementPoints, points);
  }
}

// Message length bonus
if (message.length > 100) engagementPoints += 3;
if (message.split(' ').length > 50) engagementPoints += 2;

return { engagementPoints: Math.min(engagementPoints, 25) };

A message containing “demo” or “consultation” gets 12 to 15 points. A blank message with fewer than 10 words gets zero. Long-tail messages describing a specific problem get the bonus points.

Step 4: Score Company Fit and Role Authority

Company fit requires enrichment data. If you have Clearbit running, pull the company’s industry, employee count, and revenue range.

Here is the function I use for a B2B professional services model:

// companyFitScorer node
const companyData = $json.company || {};
let fitPoints = 0;

// Industry match
const targetIndustries = ['technology', 'professional services', 'healthcare', 'legal', 'financial services'];
if (targetIndustries.includes((companyData.industry || '').toLowerCase())) {
  fitPoints += 8;
} else if (companyData.industry) {
  fitPoints += 3; // Known industry but not in target
}

// Employee count
const employees = companyData.employees || 0;
if (employees >= 10 && employees <= 500) {
  fitPoints += 7; // SME sweet spot
} else if (employees > 500) {
  fitPoints += 4; // enterprise, longer sales cycle
} else if (employees > 0) {
  fitPoints += 2; // very small, may not have budget
}

// Revenue range
const revenue = companyData.revenue || 0;
if (revenue >= 1000000 && revenue <= 50000000) {
  fitPoints += 5;
} else if (revenue > 50000000) {
  fitPoints += 3;
}

return { companyFitPoints: Math.min(fitPoints, 20) };

Role authority is simpler. I parse the job title from enrichment data and score on a hierarchy:

// roleAuthorityScorer node
const title = ($json.title || '').toLowerCase();
let rolePoints = 0;

if (/founder|ceo|cfo|cto|coo|vp|director|owner|partner|principal/.test(title)) {
  rolePoints = 20;
} else if (/manager|head of|lead|senior/.test(title)) {
  rolePoints = 14;
} else if (/specialist|analyst|coordinator|associate/.test(title)) {
  rolePoints = 8;
} else if (/intern|assistant|student/.test(title)) {
  rolePoints = 2;
} else {
  rolePoints = 5; // Unknown title, default to moderate score
}

return { roleAuthorityPoints: rolePoints };

Step 5: Calculate the Total Score and Route

The final stage sums all dimensions and applies the threshold routing:

// scoreCalculator node
const sourcePoints = $json.sourcePoints || 0;
const engagementPoints = $json.engagementPoints || 0;
const companyFitPoints = $json.companyFitPoints || 0;
const roleAuthorityPoints = $json.roleAuthorityPoints || 0;
const timingPoints = $json.timingPoints || 0;

const totalScore = sourcePoints + engagementPoints + companyFitPoints + roleAuthorityPoints + timingPoints;

let tier = 'cold';
if (totalScore >= 75) {
  tier = 'hot';
} else if (totalScore >= 40) {
  tier = 'warm';
}

return {
  score: totalScore,
  tier: tier,
  breakdown: {
    source: sourcePoints,
    engagement: engagementPoints,
    companyFit: companyFitPoints,
    roleAuthority: roleAuthorityPoints,
    timing: timingPoints
  }
};

The IF node after this routes based on $json.tier:

  • Hot (75+): Push to Twenty CRM{target=“_blank”} with priority tag, send Slack notification to the sales channel, trigger a same-day call task.
  • Warm (40-74): Push to Twenty CRM with nurture tag, add a 5-touch email sequence in Listmonk{target=“_blank”}, schedule a call task for day 3.
  • Cold (0-39): Push to a NocoDB table for monthly batch outreach. No notification. No email sequence.

Info: I set up a lead routing automation that extends this scoring pipeline with additional destination logic for multiple sales teams. The scoring and routing are separate layers for a reason: you want to change one without breaking the other. Combined with client onboarding automation, this forms the backbone of a complete lead-to-customer system.

Step 6: Log Everything for Tuning

The most important step most scoring guides skip: logging every score so you can tune the weights.

I create a record in a NocoDB table called lead_scoring_log with every scored lead. The schema:

FieldExamplePurpose
lead_iduuidUnique identifier
score68Total weighted score
tierwarmThreshold category
source_points20Source quality component
engagement_points12Engagement depth component
company_fit_points8Company fit component
role_authority_points20Role authority component
outcomeclosed_wonDownstream conversion result
scoring_date2026-07-27When the score was calculated

After 30 days, run a simple analysis: for each score bucket (0-20, 21-40, 41-60, 61-80, 81-100), what percentage converted? If the 41-60 bucket converts at the same rate as the 61-80 bucket, the warm/hot threshold is wrong. If nobody in the 0-20 bucket ever converts, drop those leads entirely instead of batching them.

The Pitfalls I Hit

False positives from short engagement signals. A visitor who types “pricing” gets 10 engagement points. A visitor who writes 300 words about their implementation problem also gets 10 because both contain the keyword. I fixed this by adding the message length bonus: longer, specific messages get weighted higher than single-keyword submissions.

Enrichment gaps for small businesses. Many SMBs do not have a Clearbit profile. Their domain resolves to a generic email (Gmail, Outlook), enrichment returns nothing, and they score zero on company fit. I added a fallback: if the domain is a free provider, assign a default of 5 points instead of 0. Solo operators are still real leads, they just need a different scoring model.

Over-scoring on referral source. A direct link from a client scores 18 points for source quality. But the recipient might not be a buyer. I added a check: if source is “direct referral” and the message is under 15 words, cap the combined source and engagement score at 25. This prevents warm referrals from bypassing qualification.

The timing dimension gap. I do not have a reliable way to measure timing in most pipelines. “Pricing page visit” and “demo request” are closest but require website tracking. If you run Plausible or Umami, push page visit events to an n8n webhook. Until I have that, I set timing points to 0 and score out of 85 instead of 100.

Warning: Do not guess timing points. If you do not have the data to support a timing score, set it to zero and rescale your total to 85. Guessing timing inflates hot scores and trains your sales team to ignore the scoring system.

When to Use This (And When Not To)

This scoring model works best when you have at least 100 incoming leads per month. Below that threshold, the tuning data takes too long to accumulate and you will be adjusting weights based on anecdotes instead of statistics. If you are setting up a full marketing operations stack, I offer a marketing automation services engagement that builds the scoring model alongside the enrichment, routing, and reporting layers.

Use this for B2B and high-ticket B2C where missing a hot lead costs real money. For low-ticket, high-volume B2C (subscriptions under USD 50), skip scoring and use a behavior-triggered email sequence instead.

If your sales team qualifies leads by phone within 2 hours of submission, scoring is redundant. The pipeline adds value when lead volume exceeds call capacity and you need routing triage.

What Is Next

The scoring model I described is version 1. Version 2 connects the outcome field back into the scoring logic: when a hot lead is marked “closed lost” in Twenty CRM, the workflow auto-adjusts the criteria that contributed most to that false positive score. I wrote about the deal-to-cash pipeline that handles this feedback loop for my Twenty CRM setup. The reporting layers I built into the marketing analytics dashboard surface these score-to-conversion breakdowns automatically.

If you want the scoring log table and the n8n workflow JSON, I share them in my lead generation services engagements. The starting model works for most B2B pipelines out of the box. Get in touch if you want to adapt it for your specific pipeline.


n8nlead scoringlead managementmarketing automationCRM automationworkflow automationlead qualificationB2B lead scoring
Share: