← All articles

GTM Engineering

Roughly 90% of Lead Scores Still Route on a Job Title

A VP title and a company size predict almost nothing about who buys. What buys is behavior: product usage and intent. Here is the scoring rubric that weights signals, the SQL that computes it, and the routing rule that acts on it.

· 13 min read

Roughly 90% of the lead-scoring models I have opened route on a job title and a company size. Points for “VP or above,” points for “500 or more employees,” points for the right industry picklist, and a threshold that hands the total to a rep. The model has never seen the one thing that predicts a purchase: what the person did. A director who opened three docs, invited two teammates, and hit the pricing page twice this week scores lower than a VP at a big logo who has done nothing but exist in the right firmographic band.

That is the whole failure. Demographics tell you who a person is. Behavior tells you what they are about to do. Scoring on the first and ignoring the second is why sales rejects half the “hot” leads you send and why the ones they never call turn out to be the ones who bought from a competitor. I have rebuilt this on enough stacks to know the fix is not a better title-points table. It is a second axis, computed from product and engagement signals, and a routing rule that reads it.

~90%
Of models still score on title and firmographics
10-20%
Reply rate on signal-triggered vs 1-3% spray (Woodpecker, 26,000 campaigns)
63.5%
Of 1,000 B2B sites never responded to an inbound lead, avg 29 hours (RevenueHero 2025)

The fix is not a longer title-points table. It is a second scoring axis, and it goes in in a fixed order: fit becomes a gate, behavior gets instrumented, intent is computed on a rolling window, the whole thing is validated against closed-won, and only then does routing read the pair. Skip a rung and the one above it sits on sand. Here is the ladder, bottom to top.

Adding the behavior axis, bottom to topFit is the floor, intent is the climb
  1. L5Route on the pair and enforce the SLAunder 1hr

    High fit plus hot intent routes to an AE with a sub-hour SLA and the last five events attached. Low fit plus hot intent goes to self-serve. Everything else nurtures. The score is inert until this rule reads it. This is the top rung, not the first.

  2. L4Validate against closed-won before you route2 quarters

    Run the intent model as if it existed two quarters ago and confirm your actual closed deals scored hot in the weeks before they signed. Check the distribution: 90% in one band is a label, not a score. If the wins did not score hot, the weights are wrong.

  3. L3Compute intent on a rolling 7-day window7-day

    Roll those events into a weighted number that resets, so it reflects the last week, not a lifetime total. Recent, stacked signals score highest; the last 7 days convert about 3x older ones. Count each signal once per window.

  4. L2Instrument the five behaviors that predict a buy5 events

    Get product and web events into the warehouse: pricing views, core-value action, teammate invites, doc opens, return sessions. You do not need every event, you need the five that correlate with closed-won. Pull them from the last two quarters of wins.

  5. L1Fit as a gate, not a points pileA / B / C

    Collapse title, company size, industry, and region into a letter grade that qualifies an account into the pool. Stop letting a VP title out-score a director who is three doc-opens deep. Fit answers whether to sell to the account, nothing more.

Fit is who they are. Intent is what they do.

Two axes, and the mistake is blending them into one number. Fit is firmographic: title, company size, industry, region, tech stack. It answers “should we sell to this account at all.” Intent is behavioral: product usage, doc opens, pricing-page visits, teammate invites, repeat sessions. It answers “is this person moving toward a decision right now.” A single blended score that decays on engagement is a confused composite, because a high-fit account with zero intent and a low-fit account with screaming intent can land on the identical number and get treated the same way. They are not the same lead.

The reason the industry defaulted to fit-only is that fit was the data you could buy. Firmographics come from an enrichment provider on day one. Behavior requires instrumentation: product events flowing to a warehouse, engagement events captured from the site and the app. That plumbing is more work than an enrichment append, so most teams stopped at the append and called it scoring. The problem is that fit alone does not move. A VP is a VP whether they are three weeks from signing or have never heard of you.

The gap between the two axes shows up in reply rates, which is the cleanest proxy for whether a score is pointing reps at the right people. Outreach driven by firmographic lists behaves like spray; outreach driven by a fresh behavioral signal behaves like a warm intro.

How the lead was selectedReply rateWhat the score was reading
Spray to a firmographic list1-3%Title and company size only
Segmented firmographic bulk3-7%Better firmographics, still static
Signal-triggered (behavior)10-20%A recent product or intent event
Fully personalized to context20-40%Behavior plus account research

