Lead Qualification Agent

Build a voice agent that qualifies leads, collects information, and routes qualified prospects to sales.

Lead Qualification Agent

An intelligent voice agent that qualifies inbound leads by asking targeted questions, scoring prospects, and routing qualified leads to your sales team.

Configuration

{
  "agent": {
    "name": "Lead Qualifier",
    "language": "en-US",

    "llmProvider": "gemini-2.5",
    "llmModel": "gemini-2.5-flash-lite",
    "llmTemperature": 0.6,

    "sttProvider": "deepgram",
    "sttModel": "nova-3",

    "ttsProvider": "cartesia",
    "ttsVoice": "95856005-0332-41b0-935f-352e296aa0df",

    "greetingMessage": "Hi! Thanks for your interest in our product. I'd love to learn more about your needs to see how we can help. Do you have a few minutes?",

    "prompt": "...",

    "allowInterruptions": true
  }
}

System Prompt

You are a lead qualification specialist. Your goal is to understand the prospect's needs, qualify them, and schedule a meeting with sales if they're a good fit.

## Qualification Criteria (BANT)

### Budget
- What budget range are they working with?
- Are they decision-makers on budget?

### Authority
- What is their role?
- Who else is involved in decisions?

### Need
- What problem are they trying to solve?
- How urgent is it?

### Timeline
- When are they looking to implement?
- Is there a specific deadline?

## Qualification Questions

Ask these naturally throughout the conversation:

1. "What brings you to [Company] today?"
2. "Tell me about your current situation with [problem area]"
3. "What would solving this problem mean for your business?"
4. "Who else would be involved in evaluating solutions?"
5. "What's your timeline for making a decision?"
6. "Do you have a budget range in mind?"

## Scoring Guidelines

**Hot Lead (Score 80-100)**
- Clear budget authority
- Decision-maker or strong influencer
- Urgent need (within 30 days)
- Specific pain points that match our solution

**Warm Lead (Score 50-79)**
- Budget being discussed
- Part of buying committee
- Timeline within 90 days
- General interest, exploring options

**Cold Lead (Score 0-49)**
- No budget authority
- Just researching
- No timeline
- Needs we can't address

## Response Style
- Be conversational, not interrogative
- Listen actively and respond to what they say
- Don't rapid-fire questions
- Share relevant info about our solution when appropriate
- Be enthusiastic but not pushy

## Handling Objections

"I'm just looking":
→ "Totally understand! What sparked your interest today?"

"We don't have budget":
→ "Makes sense. When do budget discussions typically happen?"

"I need to talk to my team":
→ "Of course! What would be most helpful for me to share with them?"

## Next Steps

For qualified leads:
→ "Based on what you've shared, I think there's a great fit. Would you be open to a 15-minute call with our solutions team to dive deeper?"

For unqualified leads:
→ "Thanks for sharing! It sounds like timing isn't quite right. Can I send you some resources to review?"

Tools

Save Lead Information

{
  "type": "function",
  "function": {
    "name": "save_lead",
    "description": "Save lead information to CRM",
    "parameters": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "description": "Lead's full name"
        },
        "company": {
          "type": "string",
          "description": "Company name"
        },
        "role": {
          "type": "string",
          "description": "Job title/role"
        },
        "email": {
          "type": "string",
          "description": "Email address"
        },
        "phone": {
          "type": "string",
          "description": "Phone number"
        },
        "needs": {
          "type": "string",
          "description": "Summary of their needs"
        },
        "budget": {
          "type": "string",
          "description": "Budget range if discussed"
        },
        "timeline": {
          "type": "string",
          "description": "Timeline for decision"
        },
        "score": {
          "type": "integer",
          "description": "Lead score 0-100"
        },
        "notes": {
          "type": "string",
          "description": "Additional notes"
        }
      },
      "required": ["name", "needs", "score"]
    }
  }
}

Schedule Meeting

{
  "type": "function",
  "function": {
    "name": "schedule_meeting",
    "description": "Schedule a meeting with sales team",
    "parameters": {
      "type": "object",
      "properties": {
        "lead_id": {
          "type": "string"
        },
        "preferred_times": {
          "type": "array",
          "items": { "type": "string" },
          "description": "Preferred meeting times"
        },
        "meeting_type": {
          "type": "string",
          "enum": ["discovery", "demo", "technical_review"]
        }
      },
      "required": ["lead_id", "meeting_type"]
    }
  }
}

Check Availability

{
  "type": "function",
  "function": {
    "name": "check_availability",
    "description": "Check sales team availability",
    "parameters": {
      "type": "object",
      "properties": {
        "date_range_start": {
          "type": "string",
          "description": "Start date (YYYY-MM-DD)"
        },
        "date_range_end": {
          "type": "string",
          "description": "End date (YYYY-MM-DD)"
        }
      }
    }
  }
}

Tool Handlers

type Lead struct {
    ID        string
    Name      string
    Company   string
    Role      string
    Email     string
    Phone     string
    Needs     string
    Budget    string
    Timeline  string
    Score     int
    Notes     string
    CreatedAt time.Time
}

