AI Detection of Business Signals: Building a Useful RevOps System¶
There comes a point where commercial databases are no longer enough.
They can tell you a company exists. They sometimes give you an industry, a size, a website, a LinkedIn account, a few contacts.
But they rarely answer the most interesting question:
Why does this company deserve to be contacted right now?
That question is what gradually led me to work on a broader topic: business signal detection using AI, scraping, automated workflows and some RevOps logic. Not to build a magic machine to sell. Not to replace salespeople. But to better prepare sales work, understand the movements of a company, and turn fairly noisy public data into actionable context.
This page is an entry point. It connects several technical experiments around scraping, competitive monitoring, expansion signals and semi-automated prospecting, and explains the overall reasoning behind these projects.
Summary
- What this system does: automatically detect expansion, competitive and technical signals from WTTJ, LinkedIn, Trustpilot and the web.
- Main stack: n8n · Scrapy · Supabase · OpenRouter · Redis · Apify.
- The real challenge: not collection, but filtering noise, avoiding false positives, and making the output actionable.
- The role of AI here: classify and summarize already-structured signals, never decide on its own.
- What I mostly learned: a useful RevOps system is not the one that automates the most, it is the one that makes the best context available at the right moment.
Related Technical Notes¶
-
Detecting Web Technologies with Scrapy
How I built a tech stack detection engine from HTML, scripts, cookies, headers and signatures.
-
The SDR Lead Machine
How I assembled a company database, Hunter.io, pain points, customer references, n8n, Microsoft Lists and Power BI to help SDRs better prepare their prospecting.
-
Multi-Source Competitive Monitoring
How I combined Trustpilot, LinkedIn, websites, Supabase and an LLM to extract useful competitive signals.
-
Expansion Signals with WTTJ, LinkedIn and n8n
How I used WTTJ via Algolia, LinkedIn, news pages and Redis to detect growth indicators at B2B companies.
The Real Problem: Salespeople Rarely Lack Data¶
The problem is not having data. In B2B, you can already buy or enrich a lot of information: name, domain, industry, size, country, LinkedIn page, contacts, emails, job titles, supposed technologies, funding rounds, public news.
The real problem lies elsewhere.
Raw data rarely tells you what to do with it.
A 300-person SaaS company might be interesting. But why now? What has changed? Are they hiring? Are they opening a new market? Are they changing their stack? Are they getting negative reviews on their support? Are they looking for profiles that indicate an internal transformation?
That is where the concept of business signals becomes interesting.
The key insight
A commercial database describes a company. A business signal describes a movement.
And in many sales contexts, the movement matters more than the static record.
What I Call a Business Signal¶
A business signal is not just a news item. It is public or semi-structured information that can indicate a company is going through a particular moment.
| Detected signal | What it might suggest | Possible commercial angle |
|---|---|---|
| Heavy recruiting on WTTJ | Growth, expansion, team structuring | Offer a solution that supports scaling |
| New LinkedIn posts about a market | Repositioning or commercial expansion | Adapt messaging to this new market |
| Recurring negative Trustpilot reviews | Customer friction, support issue, product problem | Identify a credible competitive angle |
| Marketing stack change | Migration, digital maturity, ongoing project | Propose an audit, integration or alternative |
| New product page published | New offer, repositioning | Monitor the evolution of commercial messaging |
| Surge in recruitment content | Internal growth or difficulty attracting talent | Spot HR or operational priorities |
The signal is not necessarily proof. It is often a clue. And that is precisely why you need to be careful.
A signal is not a truth
A job posting does not prove a company is in hypergrowth. A negative review does not prove a competitor is struggling. A script detected in HTML does not always prove a technology is actually in use.
The system should help formulate a hypothesis, not produce an artificial certainty.
Why This Topic Interested Me¶
At first, I did not think I was working on a "RevOps system."
I was starting from much more concrete problems. In my prospecting workflows, I was spending too much time opening websites, inspecting source code, looking at loaded scripts, searching for marketing tools, checking whether a company was still publishing, reading barely usable LinkedIn posts, looking at job listings, cross-referencing information across several tabs, and trying to turn all of that into a credible sales message.
Each task was simple. But the whole thing was slow, repetitive and hard to industrialize properly.
The topic interested me because it sits exactly at the intersection of several domains I like: the web, scraping, automation, AI, data, prospecting, operational workflows and product logic.
It is not just a technical topic. It is also a system design topic.
How do you collect information without drowning in it? How do you keep reasoning understandable? How do you stop AI from deciding everything for you? How do you build something useful for someone who actually needs to sell afterward?
What I Wanted to Build¶
I wanted to build an actionable commercial intelligence layer. Not a CRM. Not an autonomous "AI SDR" platform. Not a tool that automatically sends 10,000 messages.
More like a system that helps answer a few simple questions:
- which companies deserve attention this week?
- why now?
- what context has been detected?
- what source allows verification?
- what commercial angle might be relevant?
- what message could be prepared, but not sent without validation?
Public data
↓
Automated collection
↓
Signal extraction
↓
AI qualification
↓
Prioritization
↓
Assisted sales action
What interested me most was the transition between available information and actionable information.
Starting Constraints¶
I did not want to start from an overly theoretical architecture.
| Constraint | Consequence |
|---|---|
| Limited budget | Avoid expensive APIs or closed platforms |
| Need to iterate fast | Use n8n, Python, Supabase, OpenRouter |
| Heterogeneous sources | Plan for cleaning, fallbacks, errors |
| Results usable by humans | Keep sources, evidence and reasoning |
| No complete black box | Avoid AI scoring that cannot be explained |
| Unstable web data | Accept timeouts, false positives, broken pages |
The temptation was to make everything "intelligent." But the further I went, the more I realized that robustness came mostly from simple components: well-identified sources, readable rules, scoped prompts, strict JSON output, basic deduplication, explainable scoring.
What I learned fairly quickly
A useful RevOps system is not necessarily the one that automates the most. It is often the one that makes the best context available at the right moment.
Why Existing Solutions Did Not Work for Me¶
There are already many tools around sales intelligence, monitoring, enrichment and intent data. The problem, in my case, was not their existence. It was their lack of flexibility for experimentation.
| Tool type | What bothered me |
|---|---|
| B2B databases | Useful data, but often static |
| Intent data tools | Opaque scoring, hard to audit |
| Monitoring tools | Too generic, lots of noise |
| Tech detection extensions | Poorly suited to automated workflows |
| Sales intelligence platforms | High cost, closed logic |
| Off-the-shelf scrapers | Hard to customize, strong dependency |
I wanted to be able to quickly modify sources, detection rules, thresholds, prompts, output formats and triggered actions. And above all, I wanted to see the reasoning.
Commercial information is not useful simply because it exists. It becomes useful when you understand where it comes from, why it was retained, what it suggests, and what it does not allow you to claim.
Overall Architecture¶
Across projects, the general architecture started to look like this:
flowchart TD
A[Public sources] --> B[Automated collection]
A1[Websites] --> B
A2[LinkedIn] --> B
A3[WTTJ] --> B
A4[Trustpilot] --> B
A5[Company databases] --> B
B --> C[Cleaning & normalization]
C --> D[Signal extraction]
D --> D1[Technical signals]
D --> D2[Expansion signals]
D --> D3[Competitive signals]
D --> D4[Prospecting signals]
D1 --> E[AI qualification]
D2 --> E
D3 --> E
D4 --> E
E --> F[Explainable scoring]
F --> G[Supabase storage]
G --> H[Operational interface]
H --> I[SDR action]
H --> J[RevOps monitoring]
H --> K[Reporting]
The important idea: AI is not placed at the beginning. I do not use it to "guess" from nothing. I use it after a collection and structuring step.
flowchart LR
A[Raw data] --> B[Parsing]
B --> C[Structured context]
C --> D[AI analysis]
D --> E[Actionable JSON]
E --> F[Human action]
Design principle
The more structured the input sent to the LLM, the more stable the output. The prompt should not compensate for a dirty data pipeline.
The Main Signal Families¶
I gradually distinguished several signal families.
mindmap
root((Business signals))
Technical
Marketing stack
CMS
CRM
Tracking
Visible technical debt
Expansion
Recruiting
New markets
New offers
Team growth
Competitive
Customer reviews
Positioning
Pricing
Product changes
Commercial
Likely pain points
Relevant references
Message angles
SDR priority
This categorization helped me avoid mixing everything together. A technical signal does not carry the same level of evidence as an expansion signal. A Trustpilot review does not have the same nature as a WTTJ job posting. Each source has its use, but also its blind spots.
Stack Used¶
The stack evolved through experimentation, but several components kept coming back.
| Tool | Role | Why this choice |
|---|---|---|
| Scrapy | Web crawling and HTML collection | Fast, structured, suited to volume |
| Playwright | JavaScript fallback | Useful when the initial HTML is empty |
| n8n | Workflow orchestration | Fast to prototype and connect components |
| Supabase | Structured storage | Simple, PostgreSQL, convenient for history |
| OpenRouter | LLM calls | Flexible, lets you test multiple models |
| GPT-4o-mini | AI analysis at reasonable cost | Sufficient for classification and extraction |
| Apify | LinkedIn collection | Avoids maintaining a fragile scraper |
| Hunter.io | Company and contact enrichment | Useful for going from company to contact |
| Redis | Concurrency control | Avoids saturating external APIs |
| Microsoft Lists | Simple SDR interface | Understandable by an operational team |
| Power BI | Reporting | Lets you track volume, statuses and performance |
Why n8n¶
n8n served as my backbone. Not because it is perfect, but because it is very effective for quickly testing chains of actions. What I appreciated: visualizing the workflow, quickly connecting APIs, testing a prompt, inspecting inputs and outputs, relaunching an execution, prototyping before writing clean code.
What I liked less: workflows quickly get large, readability drops when there are too many branches, and some complex transformations would be cleaner in code.
The n8n trap
n8n gives the impression you can assemble everything very quickly. That is true at the start. But as soon as the business logic grows, you need to be disciplined: naming, sub-workflows, logs, normalized outputs, error handling.
Why Scrapy¶
Scrapy entered the system through the web technology detection project. At first, I simply wanted to avoid opening sites one by one to understand their stack.
The key point: Scrapy does not detect anything on its own. It collects signals. The business logic lives elsewhere: in the signatures, the scoring and the interpretation.
flowchart TD
A[Domain list] --> B[Scrapy spider]
B --> C[HTML]
B --> D[Scripts]
B --> E[Headers]
B --> F[Cookies]
B --> G[Meta tags]
C --> H[Signature engine]
D --> H
E --> H
F --> H
G --> H
H --> I[Confidence score]
I --> J[Detected technologies]
J --> K[RevOps workflow]
To go further on this part: Building a Web Technology Detector with Scrapy
Why Supabase¶
Supabase mostly helped me keep a clean foundation: companies, domains, signals, crawl results, WTTJ slugs, enrichments, statuses, history.
The temptation at first is to route everything through JSON or CSV files. That is fine for testing. But as soon as you want to compare over time, deduplicate, score and track history, a database becomes necessary.
Simplified structure:
create table companies (
id uuid primary key default gen_random_uuid(),
name text,
domain text,
linkedin_url text,
industry text,
headcount text,
country text,
wttj_slug text,
created_at timestamptz default now()
);
create table business_signals (
id uuid primary key default gen_random_uuid(),
company_id uuid references companies(id),
source text,
signal_type text,
title text,
summary text,
evidence_url text,
confidence_score numeric,
detected_at timestamptz default now(),
raw_payload jsonb,
prompt_version text
);
Why AI, But Not Everywhere¶
AI is useful in this system, but only in certain places.
I used it to summarize signals, classify information, identify a commercial angle, distinguish noise from signal, formulate a hypothesis, generate JSON output, and prepare a sales brief.
I avoided using it to decide truth on its own, to scrape, to deduplicate without a stable key, to replace simple rules, to hide sources, or to invent missing context.
Personal rule
If a simple rule is enough, I prefer a simple rule. AI comes in when the context becomes ambiguous, narrative or hard to reduce to a condition.
Step-by-Step Construction¶
Step 1 — Build a Company Database¶
Everything starts with a list. In the SDR lead machine project, I started with a company website database pulled from several sources. The goal was not to have a perfect database from the start, it was actually the opposite: accept an imperfect database, then use the following steps to filter it.
flowchart LR
A[Domain sources] --> B[Cleaning]
B --> C[Deduplication]
C --> D[Company database]
D --> E[Crawl]
E --> F[Qualification]
You often want to start with "perfect data." In practice, it does not exist. You start with noise, then build mechanisms to filter, enrich, score, exclude and learn.
Step 2 — Detect Technical Signals¶
The first industrialized signal family concerns web technologies.
{
"domain": "example-store.com",
"technologies": [
{
"name": "Shopify",
"confidence": 0.97,
"signals": ["cdn.shopify.com", "_shopify_y", "shopify-checkout"]
},
{
"name": "Klaviyo",
"confidence": 0.91,
"signals": ["static.klaviyo.com", "__kla_id"]
}
],
"meta": {
"crawl_time_ms": 1840,
"javascript_rendered": false,
"redirects": 1
}
}
This type of output can be reused to identify companies using a competing technology, detect e-commerce stacks, estimate marketing maturity, feed a scoring model or personalize a message. But it requires discipline: each detection must keep its evidence.
Step 3 — Enrich Companies and Contacts¶
Once companies are filtered, the next step is enrichment. In the SDR workflow, Hunter.io was used to retrieve company information, contacts, job titles and sometimes emails.
But enrichment alone is not enough. A contact without context often produces a generic message. Context without a contact remains hard to act on. You need both.
Field note
The hardest part is not finding contacts. The hardest part is not generating a list of useless contacts.
Step 4 — Cross-Reference with Pain Points and References¶
Personalization should not come only from the LLM. In my approach, I preferred preparing business databases: pain points by competing technology, customer references by industry, differentiating arguments, integration capabilities.
The LLM therefore does not start from a blank page. It receives scoped context:
{
"company": {
"name": "Example Company",
"industry": "Retail",
"detected_technologies": ["Klaviyo", "Shopify"]
},
"pain_points": [
"difficulty synchronizing CRM data",
"limited segmentation on some advanced scenarios"
],
"relevant_references": [
{
"name": "Client A",
"sector": "Retail",
"use_case": "CRM automation + email"
}
],
"commercial_goal": "prepare a contextualized prospecting angle"
}
Instead of asking "Write a personalized email for this company," you ask: "Based on the detected signals, known pain points and relevant references, suggest a careful, verifiable and useful commercial angle for an SDR." The difference in output quality is significant.
Step 5 — Detect Expansion Signals¶
Expansion signals come from complementary sources.
| Source | What it can reveal | Main limitation |
|---|---|---|
| WTTJ | Active recruiting, job types sought, location | Requires good company / slug mapping |
| Public announcements, launches, partnerships | A lot of noise | |
| News pages | Official messaging, new markets, new offers | Heterogeneous web structures |
flowchart TD
A[(Supabase companies)] --> B[n8n Scheduler]
B --> C{Loop companies}
C --> D[WTTJ via Algolia]
C --> E[LinkedIn via Apify]
C --> F[News pages]
D --> G[Job parsing]
E --> H[Post parsing]
F --> I[Content parsing]
G --> J[Structured context]
H --> J
I --> J
J --> K[AI analysis]
K --> L[Signal JSON]
L --> M[(Supabase)]
An important lesson about WTTJ: going through the Algolia requests visible on the frontend is much more stable than parsing HTML. It is not a hack, it is the normal behavior of a client-side search-only key. But it creates another difficulty: you need to know the correct WTTJ slug for the company.
Classic mistake
When the slug is missing, the API can return zero results without an error. Technically, everything works. Functionally, the signal is lost.
For the details: Detecting Company Expansion Signals with WTTJ, LinkedIn and n8n
Step 6 — Build Multi-Source Competitive Monitoring¶
Competitive monitoring adds another difficulty: the signal is rarely isolated. A single Trustpilot review can be anecdotal. A single LinkedIn post can be marketing. When multiple sources tell a coherent story, the signal becomes more interesting.
flowchart TD
A[Competitor] --> B[Trustpilot]
A --> C[LinkedIn]
A --> D[Website / blog]
B --> E[Customer friction]
C --> F[Visible movements]
D --> G[Official messaging]
E --> H[Context assembly]
F --> H
G --> H
H --> I[AI analysis]
I --> J[Competitive signal]
J --> K[Prioritization]
Example of expected output
{
"competitor": "Example Competitor",
"signal_type": "customer_support_weakness",
"confidence": 0.78,
"sources": ["trustpilot", "linkedin"],
"summary": "Several recent reviews mention long response times. Meanwhile, the company is communicating about strong customer growth.",
"commercial_angle": "Position support quality and responsiveness as differentiators, without directly attacking the competitor.",
"evidence": [
{ "source": "trustpilot", "url": "https://example.com/review/123" },
{ "source": "linkedin", "url": "https://linkedin.com/posts/example" }
]
}
For the details: Building Multi-Source Competitive Monitoring with Trustpilot, LinkedIn and the Web
Step 7 — Make the System Actionable for SDRs¶
A signal sitting in a database is not very useful. You need an operational output.
In the SDR project, the interface went through Microsoft Lists, with simple statuses:
| Prospect status | Email status | Sales status |
|---|---|---|
| new | not sent | to process |
| in progress | sent | follow-up |
| closed | replied | meeting |
| off-target | error | declined |
It is not the most glamorous interface. But it is understandable by a team already working in a Microsoft environment.
flowchart LR
A[Qualified signals] --> B[Prioritization]
B --> C[SDR assignment]
C --> D[Microsoft Lists]
D --> E[Teams notification]
D --> F[Status tracking]
F --> G[Power BI]
What worked
Adoption depends less on the sophistication of the system than on the clarity of the output. If the salesperson understands why a company appears in their list, the system becomes useful.
Scoring Model¶
I was not trying to create a perfect score. I was trying to create a readable one.
def compute_signal_score(signal):
score = 0
if signal["source"] == "wttj":
score += 20
if signal["source"] == "linkedin":
score += 10
if signal["source"] == "trustpilot":
score += 15
if signal.get("recency_days", 999) <= 30:
score += 20
if signal.get("has_multiple_sources"):
score += 25
if signal.get("matches_icp"):
score += 20
return min(score, 100)
This scoring is deliberately simple. It does not claim to produce a statistical truth. It is used to sort, filter, prioritize. In an operational system, that is often enough to get started.
To improve later
Scoring becomes smarter with history: which signals actually lead to a reply? which signals produce meetings? which signals are mostly noise?
Indicative Signal Distribution¶
After several weeks of execution, here is how signals and their estimated relevance break down:
pie title Distribution of detected signals by source
"LinkedIn" : 35
"WTTJ" : 25
"Websites / news" : 20
"Trustpilot" : 12
"Tech detection" : 8
The signal-to-noise ratio varies considerably by source:
| Source | Estimated relevance | Comment |
|---|---|---|
| Tech detection | ~70% | Precise signal if signatures are good |
| WTTJ | ~65% | Reliable, but depends on slug and deduplication |
| Trustpilot | ~55% | Biased toward extreme experiences |
| Websites / news | ~45% | Heterogeneous, blogs often abandoned |
| ~25% | Lots of generic employer brand content |
Errors and Problems Encountered¶
1. Assuming Web Data Is Clean¶
It is not. Sites redirect poorly. Certificates expire. Pages change. Content is rendered in JavaScript. Blogs have no dates. Slugs do not match company names. APIs respond differently depending on the case.
In a signal detection workflow, edge cases are the norm.
The real cost
The main cost is not writing the first crawler. The main cost is managing exceptions.
2. Triggering AI Too Early¶
At first, you can be tempted to send a lot of raw content to the LLM. Problems encountered: responses too long, hallucinations, poor prioritization, hard to parse, higher cost, results hard to compare.
What works better:
flowchart LR
A[Raw content] --> B[Cleaning]
B --> C[Useful field extraction]
C --> D[Structured context]
D --> E[Scoped prompt]
E --> F[Strict JSON]
3. Underestimating Deduplication¶
A signal can appear multiple times: same LinkedIn post retrieved twice, WTTJ job reposted, news page crawled on multiple dates, customer review already analyzed.
The simple solution: generate a hash.
import hashlib
def make_signal_hash(company_id, source, title, published_at):
raw = f"{company_id}:{source}:{title}:{published_at}".lower().strip()
return hashlib.sha256(raw.encode("utf-8")).hexdigest()
4. Confusing Volume with Value¶
More signals does not mean a better system. A workflow that surfaces 200 weak signals per week quickly becomes unusable.
| Bad indicator | Better indicator |
|---|---|
| Number of signals detected | Number of actionable signals |
| Number of sources crawled | Quality of useful sources |
| Number of emails generated | Rate of messages actually validated |
| Number of companies enriched | Number of relevant companies |
| Average score | Conversion by signal type |
Simple formulation
The goal is not to find more information. The goal is to reduce the time needed to understand what to do.
What Worked¶
Cross-Referencing Sources¶
Each source alone is imperfect. But cross-referencing changes a lot.
| Source 1 | Source 2 | Stronger interpretation |
|---|---|---|
| WTTJ: Sales recruiting | LinkedIn: new market announcement | Likely commercial expansion |
| Trustpilot: support complaints | Website: premium support messaging | Competitive angle to verify |
| Competing tech detection | Relevant customer reference | More contextualized SDR message |
| Active company blog | Product recruiting | Transformation or launch underway |
Strict JSON Outputs¶
Another point that worked well: forcing structured outputs.
{
"is_relevant": true,
"signal_type": "expansion",
"confidence": 0.82,
"summary": "The company is recruiting several sales profiles in Europe, suggesting an expansion phase.",
"evidence": {
"source": "wttj",
"url": "https://example.com/jobs"
},
"recommended_action": "prepare a growth-support oriented approach"
}
Strict JSON makes the system more testable, more storable, more exploitable and easier to compare over time.
Keeping Humans in the Loop¶
The goal is not for the system to decide on its own. The goal is for it to better prepare human work. The SDR must be able to see the source, understand the reasoning, invalidate a signal, change a status, adapt the message, decide not to contact.
flowchart TD
A[System detects] --> B[Human verifies]
B --> C[Human adjusts]
C --> D[Sales action]
D --> E[Result]
E --> F[Future learning]
The right balance
Automate the preparation. Keep the decision human.
What I Learned¶
- Clean logs from the start. Handled errors, timeouts, retries, typed outputs, preserved sources, deduplication, versioned prompts, readable scoring, historical storage. The boring details make the quality of the system.
- Inspect the network before scraping. WTTJ would have taken several days with HTML parsing. Algolia reduced it to under an hour. Opening DevTools first became a reflex.
- Log "0 results" differently from errors. An empty return can be correct or symptomatic. Treating them the same hides bugs.
- A signal only has value if it helps make a decision. You therefore need to think about the display as much as the collection. A good signal should answer four questions: what was detected? why might it matter? what evidence can be checked? what can be done next?
- Raw totals are a trap. Variation over time is more useful than instantaneous volume. What you really want is a rolling 4-week history.
- The persona in the prompt changes output quality. "Analyze" produces summaries. "You are looking for concrete upsell opportunities" produces actions.
- RevOps is not just about tooling. It is a way of connecting data, workflows, sales teams, existing tools, reporting and field learnings.
- The value comes from the complete loop. Without field feedback, the system remains a hypothesis-generating machine. With field feedback, it can progressively improve.
flowchart LR
A[Collection] --> B[Qualification]
B --> C[Action]
C --> D[Result]
D --> E[Analysis]
E --> A
Current Limitations¶
Scraping Remains Fragile¶
Even with Scrapy, Playwright, Apify or indirect APIs, collection remains dependent on external sources: HTML structure changes, anti-bot protections, costly JavaScript rendering, deleted pages, undated content, undocumented APIs, variable costs depending on volume.
You need to accept this fragility from the start.
Signals Are Not All Comparable¶
A WTTJ job posting, a Trustpilot review, a Klaviyo script and a LinkedIn post do not have the same reliability, freshness, bias, commercial value, or level of evidence. Scoring must therefore remain cautious.
The System Does Not See Everything¶
It sees what is public. It does not see internal projects, commercial discussions, unpublished organizational changes, backend tools, budget decisions, or actual buying cycles.
Do not forget
A signal system does not read companies' minds. It observes public traces and proposes hypotheses.
Scaling Requires Another Level of Rigor¶
A prototype that works on 100 companies does not necessarily work on 10,000. At larger scale, you need to seriously address execution concurrency, API quotas, costs, pagination, silent errors, failure recovery and data governance.
What I Would Do Differently¶
I Would Version Prompts from the Start¶
At first, prompts evolve quickly. You change a sentence, add a field, change an instruction, then you no longer know which version produced which result.
/prompts
/signals
expansion_v1.md
expansion_v2.md
competitor_watch_v1.md
/sales
email_generation_v1.md
account_brief_v1.md
With in the database: alter table business_signals add column prompt_version text;
I Would Build a Business Test Suite¶
Not just technical tests. A real set of cases:
| Case | Expected result |
|---|---|
| 5 recent WTTJ job postings across multiple countries | Expansion signal |
| Generic employer brand LinkedIn post | Not relevant |
| 10 recent negative reviews about support | Competitive signal |
| Blog abandoned for 2 years | Ignore |
| Tech detection with a single ambiguous HTML word | Low confidence |
This is probably what is most missing from many AI workflows: business tests.
I Would Separate Orchestration and Business Logic Earlier¶
n8n is very convenient, but all the logic should not live in the nodes.
flowchart TD
A[n8n orchestration] --> B[Business services]
B --> C[Scraping]
B --> D[Scoring]
B --> E[Deduplication]
B --> F[LLM analysis]
B --> G[Storage]
G --> H[Interface]
H --> I[User feedback]
I --> G
System Evolution¶
flowchart LR
P[Prototype\nLocal Scrapy] --> W[First workflow\nn8n + enrichment]
W --> Q[AI qualification\nStrict JSON]
Q --> S[Storage\nSupabase + history]
S --> I[SDR interface\nLists + Teams]
I --> R[Reporting\nPower BI]
R --> N[Next step\nfeedback loop + adaptive scoring]
FAQ¶
What is a B2B business signal?
A business signal is public or semi-structured information indicating that a company is going through a particular moment: heavy recruiting, stack change, recurring negative customer reviews, geographic expansion announcement.
Unlike a static company record, a signal describes a movement, not a state. It is that movement that makes sales outreach better timed.
What sources should you use to detect business signals?
In my practice, web technology detection (Scrapy) and WTTJ job postings via Algolia have the best signal-to-noise ratio. LinkedIn generates a lot of noise but remains useful for expansion announcements combined with other sources. Trustpilot is interesting for competitive signals.
The key: a signal confirmed by multiple sources is far more reliable than an isolated signal.
Do you really need AI to detect business signals?
No, not necessarily. Simple rules are sufficient for many signals: more than 15 recent WTTJ job postings across two different countries is a readable expansion signal without an LLM.
AI becomes useful when context becomes ambiguous or narrative: summarizing LinkedIn posts, qualifying whether a stack change is relevant to your offer, or identifying an angle within a set of heterogeneous signals.
How do you avoid false positives in a RevOps system?
Three mechanisms helped me the most:
- Hash-based deduplication -- prevents a reposted job from generating a new signal.
- Source cross-referencing -- require multiple concordant sources before scoring high.
- Strict JSON in prompts -- force structured output rather than free interpretation.
And above all: keep humans in the loop. The system prepares, the human validates.
Which AI model should you use to qualify business signals?
For classification, structured extraction and summarization tasks, gpt-4o-mini via OpenRouter offers a good cost-to-quality ratio. Output quality depends mostly on the prompt and the quality of the context sent, not just the model. Well-structured context with a scoped prompt produces more stable results than noisy context sent to a more powerful model.
How do you make the system usable by a sales team?
Adoption depends mostly on the clarity of the output. If the salesperson understands why a company appears in their list, with the source, context and an action suggestion, the system becomes useful. If the output is a list of unexplained scores, it will be ignored.
In my SDR project, Microsoft Lists with simple statuses and Teams notifications worked better than an over-engineered interface.
Conclusion: What This Project Really Shows¶
Looking back, I did not just build scraping or prospecting workflows.
I explored a fairly simple question:
How do you help a sales team better understand the right moment to act?
The answer is not purely technical. It involves data, scraping, AI, orchestration. But it mostly comes down to a discipline: not confusing information, signal and decision.
Information is something you collect. A signal is information that suggests a movement. A decision remains a human act, contextualized, sometimes cautious.
That is why I find the topic interesting. It forces you to build concrete, imperfect, but useful systems. Systems that do not replace sales work, but can make it less repetitive, more contextualized and more intelligent.
There is still a lot to improve: adaptive scoring, feedback loop, better historization, dedicated interface, business tests, source monitoring, real measurement of commercial impact.
But the direction seems right to me: start from public traces, structure them, cross-reference them, qualify them, make them actionable, then let humans decide. That is probably where AI is most interesting in a RevOps context, not as a magic layer on top of sales, but as a discreet engine for preparation, synthesis and context.
Read Next¶
-
Scraping & tech detection
Build a web technology detection engine with Scrapy.
-
Semi-automated prospecting
Build an SDR lead machine with enrichment, scoring and operational tracking.
-
Competitive monitoring
Combine Trustpilot, LinkedIn and the web to detect competitive movements.
-
Expansion signals
Use WTTJ, LinkedIn, Algolia and n8n to detect growth indicators.