Source: Woodpecker’s analysis of 26,000 campaigns. The jump from the top row to the third is the entire argument for a behavior axis: same reps, same product, a 3x to 10x lift in reply rate purely from selecting on what people did instead of who they are.

Two axes Score fit and intent separately, then route on the combination
Intent (behavior): usage, doc opens, pricing visitsFit (firmographic)High fit, low intentnurture, do not page a repHigh fit, high intentroute now, under 1 hourLow fit, low intentsuppressLow fit, high intentPLG self-serve, watch to expand
Fit answers whether to sell to the account. Intent answers whether the person is moving now. Blending them into one number hides which lever is missing.

The rubric: weight behavior, cap firmographics

Here is the rubric I build. Fit is a letter grade from firmographics and caps at a gate, not a pile of points. Intent is a number, and it is where the weight lives, because intent is what changes and what predicts the next 30 days. The scores stay separate. Routing reads the pair.

SignalTypeWeightWhy it earns the weight
Pricing page visited 2+ times in 7 daysIntent25Highest-correlation pre-buy behavior; near-term
Product: invited a teammateIntent20Expansion of use, buying-committee formation
Product: hit a core-value actionIntent20Reached the moment that predicts retention
Opened 2+ docs / demo pages this weekIntent15Active evaluation, recent
Return session within 72 hoursIntent10Signal freshness; last 7 days convert ~3x older
Title is buyer or aboveFitgateQualifies the account; does not stack points
Company in ICP size and industryFitgateQualifies the account; does not stack points

Source on the weighting logic: signal freshness and strength tiering follow the same signal-based outbound practice that ranks recent, stacked behavioral signals far above single or stale ones, and the Woodpecker 26,000-campaign benchmark showing signal-triggered outreach at 10-20% reply against 1-3% for firmographic spray. Fit is deliberately a gate. A VP title should qualify an account into the pool; it should not out-score a director who is three doc-opens and a pricing visit deep, because the director is the one moving.

The SQL that computes it

Behavior scoring lives where the behavior lives: the warehouse, next to product events and web events, not in a CRM field a marketer types into. Here is the model that rolls raw events into an intent score per person, windowed to the last seven days so it reflects what is happening now, not a lifetime accumulation that never resets.

-- intent_score: behavioral, windowed, freshness-weighted
-- runs on the events already flowing to the warehouse, syncs back to CRM
with events as (
  select
    person_id,
    event_name,
    occurred_at
  from analytics.gtm_events
  where occurred_at >= current_date - interval '7 days'
),
scored as (
  select
    person_id,
    -- weight each behavior; a signal counted once per window, not per fire
    max(case when event_name = 'pricing_view' then 25 else 0 end)
      + max(case when event_name = 'teammate_invited' then 20 else 0 end)
      + max(case when event_name = 'core_value_action' then 20 else 0 end)
      + least(count(distinct case when event_name = 'doc_open' then occurred_at end) * 8, 15)
      + max(case when event_name = 'return_session' then 10 else 0 end)
      as intent_raw
  from events
  group by person_id
)
select
  person_id,
  intent_raw,
  case
    when intent_raw >= 55 then 'hot'
    when intent_raw >= 30 then 'warm'
    else 'cool'
  end as intent_band
from scored;

The max() per behavior is deliberate: it counts a signal once inside the window instead of rewarding someone for reloading the pricing page ten times, which is noise, not ten times the intent. The doc-open line uses a capped count, so evaluation depth adds up but does not run away. Fit stays in its own model, a letter grade off firmographics, and the two join at routing time.

-- join fit + intent at routing time; never sum them into one column
select
  p.person_id,
  f.fit_grade,          -- A / B / C from firmographics
  i.intent_band,        -- hot / warm / cool from behavior
  case
    when f.fit_grade in ('A','B') and i.intent_band = 'hot' then 'route_now'
    when f.fit_grade in ('A','B') and i.intent_band = 'warm' then 'route_sla_1d'
    when f.fit_grade = 'C' and i.intent_band = 'hot' then 'plg_selfserve'
    else 'nurture'
  end as routing_action
from people p
join fit_scores f    on f.person_id = p.person_id
join intent_scores i on i.person_id = p.person_id;

The routing rule that acts on it

