Lead scoring based on web search signals identifies which leads deserve immediate attention. Instead of scoring solely on form data (job title, company size), you can enrich each lead with live search data about their company -- funding status, tech stack, hiring activity, and growth signals -- then score based on that context. This tutorial builds an n8n workflow that receives leads via webhook, enriches them with search API data, calculates a qualification score, and routes high-scoring leads to your sales team. Each enrichment costs $0.005-0.015 via Scavio.
Prerequisites
- n8n instance running (self-hosted or cloud)
- A Scavio API key from scavio.dev
- A webhook URL to receive leads (or manual trigger for testing)
- Slack or email for lead notifications
Walkthrough
Step 1: Set up the lead intake webhook
Create an n8n webhook that receives new leads. This can connect to your form tool, CRM, or any system that generates leads.
// n8n Webhook node configuration:
// HTTP Method: POST
// Path: /score-lead
// Response Mode: Last Node
//
// Expected payload:
// {
// "company": "Acme Corp",
// "domain": "acme.com",
// "contact_name": "John Doe",
// "email": "john@acme.com",
// "job_title": "VP Engineering"
// }
// Test with curl:
// curl -X POST http://your-n8n:5678/webhook/score-lead \
// -H 'Content-Type: application/json' \
// -d '{"company": "Acme Corp", "domain": "acme.com"}'Step 2: Enrich with company search data
Search for the company to find recent news, funding, and general information. Use two targeted searches for maximum signal extraction.
// HTTP Request node 1 - Company overview:
// POST https://api.scavio.dev/api/v1/search
// Headers: x-api-key: {{ $env.SCAVIO_API_KEY }}
// Body:
{
"query": "{{ $json.company }} company overview funding",
"country_code": "us"
}
// HTTP Request node 2 - Hiring signals:
// Body:
{
"query": "{{ $json.company }} hiring OR jobs OR careers 2026",
"country_code": "us"
}
// HTTP Request node 3 - Tech stack (optional, +1 credit):
// Body:
{
"query": "site:{{ $json.domain }} technology OR stack OR platform",
"country_code": "us"
}Step 3: Extract scoring signals from search results
Parse the search results to identify growth signals, funding status, company size, and technology usage. Each signal contributes to the lead score.
// n8n Code node - Extract and score signals:
const overview = $('HTTP Request - Overview').first().json;
const hiring = $('HTTP Request - Hiring').first().json;
const overviewText = (overview.organic_results || [])
.map(r => `${r.title} ${r.snippet || ''}`).join(' ').toLowerCase();
const hiringText = (hiring.organic_results || [])
.map(r => `${r.title} ${r.snippet || ''}`).join(' ').toLowerCase();
const signals = {
hasFunding: /series [a-d]|raised|funding|venture/.test(overviewText),
isHiring: (hiring.organic_results || []).length > 3,
isSaaS: /saas|software|platform|cloud/.test(overviewText),
hasGrowth: /growing|growth|expanding|scaling/.test(overviewText),
seniorTitle: /vp|director|head|chief|cto|ceo/.test(
($input.first().json.job_title || '').toLowerCase()),
hasWebsite: Boolean($input.first().json.domain),
};
return [{ json: { ...$input.first().json, signals } }];Step 4: Calculate the composite lead score
Assign point values to each signal and compute a total score. Route leads based on score thresholds.
// n8n Code node - Calculate score:
const signals = $json.signals;
const weights = {
hasFunding: 25,
isHiring: 20,
isSaaS: 15,
hasGrowth: 15,
seniorTitle: 15,
hasWebsite: 10,
};
let score = 0;
const matchedSignals = [];
for (const [signal, points] of Object.entries(weights)) {
if (signals[signal]) {
score += points;
matchedSignals.push(`${signal}: +${points}`);
}
}
const tier = score >= 70 ? 'HOT' : score >= 40 ? 'WARM' : 'COLD';
return [{
json: {
...$json,
score,
tier,
matchedSignals,
enrichmentCost: '$0.010' // 2 search queries
}
}];Step 5: Route and notify based on score
Use an n8n Switch node to route leads by tier. Hot leads go to Slack immediately, warm leads to a review queue, cold leads to nurture.
// Switch node on $json.tier:
// HOT -> Slack notification + CRM "Hot Lead" pipeline
// WARM -> CRM "Review" pipeline
// COLD -> CRM "Nurture" sequence
// Slack node for HOT leads:
// Channel: #sales-alerts
// Message:
// *HOT LEAD* (Score: {{ $json.score }}/100)
// Company: {{ $json.company }}
// Contact: {{ $json.contact_name }} ({{ $json.job_title }})
// Signals: {{ $json.matchedSignals.join(', ') }}
// Enrichment cost: {{ $json.enrichmentCost }}
// Cost summary:
// 2-3 search queries per lead = $0.010-0.015
// 100 leads/day = $1.00-1.50/day
// 3000 leads/month = $30-45/month ($30 plan covers it)Python Example
import os, requests, re
API_KEY = os.environ['SCAVIO_API_KEY']
def search(query):
resp = requests.post('https://api.scavio.dev/api/v1/search',
headers={'x-api-key': API_KEY, 'Content-Type': 'application/json'},
json={'query': query, 'country_code': 'us'})
return resp.json()
def score_lead(company, domain='', title=''):
overview = search(f'{company} company funding')
text = ' '.join(r.get('snippet', '') for r in overview.get('organic_results', [])).lower()
score = 0
if re.search(r'series [a-d]|raised|funding', text): score += 25
if re.search(r'saas|software|platform', text): score += 15
if re.search(r'growing|scaling', text): score += 15
if re.search(r'vp|director|head|chief', title.lower()): score += 15
if domain: score += 10
tier = 'HOT' if score >= 70 else 'WARM' if score >= 40 else 'COLD'
return {'company': company, 'score': score, 'tier': tier}
for company in ['Stripe', 'Local Bakery LLC']:
result = score_lead(company)
print(f'{result["company"]}: {result["score"]}/100 ({result["tier"]})')JavaScript Example
const API_KEY = process.env.SCAVIO_API_KEY;
async function search(query) {
const resp = await fetch('https://api.scavio.dev/api/v1/search', {
method: 'POST',
headers: { 'x-api-key': API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ query, country_code: 'us' })
});
return resp.json();
}
async function scoreLead(company) {
const data = await search(`${company} company funding`);
const text = (data.organic_results || []).map(r => r.snippet || '').join(' ').toLowerCase();
let score = 0;
if (/series [a-d]|raised|funding/.test(text)) score += 25;
if (/saas|software|platform/.test(text)) score += 15;
if (/growing|scaling/.test(text)) score += 15;
const tier = score >= 70 ? 'HOT' : score >= 40 ? 'WARM' : 'COLD';
console.log(`${company}: ${score}/100 (${tier})`);
}
Promise.all(['Stripe', 'Local Bakery'].map(scoreLead));Expected Output
Stripe: 55/100 (WARM)
hasFunding: +25
isSaaS: +15
hasGrowth: +15
Local Bakery LLC: 0/100 (COLD)
(no signals matched)
Cost: $0.010 per lead (2 searches)
100 leads/day = $1.00/day, $30/month