A lead goes cold in three hours. This B2B SaaS team was spending those three hours every morning cleaning up spreadsheets — and ₩3.8M of ad spend was going lukewarm in the meantime.
9 AM at the office
Before the sales rep even finishes their first coffee, five browser tabs are open: the website inquiry form admin, the Naver search-ad lead download page, the Facebook Lead Ads CSV, the business-card scan sheet from last week's trade show, and the CRM (HubSpot).
This is where two hours vanish. If an email is duplicated, figure out which source is newest. Normalize phone formats (010-1234-5678, 01012345678, +82 10-1234-5678). Attach tags. Paste into the CRM. And while you do all this, new leads pile up in the other tabs.
Average time to get one lead into the CRM: 27 hours. So someone who filled out a form yesterday gets a call this evening. By then they've already spoken with our competitor.— Seojun Park · B2B SaaS sales team lead
The real problem wasn't data cleanup
The visible problem was 'lead cleanup takes forever,' but the actual loss lived somewhere else. When the marketing team pulled the correlation between first-response time and contract conversion, the conclusion was unambiguous.
Do the math and the response lag was costing 12 contracts a month — ₩34M in revenue. Leads paid for with ad spend were dying on the 'copy-and-paste' step.
An integrated pipeline in 8 days
We built the following structure in n8n. Doesn't matter how many channels you have — they all funnel through a single webhook.
- Every channel (form, Naver, Facebook, event registration page, trade-show app) pushes leads into a single n8n webhook URL
- An AI Agent node normalizes email/phone/company name and assigns a lead score (0–100)
- Postgres looks up existing leads by email+phone; merge if found, create if new
- HubSpot API registers in real time; scores of 70+ ping the assigned rep in Slack DM immediately
- Scores of 90+ trigger a 5-minute response SLA to the rep; if unmet, auto-escalate to the team lead
The one code snippet that mattered
The most frequent breakage was phone-number normalization. Every channel used a different format, so dedup missed everything. One Code node fixed it.
// Code node · phone normalization
const raw = $json.phone || '';
const digits = raw.replace(/\D/g, '');
// Strip +82 country code, restore leading zero
let normalized = digits;
if (normalized.startsWith('82')) {
normalized = '0' + normalized.slice(2);
}
// Lowercase email too
return {
json: {
...$json,
phone: normalized,
email: ($json.email || '').toLowerCase().trim(),
dedup_key: `${normalized}|${($json.email || '').toLowerCase()}`,
},
};Result: 27 hours to 7 minutes
More striking than the numbers was the team mood. The morning stand-up shifted from 'reviewing yesterday's un-entered leads' to 'briefing on today's booked meetings.' Reps now make calls instead of transcribing rows.
When a lead comes in, Slack knows before I do. Notification hits, I dial from wherever I'm sitting. Before the coffee cools.— Seojun Park · B2B SaaS sales team lead
Unexpected side effects
- Per-channel lead quality data now accumulates automatically → basis for reallocating ad budget
- Trade-show leads integrated via the business-card scan app webhook — same-day response instead of next-day
- Late-response alerts auto-escalate to the team lead, so SLA compliance happens without separate tracking
- AI scoring logs pile up and give the marketing team A/B-test evidence for landing page copy
Why this approach worked
This project succeeded not because the tech was impressive. It worked because we proved with data where time was leaking, then focused on exactly that spot. Automating lead cleanup was a side effect; the real target was moving a single metric — 'response within 5 minutes.'
The most common reason automation projects fail is that 'automation itself' becomes the goal. Do what this case did: pick one business metric, then find the shortest path that moves it.