A score sitting in a warehouse column is not a signal until a workflow reads it. The routing rule is the other half of the build, and it has to respect the one number the whole exercise is chasing: speed. RevenueHero’s 2025 audit found 63.5% of 1,000 B2B sites never responded to an inbound lead at all, and those that did averaged 29 hours. Signal-triggered outreach only earns its 10-20% reply rate if it fires while the signal is warm, so a hot lead that routes correctly but sits in a queue for a day has been scored well and routed uselessly.

# routing rule: reads the pair, enforces the SLA, degrades safely
on: intent_score.updated
match:
  fit_grade: [A, B]
  intent_band: hot
then:
  assign: round_robin(pool = "AE_ICP")
  sla_minutes: 60                 # under 1 hour or it escalates
  on_breach: notify(manager) + reassign(next_available)
  notify_rep: slack(context = last_5_events)   # the signal, not just a name
else_if:
  fit_grade: C
  intent_band: hot
then:
  route: plg_selfserve            # high intent, low fit: do not burn an AE
  watch: expansion_signal
default:
  route: nurture                  # no rep touch until intent moves

The rule sends the rep the last five behavioral events, not a bare name and a score. That is the difference between “here is a hot lead” and “here is a director who invited two teammates and hit pricing twice on Tuesday,” which is a call the rep can open with. It also degrades safely: a low-fit high-intent lead goes to self-serve instead of burning an AE hour, and an unbreached hot lead escalates rather than rotting in a queue. This is the routing half of the scoring and routing discipline; the scoring half is the rubric above, and neither works without the other.

What changes when you flip the axis

Same 1,000 leads, scored two ways. The demographic-only model marks a big band “hot” on title and size and hands them to reps who reject most of them. The behavioral model marks a smaller, denser band hot on what people did, and the reps convert it because the leads are moving. Toggle between them.

1,000 leads: demographic scoring vs behavioral scoring
Demographic scoring inflates the hot band with high-fit, no-intent leads reps reject. Behavioral scoring produces a smaller hot band that converts, plus a self-serve band the old model never surfaced.
View as table
ItemValue
Hot (routed to AE)420
Warm260
Nurture320
PLG self-serve0

L4 is the rung teams skip

Run the ladder in order and one rung does the quiet work of saving you from a confident, wrong model: validation against closed-won. Teams instrument the events, compute a clean-looking intent score, and wire routing on top of it without ever checking that the score would have caught the deals they already won. A score nobody validated against real outcomes is a guess with decimal places.

The check is an afternoon. Pull your last two quarters of closed-won, run the intent model as if it existed back then, and confirm the deals that closed scored hot in the weeks before they signed. Two failure shapes show up. If the wins did not score hot, the weights point at the wrong behaviors and no routing logic will rescue a model that cannot see its own buyers. If 90% of every lead lands in one band, the thresholds are labels, not a score, and you recalibrate before a rep sees a number. Both are cheap to catch here and expensive to catch after routing runs on them for a quarter.

Demographic-only versus behavior-weighted

Demographic-only scoring Behavior-weighted scoring
What it measures Who the person is (static) Who they are and what they are doing now
A VP who has done nothing Scores hot, routed to a rep High fit, zero intent, nurtured
A director evaluating hard Under threshold, ignored Hot intent, routed with context
Low-fit high-intent user Buried in nurture Self-serve, watched for expansion
What the rep receives A name and a number A name and the last five behaviors
Rep trust in the score Rejects the hot band Works it, because it is moving
Same lead pool, same reps. The difference is a second axis computed from what people do, and a routing rule that reads it.

The through-line: a title is a fact about the past and behavior is evidence about the next 30 days, and you want to point the scarce rep hour at the second one. Stop scoring on who showed up in the enrichment append. Score on who is moving, compute it from the events you already have flowing, and let the routing rule fire while the signal is still warm.

Pull your last two quarters of closed-won this week and check one thing: did those deals score hot on your current model in the weeks before they closed? If the answer is no, the model is scoring the wrong axis, and the rubric and the SQL above are the afternoon that fixes it.

scoring routing signals

Keep reading

One email. Every week.

One email a week: a system I built or broke, with the config, the numbers, and what I would change. No roundups, no theory, unsubscribe whenever it stops being useful.

The newsletter opens soon.

Connect a provider in src/config.ts