By Sagar Shankaran, Founder of CallSphere
Build a complete lead qualification chat agent with structured question flows, conditional branching logic, real-time lead scoring models, and CRM integration to convert website visitors into qualified sales opportunities.
Key takeaways
Most B2B websites convert less than 3% of visitors into leads. A well-built lead qualification chat agent changes that equation by engaging visitors in real time, asking the right questions, scoring their fit, and routing qualified leads to sales — all without human intervention. The best qualification agents feel like a helpful conversation, not a form to fill out.
The architecture has three layers: a question flow engine that guides the conversation, a scoring model that evaluates fit in real time, and a CRM integration that delivers qualified leads to the right salesperson.
Start by defining what a qualified lead looks like:
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
sequenceDiagram
autonumber
participant Caller as Caller
participant Agent as CallSphere Agent
participant API as CRM API
participant DB as CRM Database
participant Webhook as Webhook Listener
Caller->>Agent: Inbound call begins
Agent->>Agent: STT plus intent detection
Agent->>API: Lookup contact by phone
API->>DB: Read contact record
DB-->>API: Contact and history
API-->>Agent: Personalized context
Agent->>API: Create call activity
Agent->>API: Update deal stage
API->>Webhook: Outbound webhook fires
Webhook-->>Agent: Confirmed
Agent->>Caller: Spoken confirmation
from pydantic import BaseModel, Field
from enum import Enum
from datetime import datetime
class LeadScore(str, Enum):
HOT = "hot" # 80-100 points, route to sales immediately
WARM = "warm" # 50-79 points, nurture sequence
COLD = "cold" # 0-49 points, marketing list
class LeadData(BaseModel):
session_id: str
name: str | None = None
email: str | None = None
company: str | None = None
company_size: str | None = None
role: str | None = None
budget: str | None = None
timeline: str | None = None
use_case: str | None = None
pain_points: list[str] = Field(default_factory=list)
score: int = 0
grade: LeadScore = LeadScore.COLD
source_page: str | None = None
created_at: datetime = Field(default_factory=datetime.utcnow)
def calculate_score(self) -> int:
score = 0
# Company size scoring
size_scores = {
"1-10": 5, "11-50": 10, "51-200": 20,
"201-1000": 30, "1000+": 40
}
score += size_scores.get(self.company_size or "", 0)
# Role scoring - decision makers score higher
role_keywords = {
"cto": 25, "vp": 25, "director": 20, "head": 20,
"manager": 15, "engineer": 5, "developer": 5,
}
if self.role:
role_lower = self.role.lower()
for keyword, points in role_keywords.items():
if keyword in role_lower:
score += points
break
# Budget scoring
budget_scores = {
"under_1k": 5, "1k_5k": 15, "5k_20k": 25,
"20k_50k": 35, "50k_plus": 40
}
score += budget_scores.get(self.budget or "", 0)
# Timeline scoring - urgency increases score
timeline_scores = {
"immediate": 20, "this_month": 15,
"this_quarter": 10, "exploring": 5
}
score += timeline_scores.get(self.timeline or "", 0)
self.score = min(score, 100)
if self.score >= 80:
self.grade = LeadScore.HOT
elif self.score >= 50:
self.grade = LeadScore.WARM
else:
self.grade = LeadScore.COLD
return self.score
Use a state machine to guide the conversation while keeping it natural. The agent asks questions in a logical sequence but adapts based on previous answers:
from dataclasses import dataclass, field
@dataclass
class QualificationStep:
field_name: str
question: str
follow_ups: dict[str, str] = field(default_factory=dict)
skip_if: str | None = None
QUALIFICATION_FLOW = [
QualificationStep(
field_name="use_case",
question="What brings you to our site today? I'd love to understand what you're looking for.",
),
QualificationStep(
field_name="company",
question="Which company are you with? That will help me tailor my recommendations.",
),
QualificationStep(
field_name="company_size",
question="How large is your team?",
),
QualificationStep(
field_name="role",
question="And what's your role there?",
),
QualificationStep(
field_name="timeline",
question="What's your timeline for getting started?",
follow_ups={
"immediate": "Great, sounds like this is a priority. Let me make sure I connect you with the right person.",
"exploring": "No rush at all. Let me share some resources that might help your evaluation.",
},
),
QualificationStep(
field_name="email",
question="What's the best email to reach you at? I'll send over the details we discussed.",
),
]
class QualificationEngine:
def __init__(self):
self.lead = LeadData(session_id="")
self.current_step = 0
self.completed_fields: set[str] = set()
def get_next_question(self) -> str | None:
while self.current_step < len(QUALIFICATION_FLOW):
step = QUALIFICATION_FLOW[self.current_step]
if step.field_name not in self.completed_fields:
return step.question
self.current_step += 1
return None
def process_answer(self, field_name: str, value: str) -> str | None:
setattr(self.lead, field_name, value)
self.completed_fields.add(field_name)
step = QUALIFICATION_FLOW[self.current_step]
follow_up = step.follow_ups.get(value)
self.current_step += 1
next_q = self.get_next_question()
if follow_up and next_q:
return f"{follow_up} {next_q}"
return next_q
Instead of rigid multiple-choice answers, let the LLM extract structured data from natural conversation:
from agents import Agent, function_tool, Runner
@function_tool
def update_lead_field(field_name: str, value: str) -> str:
"""Update a lead qualification field with extracted information.
field_name: one of name, email, company, company_size, role, budget, timeline, use_case
value: the extracted value from the conversation
"""
return f"Updated {field_name} to {value}"
@function_tool
def get_qualification_status() -> str:
"""Check which qualification fields have been collected and what to ask next."""
return "Returns current lead state and next question"
qualification_agent = Agent(
name="Lead Qualifier",
instructions="""You are a friendly lead qualification agent. Your goal is to
understand the visitor's needs and collect qualification information naturally.
Rules:
- Never ask more than one question at a time
- Extract information from natural responses (if they say "I'm a CTO at Acme",
extract both role and company)
- Be conversational, not robotic
- After collecting use_case, company, role, and timeline, ask for email
- Use update_lead_field to store extracted information
- If the lead score is high, express enthusiasm and offer to book a demo""",
tools=[update_lead_field, get_qualification_status],
)
When the lead is qualified, push it to your CRM. Here is a HubSpot integration example:
interface HubSpotContact {
email: string;
firstname?: string;
lastname?: string;
company?: string;
jobtitle?: string;
hs_lead_status: string;
lead_score: number;
}
async function pushToHubSpot(lead: LeadData): Promise<string> {
const nameParts = (lead.name || "").split(" ");
const contact: HubSpotContact = {
email: lead.email!,
firstname: nameParts[0],
lastname: nameParts.slice(1).join(" "),
company: lead.company || undefined,
jobtitle: lead.role || undefined,
hs_lead_status: lead.grade === "hot" ? "NEW" : "OPEN",
lead_score: lead.score,
};
const response = await fetch(
"https://api.hubapi.com/crm/v3/objects/contacts",
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.HUBSPOT_TOKEN}`,
},
body: JSON.stringify({ properties: contact }),
},
);
if (!response.ok) {
const error = await response.json();
if (error.category === "CONFLICT") {
return await updateExistingContact(lead.email!, contact);
}
throw new Error(`HubSpot API error: ${error.message}`);
}
const result = await response.json();
return result.id;
}
Give the user a sense of progress by subtly showing qualification advancement. On the backend, recalculate the score after each field update and send it to the frontend:
Still reading? Stop comparing — try CallSphere live.
CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.
async def on_field_update(ws, engine: QualificationEngine):
score = engine.lead.calculate_score()
await ws.send_json({
"type": "lead_progress",
"fields_collected": len(engine.completed_fields),
"total_fields": len(QUALIFICATION_FLOW),
"score": score,
"grade": engine.lead.grade.value,
})
Interleave value-giving with questions. After the user shares their use case, provide a relevant insight or case study before asking the next question. Limit consecutive questions to two before offering something helpful. The conversation should feel like a consultative discussion, not a form with a chatbot face.
Yes. Always extract information opportunistically. If the user says "I'm John at john@acme.com, we need help with X," extract the name, email, and use case in one pass, skip those steps in the flow, and move directly to the questions you still need answered. Rigid flows that repeat already-answered questions frustrate users and reduce conversion rates.
Respect the boundary. Offer alternatives: "No problem at all. Would you prefer I share some resources you can review on your own?" Provide value regardless — a useful recommendation or link. Track these sessions separately as anonymous qualified leads. Often, users return later and are more willing to share contact information after seeing the agent is genuinely helpful.
#LeadQualification #Sales #CRM #Scoring #ChatAgent #AgenticAI #LearnAI #AIEngineering

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
Chengdu, Beijing, and Shenzhen SaaS teams lose trials to slow follow-up. CallSphere qualifies inbound leads 24/7 in Mandarin and English and books demos straight into the calendar.
A data-led look at how small businesses from Lusaka to Kitwe are losing revenue to missed calls, and how CallSphere AI voice and chat agents answer every enquiry 24/7 in English, Bemba, and Nyanja.
11x.ai and Artisan promised to replace BDRs entirely. By 2026 most adopters reverted to hybrid models. Here is the outbound chat pattern that actually works.
Outbound AI voice agents writing to CRMs at 50k+ calls per day hit Salesforce API limits fast. The integration playbook that actually scales.
Independent RIAs and advisors lose qualified prospects to slow callbacks. Here is how a 2026 voice agent runs a structured pre-screen, soft-AUM ask, and books a discovery call cleanly.
Wire CallSphere's voice and chat sales agents into HubSpot, Salesforce, or Pipedrive for your Michigan sales team — go-live in under 3 days, full CRM sync.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco
Watch how CallSphere handles real customer calls, schedules appointments, and processes payments — live.