How to Use Different Attribution Models by Funnel Stage
Use different attribution models for different funnel stages: First-touch for top-of-funnel (shows who introduces prospects), Linear or Participation for mid-funnel (shows what nurtures them), and Last-touch for bottom-funnel (shows what closes them). This tiered approach captures the true contribution of each channel at each stage, producing more accurate forecasts than using any single model across the entire journey.
The Tiered Attribution Framework
Different funnel stages answer different questions—and different questions need different attribution models.
┌─────────────────────────────────────────────────────────────────┐ │ TIERED ATTRIBUTION FRAMEWORK │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ TOP OF FUNNEL (Awareness) │ │ │ │ ├── Question: "What brings new prospects?" │ │ │ │ ├── Model: FIRST-TOUCH │ │ │ │ ├── Credits: Discovery channels │ │ │ │ └── Metrics: New visitors, first touches by channel │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ MIDDLE OF FUNNEL (Consideration) │ │ │ │ ├── Question: "What keeps them engaged?" │ │ │ │ ├── Model: LINEAR or PARTICIPATION │ │ │ │ ├── Credits: All nurturing touchpoints │ │ │ │ └── Metrics: Engagement depth, channel overlap │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ BOTTOM OF FUNNEL (Conversion) │ │ │ │ ├── Question: "What closes the deal?" │ │ │ │ ├── Model: LAST-TOUCH │ │ │ │ ├── Credits: Closing channels │ │ │ │ └── Metrics: Conversions, revenue by closing channel │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘
Key insight: Each model is correct for its question. First-touch answers "who introduced?" Last-touch answers "who closed?" Neither alone answers "what drove this revenue?"
Stage 1: Top of Funnel (First-Touch)
What It Measures
First-touch attribution credits the channel that introduced a prospect to your business—their first recorded interaction.
TOFU ATTRIBUTION (FIRST-TOUCH)
First-touch credits the introducer (LinkedIn). Everything in between is invisible at the ToFU stage — that's the point.
Which Channels Win at ToFU
| Channel | Typical ToFU Share | Why |
|---|---|---|
| Paid Social | 30-45% | Excellent for cold audience targeting |
| Organic Search | 20-30% | Problem-aware searchers find content |
| Display/Programmatic | 10-20% | Broad awareness reach |
| Content Marketing | 10-15% | Attracts via valuable information |
| Direct | 5-15% | Word-of-mouth, brand recall |
Channels that rarely win ToFU:
- Email (requires existing relationship)
- Retargeting (requires prior visit)
- Branded search (requires brand awareness)
ToFU Metrics to Track
| Metric | What It Tells You |
|---|---|
| First touches by channel | Volume of new prospects per channel |
| ToFU → MoFU conversion rate | Quality of channel's prospects |
| Cost per first touch | Efficiency of awareness spend |
| New visitor composition | Mix of discovery channels |
Implementation
class TofuAttribution
def initialize(journey, lookback_days: 60)
@journey = journey
@lookback_days = lookback_days
end
def attribute
first_touchpoint = relevant_touchpoints.first
return nil unless first_touchpoint
{
stage: :tofu,
channel: first_touchpoint.channel,
source: first_touchpoint.source,
medium: first_touchpoint.medium,
campaign: first_touchpoint.campaign,
credit: 1.0,
touchpoint_at: first_touchpoint.occurred_at
}
end
private
def relevant_touchpoints
@journey.touchpoints
.where("occurred_at >= ?", @journey.conversion_at - @lookback_days.days)
.order(:occurred_at)
end
end
First-touch share by channel
The introducers (Paid Social, Organic, Display, Content) dominate ToFU. The closers (Email, Branded Search) barely show up — because they're not introducing anyone.
If your last-click report says Email and Branded Search are your top channels, this is the report that explains why that's misleading. Those channels capture demand. They don't create it. The introducers in this chart did.
Stage 2: Middle of Funnel (Linear or Participation)
Choosing Between Linear and Participation
Linear Attribution: Divides 100% credit equally among all mid-funnel touchpoints.
LINEAR MOFU CREDIT
Linear divides 100% MoFU credit equally across all mid-funnel touchpoints. ToFU and BoFU are tracked separately by their own models.
Participation Attribution: Gives 100% credit to each participating channel (total exceeds 100%).
PARTICIPATION MOFU CREDIT
Participation gives 100% to each channel that touched the journey. Totals exceed 100% on purpose — the goal is to see which channels co-occur, not divide budget.
When to Use Each
| Use Case | Linear | Participation |
|---|---|---|
| Budget allocation | ✓ Sums to 100% | ✗ Sums exceed 100% |
| Channel overlap analysis | ✗ Hides overlap | ✓ Shows interdependence |
| Journey complexity assessment | ✗ Normalized | ✓ Raw touch count |
| Simple reporting | ✓ Intuitive | ✗ Requires explanation |
Recommendation: Run both. Use Linear for budget math. Use Participation to understand how channels work together.
Interpreting Participation Totals
The sum of participation credits tells you about journey complexity:
| Participation Sum | Interpretation |
|---|---|
| 100-120% | Simple journeys, channels work independently |
| 150-200% | Moderate complexity, 1.5-2 channels per journey |
| 200-300% | Complex journeys, heavy channel interdependence |
| 300%+ | Long nurturing cycles, channels deeply intertwined |
If participation sum is high: Cutting any channel may hurt others—they're working together.
If participation sum is low: Channels are more independent—isolated optimization is safer.
Implementation
class MofuAttribution
def initialize(journey, model: :linear, lookback_days: 30)
@journey = journey
@model = model
@lookback_days = lookback_days
end
def attribute
mid_funnel_touchpoints = get_mid_funnel_touchpoints
return [] if mid_funnel_touchpoints.empty?
case @model
when :linear
linear_attribution(mid_funnel_touchpoints)
when :participation
participation_attribution(mid_funnel_touchpoints)
end
end
private
def get_mid_funnel_touchpoints
# All touchpoints between first and last
all_touchpoints = @journey.touchpoints.order(:occurred_at)
return [] if all_touchpoints.size <= 2
all_touchpoints[1..-2] # Exclude first (ToFU) and last (BoFU)
end
def linear_attribution(touchpoints)
credit_per_touch = 1.0 / touchpoints.count
touchpoints.map do |tp|
{
stage: :mofu,
channel: tp.channel,
credit: credit_per_touch,
touchpoint_at: tp.occurred_at
}
end
end
def participation_attribution(touchpoints)
touchpoints.map do |tp|
{
stage: :mofu,
channel: tp.channel,
credit: 1.0, # Full credit to each participant
touchpoint_at: tp.occurred_at
}
end
end
end
Linear vs Participation, same journey
Linear sums to 100% (clean for budget). Participation lets totals exceed 100% on purpose — that's the signal.
Blog
Webinar
Email A
Email B
When participation totals are much greater than 100%, your channels are working together. That's a feature, not a bug. Cut a channel that scored 100% in participation and you'll affect the deals where it co-occurred with other channels — not just the deals where it was the only touch.
Stage 3: Bottom of Funnel (Last-Touch)
What It Measures
Last-touch attribution credits the final touchpoint before conversion—the channel that closed the deal.
BOFU ATTRIBUTION (LAST-TOUCH)
Last-touch credits the closing channel (Demo). Earlier touches are tracked separately by the ToFU and MoFU models.
Which Channels Win at BoFU
| Channel | Typical BoFU Share | Why |
|---|---|---|
| 25-40% | Triggered sends drive action | |
| Branded Search | 15-25% | Ready-to-buy users search brand |
| Retargeting | 10-20% | Reminder at decision moment |
| Direct | 15-25% | Brand recall, returning visitors |
| Organic Search | 10-15% | Product/comparison searches |
Channels that rarely win BoFU:
- Display (awareness, not conversion)
- Paid Social (introduction, not closing)
- Content (education, not action)
BoFU Metrics to Track
| Metric | What It Tells You |
|---|---|
| Conversions by closing channel | Which channels convert |
| Revenue by closing channel | Revenue contribution |
| BoFU conversion rate by channel | Closing efficiency |
| Time from MoFU to conversion | Decision window length |
Implementation
class BofuAttribution
def initialize(journey, lookback_days: 14)
@journey = journey
@lookback_days = lookback_days
end
def attribute
last_touchpoint = relevant_touchpoints.last
return nil unless last_touchpoint
{
stage: :bofu,
channel: last_touchpoint.channel,
source: last_touchpoint.source,
medium: last_touchpoint.medium,
campaign: last_touchpoint.campaign,
credit: 1.0,
revenue: @journey.conversion_value,
touchpoint_at: last_touchpoint.occurred_at
}
end
private
def relevant_touchpoints
@journey.touchpoints
.where("occurred_at >= ?", @journey.conversion_at - @lookback_days.days)
.where("occurred_at <= ?", @journey.conversion_at)
.order(:occurred_at)
end
end
Combining All Three: Full-Funnel View
The Complete Picture
class FullFunnelAttribution
def initialize(journey)
@journey = journey
end
def attribute
{
tofu: tofu_attribution.attribute,
mofu: mofu_attribution.attribute,
bofu: bofu_attribution.attribute,
journey_id: @journey.id,
conversion_value: @journey.conversion_value
}
end
def full_funnel_credit
# Combine stages for channel-level view
combine_stage_credits
end
private
def tofu_attribution
TofuAttribution.new(@journey)
end
def mofu_attribution
MofuAttribution.new(@journey, model: :linear)
end
def bofu_attribution
BofuAttribution.new(@journey)
end
def combine_stage_credits
credits = {}
# Weight each stage (customize based on your funnel value)
stage_weights = { tofu: 0.30, mofu: 0.30, bofu: 0.40 }
[tofu_attribution.attribute].flatten.compact.each do |credit|
channel = credit[:channel]
credits[channel] ||= { tofu: 0, mofu: 0, bofu: 0, total: 0 }
credits[channel][:tofu] += credit[:credit] * stage_weights[:tofu]
credits[channel][:total] += credit[:credit] * stage_weights[:tofu]
end
mofu_attribution.attribute.each do |credit|
channel = credit[:channel]
credits[channel] ||= { tofu: 0, mofu: 0, bofu: 0, total: 0 }
credits[channel][:mofu] += credit[:credit] * stage_weights[:mofu]
credits[channel][:total] += credit[:credit] * stage_weights[:mofu]
end
[bofu_attribution.attribute].flatten.compact.each do |credit|
channel = credit[:channel]
credits[channel] ||= { tofu: 0, mofu: 0, bofu: 0, total: 0 }
credits[channel][:bofu] += credit[:credit] * stage_weights[:bofu]
credits[channel][:total] += credit[:credit] * stage_weights[:bofu]
end
credits
end
end
Example: Full-Funnel Channel Analysis
Journey: Facebook Ad (Day 1) → Blog (Day 5) → Webinar (Day 12) → Email × 2 (Days 18–20) → Demo Request (Day 25). Stage weights: ToFU 30% / MoFU 30% / BoFU 40%.
| Channel | ToFU | MoFU | BoFU | Total | Role |
|---|---|---|---|---|---|
| Paid Social | 30.0% | 0% | 0% | 30.0% | introducer |
| Organic / Content | 0% | 10.0% | 0% | 10.0% | consideration driver |
| Webinar | 0% | 10.0% | 0% | 10.0% | consideration driver |
| 0% | 10.0% | 0% | 10.0% | nurturer | |
| Demo | 0% | 0% | 40.0% | 40.0% | closer |
| Total | 30.0% | 30.0% | 40.0% | 100% |
Each channel's role becomes legible — Paid Social introduces, Demo closes, Content + Webinar together carry the 20% consideration job. A single-model report would have piled most of this on whichever channel was last-clicked.
Configuring Stage Weights
Default Stage Weights
The weighting between stages should reflect your funnel's value creation:
| Business Type | ToFU Weight | MoFU Weight | BoFU Weight |
|---|---|---|---|
| E-commerce (impulse) | 20% | 20% | 60% |
| E-commerce (considered) | 30% | 25% | 45% |
| B2B SaaS | 35% | 30% | 35% |
| B2B Enterprise | 40% | 35% | 25% |
| DTC/Subscription | 30% | 30% | 40% |
Adjusting Based on Your Data
Test your weights by comparing forecasts to actuals:
- Set initial weights based on business type
- Run forecast for 90 days
- Compare predicted vs actual channel performance
- Adjust weights where predictions missed
- Validate with holdout tests
# Example: Testing stage weight accuracy
class StageWeightOptimizer
def initialize(historical_data, stage_weights)
@data = historical_data
@weights = stage_weights
end
def evaluate
predicted = calculate_predicted_contribution(@data, @weights)
actual = calculate_actual_performance(@data)
@data.channels.each do |channel|
error = (predicted[channel] - actual[channel]).abs
puts "#{channel}: Predicted #{predicted[channel]}%, Actual #{actual[channel]}%, Error #{error}%"
end
end
# Adjust weights to minimize prediction error
def optimize
# ... gradient descent or grid search over weight combinations
end
end
Common Mistakes
Mistake 1: Same Lookback Window for All Stages
Each stage operates on different timeframes:
| Stage | Default Window | Why |
|---|---|---|
| ToFU | 30-60 days | Discovery can happen well before engagement |
| MoFU | 14-30 days | Active consideration period |
| BoFU | 7-14 days | Final decision timeframe |
Using 7-day windows for ToFU misses the actual first touch.
Mistake 2: Ignoring MoFU Entirely
Some teams jump from first-touch to last-touch, skipping mid-funnel entirely. This hides:
- How channels work together
- Which content drives engagement
- Where prospects get stuck
MoFU attribution reveals your nurturing effectiveness.
Mistake 3: Not Defining Stage Boundaries
Without clear definitions, you'll inconsistently categorize touchpoints:
| Stage Transition | Good Definition | Bad Definition |
|---|---|---|
| ToFU → MoFU | "Second session" or "Email signup" | "When they're interested" |
| MoFU → BoFU | "Pricing page visit" or "Demo request" | "When they're ready to buy" |
Use concrete, trackable events as stage boundaries.
Mistake 4: Static Weights Forever
Your funnel changes. New channels, new content, seasonal patterns. Review and adjust stage weights quarterly.
Summary
Tiered attribution uses the right model for each funnel stage:
| Stage | Model | Question | Credits |
|---|---|---|---|
| ToFU | First-touch | Who introduces? | Discovery channels |
| MoFU | Linear/Participation | Who nurtures? | All mid-funnel touches |
| BoFU | Last-touch | Who closes? | Conversion channels |
Key benefits:
- Accurate channel valuation at each stage
- Reveals introducer vs closer vs nurturer roles
- Enables better budget allocation
- Produces more reliable forecasts
Implementation steps:
1. Define your stage boundaries (concrete events)
2. Choose MoFU model (Linear for budgets, Participation for overlap)
3. Set stage weights based on business type
4. Configure lookback windows per stage
5. Combine for full-funnel view
Further Reading
On Building Forecasts:
- Why Doesn't Last-Touch Work for Funnel Forecasting? — The problem this solves
- How to Build a Bottom-Up Revenue Forecast with MTA — Complete forecasting workflow
On Individual Models:
- First-Touch Attribution — ToFU model deep dive
- Last-Touch Attribution — BoFU model deep dive
- Linear Attribution — MoFU model option
Key Takeaways
- ✓First-touch for ToFU: credits discovery channels (paid social, content, display)
- ✓Linear/Participation for MoFU: shows nurturing contribution and channel overlap
- ✓Last-touch for BoFU: credits closing channels (email, branded search, retargeting)
- ✓Full-funnel view combines all three for accurate budget allocation
What's the difference between Linear and Participation for mid-funnel?▼
Should I use the same lookback window for all funnel stages?▼
How do I know where ToFU ends and MoFU begins?▼
What if my business has a 2-day purchase cycle?▼
Can I use tiered attribution in Google Analytics?▼
How mature is your marketing measurement?
The free Measurement Maturity Assessment shows where you stand, where you're exposed, and what to fix first. 10 questions, 3 minutes.
Take the AssessmentReady to try server-side attribution?
Set up in 10 minutes. Free up to 30K records/month.