func handleSaveLead(params map[string]any) (string, error) {
    lead := Lead{
        ID:        generateID(),
        Name:      params["name"].(string),
        Needs:     params["needs"].(string),
        Score:     int(params["score"].(float64)),
        CreatedAt: time.Now(),
    }

    // Optional fields
    if company, ok := params["company"].(string); ok {
        lead.Company = company
    }
    if role, ok := params["role"].(string); ok {
        lead.Role = role
    }
    if email, ok := params["email"].(string); ok {
        lead.Email = email
    }
    if budget, ok := params["budget"].(string); ok {
        lead.Budget = budget
    }
    if timeline, ok := params["timeline"].(string); ok {
        lead.Timeline = timeline
    }
    if notes, ok := params["notes"].(string); ok {
        lead.Notes = notes
    }

    // Save to CRM
    err := crm.SaveLead(lead)
    if err != nil {
        return "", err
    }

    // If hot lead, notify sales
    if lead.Score >= 80 {
        notifySalesTeam(lead)
    }

    return fmt.Sprintf("Lead saved with ID %s. Score: %d", lead.ID, lead.Score), nil
}

func handleScheduleMeeting(params map[string]any) (string, error) {
    leadID := params["lead_id"].(string)
    meetingType := params["meeting_type"].(string)

    // Get available slots
    slots, _ := calendar.GetAvailableSlots(3) // Next 3 available

    if len(slots) == 0 {
        return "I don't have any available slots right now. Can I have someone reach out to you?", nil
    }

    // Format slots for speech
    response := "Great! I have a few times available: "
    for i, slot := range slots {
        response += formatTimeForSpeech(slot)
        if i < len(slots)-1 {
            response += ", or "
        }
    }
    response += ". Which works best for you?"

    return response, nil
}

Lead Scoring Logic

type LeadScore struct {
    Budget    int // 0-25
    Authority int // 0-25
    Need      int // 0-25
    Timeline  int // 0-25
}

func calculateScore(lead *Lead) int {
    var score LeadScore

    // Budget scoring
    switch lead.Budget {
    case "over_50k":
        score.Budget = 25
    case "25k_50k":
        score.Budget = 20
    case "10k_25k":
        score.Budget = 15
    case "under_10k":
        score.Budget = 10
    default:
        score.Budget = 5
    }

    // Authority scoring
    switch lead.Role {
    case "ceo", "cto", "vp":
        score.Authority = 25
    case "director":
        score.Authority = 20
    case "manager":
        score.Authority = 15
    case "individual":
        score.Authority = 10
    default:
        score.Authority = 5
    }

    // Need scoring
    if lead.Needs != "" && strings.Contains(lead.Needs, "urgent") {
        score.Need = 25
    } else if lead.Needs != "" {
        score.Need = 15
    } else {
        score.Need = 5
    }

    // Timeline scoring
    switch lead.Timeline {
    case "immediate", "this_month":
        score.Timeline = 25
    case "this_quarter":
        score.Timeline = 20
    case "this_year":
        score.Timeline = 10
    default:
        score.Timeline = 5
    }

    return score.Budget + score.Authority + score.Need + score.Timeline
}

Conversation Flow

1. Greeting & Permission
   ↓
2. Understand Situation (Need)
   ↓
3. Explore Impact (qualify need)
   ↓
4. Discuss Decision Process (Authority)
   ↓
5. Timeline Discussion
   ↓
6. Budget (if appropriate)
   ↓
7. Score & Route
   ├─ Hot (80+) → Schedule meeting
   ├─ Warm (50-79) → Send resources + follow-up
   └─ Cold (<50) → Thank + nurture

Outbound Lead Qualification

For outbound campaigns, use variables to personalize:

curl -X POST https://voice-agent.edesy.in/api/v1/calls \
  -d '{
    "agent_id": "lead_qualifier",
    "to": "+1234567890",
    "variables": {
      "leadName": "Sarah",
      "company": "Acme Corp",
      "triggerEvent": "downloaded whitepaper",
      "salesRep": "John from Sales"
    }
  }'

Outbound Greeting:

Hi {{leadName}}! This is Alex from [Company]. I noticed you {{triggerEvent}} recently and wanted to reach out. Do you have a quick minute?

Analytics

type QualificationMetrics struct {
    TotalCalls           int
    QualifiedLeads       int
    MeetingsScheduled    int
    AverageCallDuration  time.Duration
    AverageLeadScore     float64
    QualificationRate    float64
    ConversionRate       float64
}

func trackQualification(call *Call, lead *Lead) {
    metrics.Increment("leads.calls.total")
    metrics.Histogram("leads.score", float64(lead.Score))
    metrics.Histogram("leads.call_duration", call.Duration.Seconds())

    if lead.Score >= 50 {
        metrics.Increment("leads.qualified")
    }

    if call.MeetingScheduled {
        metrics.Increment("leads.meetings.scheduled")
    }
}

Best Practices

1. Natural Conversation Flow

// Don't rapid-fire questions
badFlow := []string{
    "What's your budget?",
    "What's your timeline?",
    "Who makes decisions?",
}

// Build on their responses
goodFlow := `
Lead: "We're struggling with X"
You: "I hear that a lot. Tell me more about how X is affecting your business..."
Lead: [shares more]
You: "That sounds challenging. What would solving this mean for your team?"
`

2. Handle "Not Interested"

notInterestedResponses := map[string]string{
    "timing": "Totally understand. Would it be helpful if I sent some resources for when timing is better?",
    "competitor": "Got it! Just curious - what drew you to [competitor]?",
    "budget": "Makes sense. Budgets are tight everywhere. When do planning cycles typically happen?",
}

3. Capture Information Naturally

// Extract info from conversation, don't ask directly
func extractLeadInfo(transcript string) *Lead {
    // Use LLM to extract structured data from natural conversation
    prompt := `Extract lead information from this conversation:
    - Name
    - Company
    - Role
    - Budget signals
    - Timeline
    - Pain points
    - Decision process`

    return llm.Extract(transcript, prompt)
}

Next Steps