A target account reads 5 reviews of your competitor on TrustRadius, visits your pricing page twice, and posts a job for the role that would use your product. That's not a lead. That's a buying signal. And most companies never see it.
Account-Based Marketing signals come from five different tools, none of which talk to each other. Your warehouse is where they converge into something actionable.
What ABM signals actually are
Three categories:
1. Buying intent signals
Direct indicators that a company is researching your category:
- Review site activity — reading reviews on G2, TrustRadius, Capterra for products in your space
- Content consumption — downloading whitepapers, attending webinars from you or competitors
- Website visits — visiting your pricing page, case studies, comparison pages (via RB2B or similar)
- Search behavior — searching for category terms, competitor names, solution keywords (from Bombora, G2 intent)
2. Tech-stack signals
Changes in a company's technology that indicate readiness to buy:
- New tool adoption — switching from a competitor to a complementary tool (spotted via BuiltWith, HG Insights)
- Tool removal — dropping a tool you replace (contract ending, tech-stack change)
- Integration patterns — adopting tools that commonly pair with yours
3. Organizational signals
Company-level changes that create buying opportunities:
- Hiring patterns — posting roles that would use your product (data engineers, RevOps, marketing ops)
- Funding events — new round means new budget and new priorities
- Leadership changes — new CTO, VP of Data, or CMO often triggers vendor evaluation
- Expansion — new offices, new markets, growing headcount
The pipeline we built
For one client (SecureW2), we built the full signal-processing pipeline:
Clay (enrichment) ──→ Webhooks ──→ Cloud Function ──→ Pub/Sub ──→ BigQuery
TrustRadius ──→ API ──→ Cloud Function ──→ BigQuery
ZoomInfo intent ──→ API ──→ Cloud Function ──→ BigQuery
HG Insights ──→ CSV upload ──→ BigQuery
LinkedIn Jobs ──→ Scraper ──→ BigQuery
RB2B (web visitors) ──→ Webhook ──→ Cloud Function ──→ BigQuery
│
▼
dbt models (classify, score, route)
│
▼
Salesforce sync + Ad audience push + Slack alerts
Staging: normalize signals
-- models/staging/stg_signals__unified.sql
SELECT
signal_id,
account_domain,
account_name,
signal_type, -- 'buying', 'tech', 'organizational'
signal_source, -- 'trustradius', 'zoominfo', 'rb2b', 'linkedin_jobs', 'clay'
signal_detail, -- human-readable description
signal_strength, -- raw score from source (normalized 0-100)
detected_at,
raw_payload
FROM {{ source('signals', 'raw_buying_signals') }}
UNION ALL
SELECT
signal_id,
account_domain,
account_name,
'tech' AS signal_type,
'hg_insights' AS signal_source,
CONCAT('Adopted ', technology_name) AS signal_detail,
80 AS signal_strength, -- tech adoption is a strong signal
detected_at,
raw_payload
FROM {{ source('signals', 'raw_tech_signals') }}
UNION ALL
SELECT
signal_id,
account_domain,
account_name,
'organizational' AS signal_type,
'linkedin_jobs' AS signal_source,
CONCAT('Hiring: ', job_title) AS signal_detail,
CASE
WHEN LOWER(job_title) LIKE '%head of data%' THEN 90
WHEN LOWER(job_title) LIKE '%data engineer%' THEN 70
WHEN LOWER(job_title) LIKE '%analytics%' THEN 60
ELSE 40
END AS signal_strength,
posted_date AS detected_at,
raw_payload
FROM {{ source('signals', 'raw_job_signals') }}Scoring: aggregate per account
Individual signals are weak. Aggregated signals per account are strong.
-- models/intermediate/int_signals__account_score.sql
SELECT
account_domain,
account_name,
COUNT(*) AS total_signals,
COUNT(DISTINCT signal_type) AS signal_diversity,
SUM(signal_strength) AS raw_score,
MAX(detected_at) AS most_recent_signal,
-- Weighted score: more signal types = higher confidence
SUM(signal_strength) * (1 + 0.2 * COUNT(DISTINCT signal_type)) AS weighted_score,
-- Time decay: recent signals matter more
SUM(
signal_strength * EXP(-0.1 * DATE_DIFF(CURRENT_DATE, DATE(detected_at), DAY))
) AS decayed_score,
ARRAY_AGG(STRUCT(signal_type, signal_source, signal_detail, detected_at)
ORDER BY detected_at DESC LIMIT 5) AS recent_signals
FROM {{ ref('stg_signals__unified') }}
WHERE detected_at >= CURRENT_DATE - 90 -- 90-day lookback
GROUP BY 1, 2Routing: threshold → action
-- models/marts/mart_signal_routing.sql
SELECT
s.account_domain,
s.account_name,
s.decayed_score,
s.total_signals,
s.recent_signals,
-- Check CRM status
sf.account_id AS salesforce_account_id,
sf.account_owner,
sf.account_stage,
-- Route decision
CASE
WHEN sf.account_id IS NOT NULL AND sf.account_stage = 'Customer'
THEN 'UPSELL_ALERT' -- existing customer showing new signals
WHEN sf.account_id IS NOT NULL AND sf.account_stage IN ('Prospect', 'Qualified')
THEN 'ACCELERATE' -- known prospect with rising intent
WHEN s.decayed_score > 200
THEN 'NEW_HIGH_INTENT' -- unknown account, strong signals
WHEN s.decayed_score > 100
THEN 'ADD_TO_NURTURE' -- warm enough for ad audience
ELSE 'MONITOR'
END AS routing_action
FROM {{ ref('int_signals__account_score') }} s
LEFT JOIN {{ ref('stg_salesforce__accounts') }} sf
ON s.account_domain = sf.website_domainActivation
The routing model feeds three outputs:
- Salesforce sync (via Hightouch) — create or update accounts, attach signals as activities, assign to the right owner
- Ad audience push — accounts scoring above threshold get added to LinkedIn and Google Ads matched audiences for retargeting
- Slack alerts — high-intent signals from named accounts get pushed to a
#signalschannel with context
The scoring philosophy
Signal scoring is an art, not a science. Our approach:
- Start simple. Binary thresholds first (above/below). Add nuance later.
- Weight by signal type. A pricing page visit (buying intent) matters more than a job posting (organizational). A TrustRadius competitor review (buying intent + competitive) matters most.
- Require signal diversity. One signal from one source is noise. Three signals from three sources is a pattern. The
signal_diversitymultiplier handles this. - Decay over time. A signal from last week matters. A signal from 4 months ago doesn't. Exponential decay ensures the score reflects current intent, not historical activity.
- Validate against closed deals. After 3 months, compare signal scores at the time of deal creation against win rates. Adjust weights based on what actually predicts revenue.
What most teams get wrong
1. Buying a tool instead of building a pipeline
6sense, Demandbase, and Bombora are expensive and opaque. They give you a score and a tier. They don't show you the underlying signals, and you can't customize the scoring for your specific ICP.
A warehouse-native approach costs less, gives you full transparency, and lets you iterate on the scoring model weekly.
2. Treating all signals equally
A website visit is not the same as a competitor review. Weight accordingly.
3. No CRM integration
Signals that don't reach the sales team don't generate revenue. The pipeline must end in Salesforce (or whatever CRM you use), with signals attached to accounts and routed to owners.
The payoff
Before the pipeline: sales reps research accounts manually, check LinkedIn sporadically, and rely on inbound to tell them who's interested.
After: a Slack message arrives — "Acme Corp (500 employees, $2M ARR target) scored 280 this week. Signals: visited pricing page 3x, posted 2 data-engineer roles, reviewed Competitor X on TrustRadius. Routed to: @sales-rep."
That's the difference between waiting for a lead and knowing who's ready to buy.
We built this exact pipeline for a B2B SaaS company — from Clay webhooks to BigQuery to Salesforce sync. If your sales team is still guessing which accounts are in-market, book a discovery call and we'll show you what signal-driven outreach looks like.