Pular para conteúdo

Tutorial: Sistema Automático de Lead Scoring e Roteamento

Aprenda a criar um sistema inteligente que pontua leads automaticamente, qualifica prospects e roteia para o vendedor certo no momento ideal, integrando HubSpot, Salesforce ou Pipedrive.

O Que Você Vai Construir

Um sistema completo de lead scoring que: 1. Captura leads de múltiplas fontes (formulários, chat, LinkedIn) 2. Enriquece dados com APIs externas (Clearbit, Hunter.io) 3. Calcula score baseado em comportamento e firmografia 4. Classifica leads (Quente/Morno/Frio) 5. Roteia automaticamente para o vendedor ideal 6. Envia notificações em tempo real 7. Atualiza CRM automaticamente 8. Cria tarefas de follow-up

Tempo estimado: 60 minutos Nível: Avançado Impacto esperado: +40% na taxa de conversão, -60% no tempo de resposta

O Que Você Vai Aprender

  • Integração com múltiplos CRMs (HubSpot, Salesforce, Pipedrive)
  • Enriquecimento de dados com APIs externas
  • Algoritmos de lead scoring
  • Roteamento inteligente (round-robin, territory, expertise)
  • Automação de tarefas comerciais
  • Notificações em tempo real (Slack, email, SMS)
  • Análise preditiva de conversão

Pré-requisitos

  • ✅ Conta em CRM (HubSpot, Salesforce ou Pipedrive)
  • ✅ API key do Clearbit (opcional, para enriquecimento)
  • ✅ Slack workspace configurado
  • ✅ Conhecimento de scoring models
  • ✅ Acesso ao PostgreSQL

Parte 1: Setup Inicial

1.1. Criar Estrutura no Banco de Dados

-- Tabela de leads
CREATE TABLE leads (
  id SERIAL PRIMARY KEY,
  email VARCHAR(255) UNIQUE NOT NULL,
  first_name VARCHAR(100),
  last_name VARCHAR(100),
  phone VARCHAR(50),
  company VARCHAR(255),
  job_title VARCHAR(255),

  -- Dados enriquecidos
  company_size VARCHAR(50),
  industry VARCHAR(100),
  annual_revenue BIGINT,
  technology_stack TEXT[],
  employee_count INT,
  linkedin_url VARCHAR(500),
  company_domain VARCHAR(255),

  -- Scoring
  demographic_score INT DEFAULT 0,
  behavioral_score INT DEFAULT 0,
  engagement_score INT DEFAULT 0,
  total_score INT DEFAULT 0,
  grade VARCHAR(1), -- A, B, C, D
  temperature VARCHAR(20), -- hot, warm, cold

  -- Tracking
  source VARCHAR(100),
  utm_source VARCHAR(100),
  utm_medium VARCHAR(100),
  utm_campaign VARCHAR(100),
  first_touch_page VARCHAR(500),
  last_activity_at TIMESTAMP,

  -- Roteamento
  assigned_to VARCHAR(255),
  assigned_at TIMESTAMP,
  crm_id VARCHAR(255),
  crm_type VARCHAR(50),
  status VARCHAR(50) DEFAULT 'new',

  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

-- Tabela de atividades
CREATE TABLE lead_activities (
  id SERIAL PRIMARY KEY,
  lead_id INT REFERENCES leads(id),
  activity_type VARCHAR(100),
  activity_data JSONB,
  points_awarded INT,
  created_at TIMESTAMP DEFAULT NOW()
);

-- Tabela de vendedores
CREATE TABLE sales_reps (
  id SERIAL PRIMARY KEY,
  name VARCHAR(255) NOT NULL,
  email VARCHAR(255) NOT NULL,
  phone VARCHAR(50),
  territory VARCHAR(100),
  expertise TEXT[],
  max_leads_per_day INT DEFAULT 10,
  leads_today INT DEFAULT 0,
  slack_user_id VARCHAR(100),
  is_active BOOLEAN DEFAULT true,
  created_at TIMESTAMP DEFAULT NOW()
);

-- Índices
CREATE INDEX idx_leads_email ON leads(email);
CREATE INDEX idx_leads_score ON leads(total_score DESC);
CREATE INDEX idx_leads_status ON leads(status);
CREATE INDEX idx_leads_assigned ON leads(assigned_to);
CREATE INDEX idx_activities_lead ON lead_activities(lead_id);

1.2. Inserir Vendedores

INSERT INTO sales_reps (name, email, phone, territory, expertise, max_leads_per_day, slack_user_id) VALUES
('João Silva', 'joao@empresa.com', '+5511999999999', 'Sul', ARRAY['tecnologia', 'saas'], 15, 'U024BE7LH'),
('Maria Santos', 'maria@empresa.com', '+5511888888888', 'Sudeste', ARRAY['financeiro', 'ecommerce'], 12, 'U024BE7LI'),
('Pedro Costa', 'pedro@empresa.com', '+5511777777777', 'Nacional', ARRAY['industria', 'logistica'], 10, 'U024BE7LJ');

1.3. Criar o Flow Principal

  1. No Lumina, crie um novo flow:
  2. Nome: "Lead Scoring e Roteamento Automático"
  3. Descrição: "Sistema inteligente de qualificação e distribuição de leads"
  4. Tags: "crm", "sales", "automation", "scoring"

Parte 2: Captura e Normalização de Leads

2.1. Configurar Webhook Trigger

Adicione um nó Webhook Trigger que receberá leads de múltiplas fontes.

2.2. Normalizar Dados de Entrada

Diferentes fontes enviam dados em formatos diferentes. Vamos normalizar:

// Transform Node: "Normalizar Dados do Lead"
{{
  {
    // Dados pessoais
    "email": ($trigger.body.email || $trigger.body.Email || $trigger.body.contact_email || '').toLowerCase().trim(),
    "firstName": $trigger.body.first_name || $trigger.body.firstName || $trigger.body.nome || $trigger.body.name?.split(' ')[0] || '',
    "lastName": $trigger.body.last_name || $trigger.body.lastName || $trigger.body.sobrenome || $trigger.body.name?.split(' ').slice(1).join(' ') || '',
    "phone": $trigger.body.phone || $trigger.body.telefone || $trigger.body.mobile || '',

    // Dados profissionais
    "company": $trigger.body.company || $trigger.body.empresa || $trigger.body.organization || '',
    "jobTitle": $trigger.body.job_title || $trigger.body.jobTitle || $trigger.body.cargo || $trigger.body.position || '',
    "companySize": $trigger.body.company_size || $trigger.body.tamanho_empresa || $trigger.body.employees || '',
    "industry": $trigger.body.industry || $trigger.body.setor || $trigger.body.segment || '',

    // Tracking
    "source": $trigger.body.source || $trigger.body.origem || 'direct',
    "utmSource": $trigger.body.utm_source || '',
    "utmMedium": $trigger.body.utm_medium || '',
    "utmCampaign": $trigger.body.utm_campaign || '',
    "landingPage": $trigger.body.landing_page || $trigger.body.url || '',
    "referrer": $trigger.body.referrer || '',

    // Metadata
    "ipAddress": $trigger.body.ip || $trigger.headers['x-forwarded-for'] || '',
    "userAgent": $trigger.body.user_agent || $trigger.headers['user-agent'] || '',
    "timestamp": new Date().toISOString(),

    // Dados adicionais (custom fields)
    "customFields": {
      "message": $trigger.body.message || $trigger.body.mensagem || '',
      "budget": $trigger.body.budget || $trigger.body.orcamento || '',
      "timeline": $trigger.body.timeline || $trigger.body.prazo || '',
      "interest": $trigger.body.interest || $trigger.body.interesse || ''
    }
  }
}}

2.3. Validar Email

// If/Else Node: "Email Válido?"
{{
  $nodes['normalizar'].output.email &&
  $nodes['normalizar'].output.email.length > 0 &&
  /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test($nodes['normalizar'].output.email) &&
  // Filtrar emails descartáveis
  !$nodes['normalizar'].output.email.includes('tempmail') &&
  !$nodes['normalizar'].output.email.includes('guerrillamail') &&
  !$nodes['normalizar'].output.email.includes('10minutemail') &&
  !$nodes['normalizar'].output.email.includes('mailinator')
}}

2.4. Verificar Duplicatas

// Database Query: "Verificar Lead Existente"
SELECT id, email, total_score, status, assigned_to, created_at
FROM leads
WHERE email = '{{ $nodes['normalizar'].output.email }}'
LIMIT 1;
// If/Else: "Lead Já Existe?"
{{
  $nodes['verificar-existente'].output.rows &&
  $nodes['verificar-existente'].output.rows.length > 0
}}

Branch TRUE (Lead existe): Atualizar atividade e score Branch FALSE (Lead novo): Criar novo lead

Parte 3: Enriquecimento de Dados

3.1. Extrair Domínio do Email

// Transform: "Extrair Domínio"
{{
  {
    "domain": $nodes['normalizar'].output.email.split('@')[1]
  }
}}

3.2. Enriquecer com Clearbit

// HTTP Request: "Enriquecer com Clearbit"
// Método: GET
// URL: https://company.clearbit.com/v2/companies/find

{
  "headers": {
    "Authorization": "Bearer {{ $credentials.clearbit.apiKey }}"
  },
  "params": {
    "domain": "{{ $nodes['extrair-dominio'].output.domain }}"
  },
  "errorHandling": "continueOnFail"
}
// Transform: "Processar Dados Clearbit"
{{
  {
    "companyName": $nodes['enriquecer-clearbit'].output?.data?.name || $nodes['normalizar'].output.company,
    "domain": $nodes['enriquecer-clearbit'].output?.data?.domain || $nodes['extrair-dominio'].output.domain,
    "industry": $nodes['enriquecer-clearbit'].output?.data?.category?.industry || $nodes['normalizar'].output.industry,
    "employeeCount": $nodes['enriquecer-clearbit'].output?.data?.metrics?.employees || null,
    "employeeRange": $nodes['enriquecer-clearbit'].output?.data?.metrics?.employeesRange || $nodes['normalizar'].output.companySize,
    "annualRevenue": $nodes['enriquecer-clearbit'].output?.data?.metrics?.annualRevenue || null,
    "techStack": $nodes['enriquecer-clearbit'].output?.data?.tech || [],
    "location": $nodes['enriquecer-clearbit'].output?.data?.location || '',
    "linkedinUrl": $nodes['enriquecer-clearbit'].output?.data?.linkedin?.handle ? `https://linkedin.com/company/${$nodes['enriquecer-clearbit'].output.data.linkedin.handle}` : '',
    "description": $nodes['enriquecer-clearbit'].output?.data?.description || '',
    "logo": $nodes['enriquecer-clearbit'].output?.data?.logo || '',
    "enriched": !!$nodes['enriquecer-clearbit'].output?.data
  }
}}

3.3. Buscar Informações no LinkedIn (Opcional)

Se você tiver integração com LinkedIn Sales Navigator ou PhantomBuster:

// HTTP Request: "Buscar no LinkedIn"
{
  "method": "POST",
  "url": "https://api.phantombuster.com/api/v2/agents/fetch",
  "headers": {
    "X-Phantombuster-Key": "{{ $credentials.phantombuster.apiKey }}"
  },
  "body": {
    "agentId": "{{ $vars.linkedinProfileAgentId }}",
    "argument": {
      "sessionCookie": "{{ $credentials.linkedin.sessionCookie }}",
      "profileUrl": "{{ $nodes['processar-clearbit'].output.linkedinUrl }}"
    }
  }
}

Parte 4: Cálculo de Score

4.1. Score Demográfico (Firmografia)

// Function Node: "Calcular Score Demográfico"
{{
  (() => {
    let score = 0;
    const data = $nodes['processar-clearbit'].output;

    // Tamanho da empresa (0-30 pontos)
    const employees = data.employeeCount;
    if (employees >= 1000) score += 30;
    else if (employees >= 500) score += 25;
    else if (employees >= 100) score += 20;
    else if (employees >= 50) score += 15;
    else if (employees >= 10) score += 10;
    else score += 5;

    // Receita anual (0-25 pontos)
    const revenue = data.annualRevenue;
    if (revenue >= 100000000) score += 25; // +100M
    else if (revenue >= 50000000) score += 20; // 50-100M
    else if (revenue >= 10000000) score += 15; // 10-50M
    else if (revenue >= 1000000) score += 10; // 1-10M

    // Indústria (0-20 pontos)
    const targetIndustries = ['technology', 'software', 'saas', 'ecommerce', 'fintech'];
    if (data.industry && targetIndustries.some(ind => data.industry.toLowerCase().includes(ind))) {
      score += 20;
    } else if (data.industry) {
      score += 10;
    }

    // Tecnologias usadas (0-15 pontos)
    const techStack = data.techStack || [];
    const targetTechs = ['react', 'nodejs', 'aws', 'google-analytics', 'salesforce', 'hubspot'];
    const matchingTechs = techStack.filter(tech =>
      targetTechs.some(target => tech.toLowerCase().includes(target))
    );
    score += Math.min(matchingTechs.length * 3, 15);

    // Localização (0-10 pontos)
    const location = (data.location || '').toLowerCase();
    if (location.includes('brazil') || location.includes('brasil')) {
      score += 10;
    } else if (location.includes('latin america') || location.includes('south america')) {
      score += 5;
    }

    return {
      "demographicScore": score,
      "maxDemographicScore": 100,
      "breakdown": {
        "companySize": employees >= 1000 ? 30 : (employees >= 500 ? 25 : (employees >= 100 ? 20 : (employees >= 50 ? 15 : (employees >= 10 ? 10 : 5)))),
        "revenue": revenue >= 100000000 ? 25 : (revenue >= 50000000 ? 20 : (revenue >= 10000000 ? 15 : (revenue >= 1000000 ? 10 : 0))),
        "industry": data.industry && targetIndustries.some(ind => data.industry.toLowerCase().includes(ind)) ? 20 : (data.industry ? 10 : 0),
        "technology": Math.min(matchingTechs.length * 3, 15),
        "location": location.includes('brazil') || location.includes('brasil') ? 10 : (location.includes('latin') ? 5 : 0)
      }
    };
  })()
}}

4.2. Score Comportamental

// Function Node: "Calcular Score Comportamental"
{{
  (() => {
    let score = 0;
    const normalized = $nodes['normalizar'].output;
    const customFields = normalized.customFields || {};

    // Preenchimento de campos (0-20 pontos)
    const fields = [
      normalized.firstName,
      normalized.lastName,
      normalized.phone,
      normalized.company,
      normalized.jobTitle
    ];
    const filledFields = fields.filter(f => f && f.length > 0).length;
    score += filledFields * 4; // 4 pontos por campo preenchido

    // Cargo (0-25 pontos)
    const jobTitle = (normalized.jobTitle || '').toLowerCase();
    if (jobTitle.includes('ceo') || jobTitle.includes('founder') || jobTitle.includes('owner')) {
      score += 25;
    } else if (jobTitle.includes('cto') || jobTitle.includes('cio') || jobTitle.includes('vp') || jobTitle.includes('director')) {
      score += 20;
    } else if (jobTitle.includes('manager') || jobTitle.includes('head') || jobTitle.includes('lead')) {
      score += 15;
    } else if (jobTitle.includes('coordenador') || jobTitle.includes('coordinator')) {
      score += 10;
    } else {
      score += 5;
    }

    // Fonte do lead (0-20 pontos)
    const source = normalized.source?.toLowerCase();
    if (source === 'referral' || source === 'indicacao') score += 20;
    else if (source === 'organic' || source === 'search') score += 15;
    else if (source === 'linkedin' || source === 'social') score += 12;
    else if (source === 'email' || source === 'campaign') score += 10;
    else if (source === 'paid' || source === 'ads') score += 8;
    else score += 5;

    // Orçamento declarado (0-20 pontos)
    const budget = customFields.budget?.toLowerCase() || '';
    if (budget.includes('>') || budget.includes('100k') || budget.includes('alto')) {
      score += 20;
    } else if (budget.includes('50k') || budget.includes('medio')) {
      score += 15;
    } else if (budget) {
      score += 10;
    }

    // Timeline (0-15 pontos)
    const timeline = customFields.timeline?.toLowerCase() || '';
    if (timeline.includes('imediato') || timeline.includes('urgente') || timeline.includes('immediate')) {
      score += 15;
    } else if (timeline.includes('mes') || timeline.includes('month')) {
      score += 10;
    } else if (timeline) {
      score += 5;
    }

    return {
      "behavioralScore": score,
      "maxBehavioralScore": 100,
      "breakdown": {
        "dataCompleteness": filledFields * 4,
        "jobTitle": jobTitle.includes('ceo') ? 25 : (jobTitle.includes('cto') ? 20 : (jobTitle.includes('manager') ? 15 : 10)),
        "source": source === 'referral' ? 20 : 10,
        "budget": budget.includes('>') ? 20 : (budget.includes('50k') ? 15 : 10),
        "timeline": timeline.includes('imediato') ? 15 : 5
      }
    };
  })()
}}

4.3. Score de Engajamento

Para leads existentes, calcule baseado no histórico:

// Database Query: "Buscar Atividades do Lead"
SELECT
  activity_type,
  COUNT(*) as count,
  MAX(created_at) as last_activity
FROM lead_activities
WHERE lead_id = {{ $nodes['verificar-existente'].output.rows[0]?.id || 0 }}
  AND created_at >= NOW() - INTERVAL '30 days'
GROUP BY activity_type;
// Function Node: "Calcular Score de Engajamento"
{{
  (() => {
    const activities = $nodes['buscar-atividades'].output.rows || [];
    let score = 0;

    activities.forEach(activity => {
      switch(activity.activity_type) {
        case 'email_opened': score += activity.count * 2; break;
        case 'email_clicked': score += activity.count * 5; break;
        case 'page_visited': score += activity.count * 3; break;
        case 'demo_requested': score += 30; break;
        case 'pricing_viewed': score += 15; break;
        case 'whitepaper_downloaded': score += 10; break;
        case 'webinar_attended': score += 20; break;
        default: score += activity.count * 1;
      }
    });

    // Cap em 100
    score = Math.min(score, 100);

    return {
      "engagementScore": score,
      "maxEngagementScore": 100,
      "activityCount": activities.reduce((sum, a) => sum + parseInt(a.count), 0),
      "lastActivity": activities.length > 0 ?
        Math.max(...activities.map(a => new Date(a.last_activity).getTime())) : null
    };
  })()
}}

4.4. Calcular Score Total e Grade

// Function Node: "Calcular Score Total"
{{
  (() => {
    const demographic = $nodes['calc-demografico'].output.demographicScore || 0;
    const behavioral = $nodes['calc-comportamental'].output.behavioralScore || 0;
    const engagement = $nodes['calc-engajamento']?.output.engagementScore || 0;

    // Pesos (ajuste conforme seu negócio)
    const weights = {
      demographic: 0.40,
      behavioral: 0.40,
      engagement: 0.20
    };

    const totalScore = Math.round(
      (demographic * weights.demographic) +
      (behavioral * weights.behavioral) +
      (engagement * weights.engagement)
    );

    // Determinar grade (A, B, C, D)
    let grade;
    if (totalScore >= 80) grade = 'A';
    else if (totalScore >= 60) grade = 'B';
    else if (totalScore >= 40) grade = 'C';
    else grade = 'D';

    // Determinar temperatura (hot, warm, cold)
    let temperature;
    if (totalScore >= 75 && engagement > 50) temperature = 'hot';
    else if (totalScore >= 50) temperature = 'warm';
    else temperature = 'cold';

    return {
      "totalScore": totalScore,
      "grade": grade,
      "temperature": temperature,
      "scores": {
        "demographic": demographic,
        "behavioral": behavioral,
        "engagement": engagement
      },
      "weights": weights
    };
  })()
}}

Parte 5: Roteamento Inteligente

5.1. Definir Regras de Roteamento

// Function Node: "Determinar Regras de Roteamento"
{{
  (() => {
    const score = $nodes['calc-total'].output.totalScore;
    const grade = $nodes['calc-total'].output.grade;
    const temperature = $nodes['calc-total'].output.temperature;
    const enriched = $nodes['processar-clearbit'].output;

    // Regra 1: Leads A e Hot vão para o melhor vendedor
    if (grade === 'A' && temperature === 'hot') {
      return {
        "routingMethod": "best-performer",
        "priority": "high",
        "responseTime": "5min"
      };
    }

    // Regra 2: Rotear por território
    const location = enriched.location?.toLowerCase() || '';
    if (location.includes('sul')) {
      return {
        "routingMethod": "territory",
        "territory": "Sul",
        "priority": "medium",
        "responseTime": "30min"
      };
    }

    // Regra 3: Rotear por expertise (indústria)
    const industry = enriched.industry?.toLowerCase() || '';
    if (industry.includes('tecnologia') || industry.includes('saas')) {
      return {
        "routingMethod": "expertise",
        "expertise": "tecnologia",
        "priority": "medium",
        "responseTime": "1hour"
      };
    }

    // Regra 4: Round-robin para o resto
    return {
      "routingMethod": "round-robin",
      "priority": "normal",
      "responseTime": "2hours"
    };
  })()
}}

5.2. Selecionar Vendedor

// Database Query: "Selecionar Vendedor"
// Esse query depende do método de roteamento

-- Para best-performer:
SELECT id, name, email, slack_user_id
FROM sales_reps
WHERE is_active = true
  AND leads_today < max_leads_per_day
ORDER BY (
  SELECT COUNT(*)
  FROM leads
  WHERE assigned_to = sales_reps.email
    AND status = 'converted'
    AND created_at >= NOW() - INTERVAL '30 days'
) DESC
LIMIT 1;

-- Para territory:
SELECT id, name, email, slack_user_id
FROM sales_reps
WHERE is_active = true
  AND territory = '{{ $nodes['regras-roteamento'].output.territory }}'
  AND leads_today < max_leads_per_day
ORDER BY leads_today ASC
LIMIT 1;

-- Para expertise:
SELECT id, name, email, slack_user_id
FROM sales_reps
WHERE is_active = true
  AND '{{ $nodes['regras-roteamento'].output.expertise }}' = ANY(expertise)
  AND leads_today < max_leads_per_day
ORDER BY leads_today ASC
LIMIT 1;

-- Para round-robin:
SELECT id, name, email, slack_user_id
FROM sales_reps
WHERE is_active = true
  AND leads_today < max_leads_per_day
ORDER BY leads_today ASC, RANDOM()
LIMIT 1;

Dica: Para fazer query dinâmica baseada no método:

// Code Node com query dinâmica
{{
  const method = $nodes['regras-roteamento'].output.routingMethod;

  const queries = {
    "best-performer": `
      SELECT id, name, email, slack_user_id
      FROM sales_reps
      WHERE is_active = true AND leads_today < max_leads_per_day
      ORDER BY (SELECT COUNT(*) FROM leads WHERE assigned_to = sales_reps.email AND status = 'converted') DESC
      LIMIT 1
    `,
    "territory": `
      SELECT id, name, email, slack_user_id
      FROM sales_reps
      WHERE is_active = true AND territory = '${$nodes['regras-roteamento'].output.territory}' AND leads_today < max_leads_per_day
      ORDER BY leads_today ASC
      LIMIT 1
    `,
    "expertise": `
      SELECT id, name, email, slack_user_id
      FROM sales_reps
      WHERE is_active = true AND '${$nodes['regras-roteamento'].output.expertise}' = ANY(expertise) AND leads_today < max_leads_per_day
      ORDER BY leads_today ASC
      LIMIT 1
    `,
    "round-robin": `
      SELECT id, name, email, slack_user_id
      FROM sales_reps
      WHERE is_active = true AND leads_today < max_leads_per_day
      ORDER BY leads_today ASC, RANDOM()
      LIMIT 1
    `
  };

  return {
    "query": queries[method] || queries["round-robin"]
  };
}}

5.3. Fallback (Se Nenhum Vendedor Disponível)

// If/Else: "Vendedor Encontrado?"
{{
  $nodes['selecionar-vendedor'].output.rows &&
  $nodes['selecionar-vendedor'].output.rows.length > 0
}}

Branch FALSE: Adicione lógica de fallback: - Notificar gerente de vendas - Colocar lead em fila de espera - Enviar email automático para o lead

Parte 6: Salvar Lead e Atualizar CRM

6.1. Salvar no Banco de Dados

// Database Insert/Update: "Salvar Lead"
{
  "table": "leads",
  "operation": "upsert",
  "conflictTarget": ["email"],
  "data": {
    "email": "{{ $nodes['normalizar'].output.email }}",
    "first_name": "{{ $nodes['normalizar'].output.firstName }}",
    "last_name": "{{ $nodes['normalizar'].output.lastName }}",
    "phone": "{{ $nodes['normalizar'].output.phone }}",
    "company": "{{ $nodes['processar-clearbit'].output.companyName }}",
    "job_title": "{{ $nodes['normalizar'].output.jobTitle }}",

    "company_size": "{{ $nodes['processar-clearbit'].output.employeeRange }}",
    "industry": "{{ $nodes['processar-clearbit'].output.industry }}",
    "annual_revenue": "{{ $nodes['processar-clearbit'].output.annualRevenue }}",
    "technology_stack": "{{ JSON.stringify($nodes['processar-clearbit'].output.techStack) }}",
    "employee_count": "{{ $nodes['processar-clearbit'].output.employeeCount }}",
    "linkedin_url": "{{ $nodes['processar-clearbit'].output.linkedinUrl }}",
    "company_domain": "{{ $nodes['processar-clearbit'].output.domain }}",

    "demographic_score": "{{ $nodes['calc-demografico'].output.demographicScore }}",
    "behavioral_score": "{{ $nodes['calc-comportamental'].output.behavioralScore }}",
    "engagement_score": "{{ $nodes['calc-engajamento']?.output.engagementScore || 0 }}",
    "total_score": "{{ $nodes['calc-total'].output.totalScore }}",
    "grade": "{{ $nodes['calc-total'].output.grade }}",
    "temperature": "{{ $nodes['calc-total'].output.temperature }}",

    "source": "{{ $nodes['normalizar'].output.source }}",
    "utm_source": "{{ $nodes['normalizar'].output.utmSource }}",
    "utm_medium": "{{ $nodes['normalizar'].output.utmMedium }}",
    "utm_campaign": "{{ $nodes['normalizar'].output.utmCampaign }}",
    "first_touch_page": "{{ $nodes['normalizar'].output.landingPage }}",
    "last_activity_at": "{{ new Date().toISOString() }}",

    "assigned_to": "{{ $nodes['selecionar-vendedor'].output.rows[0]?.email }}",
    "assigned_at": "{{ new Date().toISOString() }}",
    "status": "{{ $nodes['calc-total'].output.grade === 'A' ? 'qualified' : 'new' }}",
    "updated_at": "{{ new Date().toISOString() }}"
  }
}

6.2. Criar no HubSpot

// HTTP Request: "Criar Contato no HubSpot"
{
  "method": "POST",
  "url": "https://api.hubapi.com/crm/v3/objects/contacts",
  "headers": {
    "Authorization": "Bearer {{ $credentials.hubspot.accessToken }}",
    "Content-Type": "application/json"
  },
  "body": {
    "properties": {
      "email": "{{ $nodes['normalizar'].output.email }}",
      "firstname": "{{ $nodes['normalizar'].output.firstName }}",
      "lastname": "{{ $nodes['normalizar'].output.lastName }}",
      "phone": "{{ $nodes['normalizar'].output.phone }}",
      "company": "{{ $nodes['processar-clearbit'].output.companyName }}",
      "jobtitle": "{{ $nodes['normalizar'].output.jobTitle }}",

      // Custom properties (crie no HubSpot antes)
      "lead_score": "{{ $nodes['calc-total'].output.totalScore }}",
      "lead_grade": "{{ $nodes['calc-total'].output.grade }}",
      "lead_temperature": "{{ $nodes['calc-total'].output.temperature }}",
      "demographic_score": "{{ $nodes['calc-demografico'].output.demographicScore }}",
      "behavioral_score": "{{ $nodes['calc-comportamental'].output.behavioralScore }}",

      "lifecyclestage": "{{ $nodes['calc-total'].output.grade === 'A' ? 'marketingqualifiedlead' : 'lead' }}",
      "hs_lead_status": "{{ $nodes['calc-total'].output.temperature === 'hot' ? 'OPEN' : 'NEW' }}"
    }
  }
}
// Database Update: "Salvar ID do HubSpot"
{
  "table": "leads",
  "data": {
    "crm_id": "{{ $nodes['criar-hubspot'].output.data.id }}",
    "crm_type": "hubspot"
  },
  "where": {
    "email": "{{ $nodes['normalizar'].output.email }}"
  }
}

6.3. Criar Tarefa para o Vendedor no HubSpot

// HTTP Request: "Criar Tarefa no HubSpot"
{
  "method": "POST",
  "url": "https://api.hubapi.com/crm/v3/objects/tasks",
  "headers": {
    "Authorization": "Bearer {{ $credentials.hubspot.accessToken }}",
    "Content-Type": "application/json"
  },
  "body": {
    "properties": {
      "hs_task_subject": "🔥 Novo Lead {{ $nodes['calc-total'].output.grade }} - {{ $nodes['normalizar'].output.firstName }} {{ $nodes['normalizar'].output.lastName }}",
      "hs_task_body": `Lead Score: {{ $nodes['calc-total'].output.totalScore }}/100
Grade: {{ $nodes['calc-total'].output.grade }}
Temperatura: {{ $nodes['calc-total'].output.temperature }}

Empresa: {{ $nodes['processar-clearbit'].output.companyName }}
Cargo: {{ $nodes['normalizar'].output.jobTitle }}
Funcionários: {{ $nodes['processar-clearbit'].output.employeeCount }}
Indústria: {{ $nodes['processar-clearbit'].output.industry }}

Mensagem: {{ $nodes['normalizar'].output.customFields.message }}

LinkedIn: {{ $nodes['processar-clearbit'].output.linkedinUrl }}`,
      "hs_task_status": "NOT_STARTED",
      "hs_task_priority": "{{ $nodes['regras-roteamento'].output.priority === 'high' ? 'HIGH' : 'MEDIUM' }}",
      "hs_timestamp": "{{ new Date(Date.now() + 5 * 60 * 1000).toISOString() }}", // +5 min
      "hs_task_type": "CALL"
    },
    "associations": [
      {
        "to": {
          "id": "{{ $nodes['criar-hubspot'].output.data.id }}"
        },
        "types": [
          {
            "associationCategory": "HUBSPOT_DEFINED",
            "associationTypeId": 204 // Task to Contact
          }
        ]
      }
    ]
  }
}

Parte 7: Notificações em Tempo Real

7.1. Notificar no Slack

// Slack - Send Message
{
  "channel": "{{ $nodes['selecionar-vendedor'].output.rows[0]?.slack_user_id }}",
  "text": "🚨 Novo Lead Atribuído!",
  "blocks": [
    {
      "type": "header",
      "text": {
        "type": "plain_text",
        "text": "🎯 Novo Lead {{ $nodes['calc-total'].output.temperature === 'hot' ? '🔥' : ($nodes['calc-total'].output.temperature === 'warm' ? '🌡️' : '❄️') }}",
        "emoji": true
      }
    },
    {
      "type": "section",
      "fields": [
        {
          "type": "mrkdwn",
          "text": `*Nome:*\n{{ $nodes['normalizar'].output.firstName }} {{ $nodes['normalizar'].output.lastName }}`
        },
        {
          "type": "mrkdwn",
          "text": `*Empresa:*\n{{ $nodes['processar-clearbit'].output.companyName }}`
        },
        {
          "type": "mrkdwn",
          "text": `*Email:*\n{{ $nodes['normalizar'].output.email }}`
        },
        {
          "type": "mrkdwn",
          "text": `*Telefone:*\n{{ $nodes['normalizar'].output.phone }}`
        },
        {
          "type": "mrkdwn",
          "text": `*Cargo:*\n{{ $nodes['normalizar'].output.jobTitle }}`
        },
        {
          "type": "mrkdwn",
          "text": `*Score:*\n{{ $nodes['calc-total'].output.totalScore }}/100 (Grade {{ $nodes['calc-total'].output.grade }})`
        }
      ]
    },
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": `*📊 Breakdown do Score:*\n• Demográfico: {{ $nodes['calc-demografico'].output.demographicScore }}/100\n• Comportamental: {{ $nodes['calc-comportamental'].output.behavioralScore }}/100\n• Engajamento: {{ $nodes['calc-engajamento']?.output.engagementScore || 0 }}/100`
      }
    },
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": `*🏢 Sobre a Empresa:*\n• Funcionários: {{ $nodes['processar-clearbit'].output.employeeCount || 'N/A' }}\n• Indústria: {{ $nodes['processar-clearbit'].output.industry || 'N/A' }}\n• Receita: {{ $nodes['processar-clearbit'].output.annualRevenue ? '$' + ($nodes['processar-clearbit'].output.annualRevenue / 1000000).toFixed(1) + 'M' : 'N/A' }}`
      }
    },
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": `*💬 Mensagem:*\n_{{ $nodes['normalizar'].output.customFields.message || 'Nenhuma mensagem' }}_`
      }
    },
    {
      "type": "actions",
      "elements": [
        {
          "type": "button",
          "text": {
            "type": "plain_text",
            "text": "Ver no HubSpot",
            "emoji": true
          },
          "url": "https://app.hubspot.com/contacts/{{ $vars.hubspotAccountId }}/contact/{{ $nodes['criar-hubspot'].output.data.id }}",
          "style": "primary"
        },
        {
          "type": "button",
          "text": {
            "type": "plain_text",
            "text": "LinkedIn",
            "emoji": true
          },
          "url": "{{ $nodes['processar-clearbit'].output.linkedinUrl || 'https://linkedin.com' }}"
        }
      ]
    },
    {
      "type": "context",
      "elements": [
        {
          "type": "mrkdwn",
          "text": `⏰ *Responder em:* {{ $nodes['regras-roteamento'].output.responseTime }} | 📍 Origem: {{ $nodes['normalizar'].output.source }}`
        }
      ]
    }
  ]
}

7.2. Enviar Email para o Vendedor

// Send Email
{
  "to": "{{ $nodes['selecionar-vendedor'].output.rows[0]?.email }}",
  "from": "leads@empresa.com",
  "subject": "🎯 Novo Lead {{ $nodes['calc-total'].output.grade }} Atribuído - {{ $nodes['normalizar'].output.firstName }} {{ $nodes['normalizar'].output.lastName }}",
  "html": `
    <!DOCTYPE html>
    <html>
    <head>
      <style>
        body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
        .container { max-width: 600px; margin: 0 auto; padding: 20px; }
        .header {
          background: {{ $nodes['calc-total'].output.temperature === 'hot' ? '#FF4444' : ($nodes['calc-total'].output.temperature === 'warm' ? '#FFA500' : '#4A90E2') }};
          color: white;
          padding: 20px;
          text-align: center;
          border-radius: 5px 5px 0 0;
        }
        .score-badge {
          display: inline-block;
          background: white;
          color: {{ $nodes['calc-total'].output.temperature === 'hot' ? '#FF4444' : '#4A90E2' }};
          padding: 10px 20px;
          border-radius: 20px;
          font-size: 24px;
          font-weight: bold;
          margin: 10px 0;
        }
        .info-section { background: #f9f9f9; padding: 15px; margin: 15px 0; border-radius: 5px; }
        .info-row { margin: 10px 0; }
        .label { font-weight: bold; color: #666; }
        .cta-button {
          display: inline-block;
          background: #4A90E2;
          color: white !important;
          padding: 15px 30px;
          text-decoration: none;
          border-radius: 5px;
          margin: 20px 10px;
        }
      </style>
    </head>
    <body>
      <div class="container">
        <div class="header">
          <h1>{{ $nodes['calc-total'].output.temperature === 'hot' ? '🔥' : ($nodes['calc-total'].output.temperature === 'warm' ? '🌡️' : '❄️') }} Novo Lead Atribuído</h1>
          <div class="score-badge">Grade {{ $nodes['calc-total'].output.grade }} - {{ $nodes['calc-total'].output.totalScore }}/100</div>
        </div>

        <div class="info-section">
          <h3>👤 Informações do Contato</h3>
          <div class="info-row"><span class="label">Nome:</span> {{ $nodes['normalizar'].output.firstName }} {{ $nodes['normalizar'].output.lastName }}</div>
          <div class="info-row"><span class="label">Email:</span> {{ $nodes['normalizar'].output.email }}</div>
          <div class="info-row"><span class="label">Telefone:</span> {{ $nodes['normalizar'].output.phone }}</div>
          <div class="info-row"><span class="label">Cargo:</span> {{ $nodes['normalizar'].output.jobTitle }}</div>
        </div>

        <div class="info-section">
          <h3>🏢 Informações da Empresa</h3>
          <div class="info-row"><span class="label">Empresa:</span> {{ $nodes['processar-clearbit'].output.companyName }}</div>
          <div class="info-row"><span class="label">Indústria:</span> {{ $nodes['processar-clearbit'].output.industry }}</div>
          <div class="info-row"><span class="label">Funcionários:</span> {{ $nodes['processar-clearbit'].output.employeeCount || 'N/A' }}</div>
          <div class="info-row"><span class="label">Receita Anual:</span> {{ $nodes['processar-clearbit'].output.annualRevenue ? '$' + ($nodes['processar-clearbit'].output.annualRevenue / 1000000).toFixed(1) + 'M' : 'N/A' }}</div>
        </div>

        <div class="info-section">
          <h3>📊 Breakdown do Score</h3>
          <div class="info-row"><span class="label">Score Demográfico:</span> {{ $nodes['calc-demografico'].output.demographicScore }}/100</div>
          <div class="info-row"><span class="label">Score Comportamental:</span> {{ $nodes['calc-comportamental'].output.behavioralScore }}/100</div>
          <div class="info-row"><span class="label">Score de Engajamento:</span> {{ $nodes['calc-engajamento']?.output.engagementScore || 0 }}/100</div>
        </div>

        <div class="info-section">
          <h3>💬 Mensagem do Lead</h3>
          <p><em>{{ $nodes['normalizar'].output.customFields.message || 'Nenhuma mensagem fornecida' }}</em></p>
        </div>

        <div style="text-align: center; margin: 30px 0;">
          <a href="https://app.hubspot.com/contacts/{{ $vars.hubspotAccountId }}/contact/{{ $nodes['criar-hubspot'].output.data.id }}" class="cta-button">
            Ver no HubSpot
          </a>
          <a href="{{ $nodes['processar-clearbit'].output.linkedinUrl || '#' }}" class="cta-button">
            Ver no LinkedIn
          </a>
        </div>

        <div style="background: #fff3cd; padding: 15px; border-left: 4px solid #ffc107; margin: 20px 0;">
          <strong>⏰ Tempo de Resposta Esperado:</strong> {{ $nodes['regras-roteamento'].output.responseTime }}
        </div>

        <div style="text-align: center; color: #666; font-size: 12px; margin-top: 30px;">
          <p>Lead capturado via {{ $nodes['normalizar'].output.source }} em {{ new Date().toLocaleString('pt-BR') }}</p>
        </div>
      </div>
    </body>
    </html>
  `
}

7.3. Enviar SMS para Leads A/Hot (Opcional)

// SMS - Send via Twilio
{
  "to": "{{ $nodes['normalizar'].output.phone }}",
  "from": "{{ $credentials.twilio.phoneNumber }}",
  "body": "Olá {{ $nodes['normalizar'].output.firstName }}! Obrigado pelo interesse. {{ $nodes['selecionar-vendedor'].output.rows[0]?.name }} da nossa equipe entrará em contato em breve. Estamos ansiosos para ajudá-lo! 🚀"
}

Parte 8: Incrementar Contador do Vendedor

// Database Update: "Incrementar Leads do Dia"
{
  "query": `
    UPDATE sales_reps
    SET leads_today = leads_today + 1
    WHERE email = '{{ $nodes['selecionar-vendedor'].output.rows[0]?.email }}'
  `
}

Parte 9: Registrar Atividade

// Database Insert: "Registrar Atividade"
{
  "table": "lead_activities",
  "data": {
    "lead_id": "{{ $nodes['salvar-lead'].output.id }}",
    "activity_type": "lead_created",
    "activity_data": "{{ JSON.stringify({ source: $nodes['normalizar'].output.source, score: $nodes['calc-total'].output.totalScore, grade: $nodes['calc-total'].output.grade }) }}",
    "points_awarded": 0,
    "created_at": "{{ new Date().toISOString() }}"
  }
}

Flow Completo (Resumido)

START (Webhook)
  ↓
Normalizar Dados
  ↓
Validar Email → [FALSE] → END (Email Inválido)
  ↓ [TRUE]
Verificar Duplicata
  ↓
[SPLIT]
  ↓ Lead Novo              ↓ Lead Existente
  ↓                        ↓
Extrair Domínio      Buscar Atividades
  ↓                        ↓
Enriquecer (Clearbit) Calc. Engagement Score
  ↓                        ↓
[MERGE] ←─────────────────┘
  ↓
Calcular Score Demográfico
  ↓
Calcular Score Comportamental
  ↓
Calcular Score Total
  ↓
Determinar Regras de Roteamento
  ↓
Selecionar Vendedor → [NOTFOUND] → Notificar Gerente
  ↓ [FOUND]
Salvar Lead no DB
  ↓
Criar/Atualizar no HubSpot
  ↓
Criar Tarefa no HubSpot
  ↓
[PARALLEL]
  ├→ Notificar Slack
  ├→ Enviar Email Vendedor
  ├→ Enviar SMS Lead (se A/Hot)
  └→ Incrementar Contador
  ↓
[MERGE]
  ↓
Registrar Atividade
  ↓
END

Teste do Sistema

1. Teste com Lead Qualificado (A/Hot)

curl -X POST https://api.lumina.app.br/v1/webhooks/wh_scoring_xyz \
  -H "Content-Type: application/json" \
  -d '{
    "email": "ceo@techcorp.com",
    "first_name": "Carlos",
    "last_name": "Mendes",
    "phone": "+5511987654321",
    "company": "TechCorp Solutions",
    "job_title": "CEO & Founder",
    "company_size": "100-500",
    "industry": "Technology",
    "source": "referral",
    "utm_source": "partner",
    "utm_campaign": "q1-2025",
    "message": "Preciso implementar automação de vendas urgentemente. Orçamento de 100k disponível.",
    "budget": ">100k",
    "timeline": "imediato"
  }'

Resultado esperado: - Score total: ~85-95 - Grade: A - Temperatura: Hot - Roteado para melhor vendedor - Notificação imediata no Slack - Email enviado - SMS enviado ao lead - Tarefa criada com prioridade ALTA

2. Teste com Lead Médio (B/Warm)

curl -X POST https://api.lumina.app.br/v1/webhooks/wh_scoring_xyz \
  -H "Content-Type: application/json" \
  -d '{
    "email": "maria@startup.com",
    "name": "Maria Silva",
    "phone": "+5511976543210",
    "company": "Startup XYZ",
    "job_title": "Marketing Manager",
    "source": "organic",
    "message": "Gostaria de conhecer mais sobre a solução."
  }'

Resultado esperado: - Score total: ~50-65 - Grade: B - Temperatura: Warm - Roteado por território ou round-robin - Notificação no Slack - Email enviado

3. Teste com Lead Frio (C/D/Cold)

curl -X POST https://api.lumina.app.br/v1/webhooks/wh_scoring_xyz \
  -H "Content-Type: application/json" \
  -d '{
    "email": "contato@pequenaempresa.com",
    "name": "João",
    "source": "paid"
  }'

Resultado esperado: - Score total: <40 - Grade: C ou D - Temperatura: Cold - Roteado em round-robin de baixa prioridade - Apenas notificação no Slack

Troubleshooting

Problema 1: Enriquecimento falha constantemente

Solução: - Configure errorHandling: "continueOnFail" no nó HTTP - Adicione valores default quando Clearbit não retorna dados - Use cache para domínios já consultados

Problema 2: Leads não estão sendo roteados

Causa: Todos os vendedores atingiram o limite diário Solução: - Aumente max_leads_per_day ou - Implemente fila de espera ou - Reset automático do contador à meia-noite:

-- Cron job diário (00:00)
UPDATE sales_reps SET leads_today = 0;

Problema 3: Scores muito baixos/altos

Solução: Ajuste os pesos e valores nos algoritmos de scoring conforme seu perfil de cliente ideal (ICP).

Problema 4: Duplicatas no HubSpot

Solução: Use upsert baseado no email:

// Primeiro, buscar por email:
GET https://api.hubapi.com/crm/v3/objects/contacts/{{ $nodes['normalizar'].output.email }}?idProperty=email

// Se existir, usar PATCH em vez de POST

Otimizações e Melhorias

1. Machine Learning para Score Preditivo

Treine um modelo ML que prevê probabilidade de conversão:

// HTTP Request para seu modelo ML
POST https://ml-api.empresa.com/predict
{
  "features": {
    "company_size": {{ $nodes['processar-clearbit'].output.employeeCount }},
    "industry": "{{ $nodes['processar-clearbit'].output.industry }}",
    "job_title": "{{ $nodes['normalizar'].output.jobTitle }}",
    "source": "{{ $nodes['normalizar'].output.source }}",
    // ... outros features
  }
}

// Response: { "conversion_probability": 0.78 }

Use isso para ajustar o score final.

2. Decay de Score ao Longo do Tempo

Leads antigos sem atividade devem ter score reduzido:

-- Executar diariamente
UPDATE leads
SET total_score = GREATEST(
  total_score -
  CASE
    WHEN last_activity_at < NOW() - INTERVAL '30 days' THEN 5
    WHEN last_activity_at < NOW() - INTERVAL '7 days' THEN 2
    ELSE 0
  END,
  0
)
WHERE status NOT IN ('converted', 'lost');

3. Re-scoring Automático

Quando um lead realiza nova atividade, recalcule o score:

// Trigger: Lead Activity Created
// Chame o mesmo flow de scoring, mas com mode="update"

4. Dashboard de Performance

Crie queries para KPIs:

-- Conversão por grade
SELECT
  grade,
  COUNT(*) as total_leads,
  COUNT(*) FILTER (WHERE status = 'converted') as converted,
  ROUND(
    COUNT(*) FILTER (WHERE status = 'converted')::DECIMAL /
    NULLIF(COUNT(*), 0) * 100,
    2
  ) as conversion_rate
FROM leads
WHERE created_at >= NOW() - INTERVAL '30 days'
GROUP BY grade
ORDER BY grade;

-- Performance por vendedor
SELECT
  assigned_to,
  COUNT(*) as leads_assigned,
  COUNT(*) FILTER (WHERE status = 'converted') as converted,
  AVG(total_score) as avg_lead_score,
  AVG(EXTRACT(EPOCH FROM (recovered_at - assigned_at))/3600) as avg_hours_to_convert
FROM leads
WHERE assigned_at >= NOW() - INTERVAL '30 days'
GROUP BY assigned_to
ORDER BY converted DESC;

-- ROI do Scoring
SELECT
  temperature,
  COUNT(*) as leads,
  COUNT(*) FILTER (WHERE status = 'converted') as converted,
  ROUND(AVG(final_value), 2) as avg_deal_value
FROM leads
WHERE created_at >= NOW() - INTERVAL '30 days'
GROUP BY temperature
ORDER BY
  CASE temperature
    WHEN 'hot' THEN 1
    WHEN 'warm' THEN 2
    WHEN 'cold' THEN 3
  END;

Métricas de Sucesso

KPIs Principais

  1. Taxa de Conversão por Grade
  2. Grade A: Meta >30%
  3. Grade B: Meta >15%
  4. Grade C/D: Meta >5%

  5. Tempo Médio de Resposta

  6. Hot: <5 min
  7. Warm: <30 min
  8. Cold: <2h

  9. Taxa de Aceite do Vendedor: Meta >90%

  10. Precisão do Scoring: Comparar score vs. conversão real

  11. ROI: (Receita de leads convertidos) / (Custo do sistema)

Próximos Passos

Depois de dominar lead scoring, explore:

  1. Carrinhos Abandonados - Recupere vendas
  2. Campanha Multicanal - Nutra seus leads
  3. Sistema de Feedback - Melhore continuamente

Recursos Adicionais

  • HubSpot API: https://developers.hubspot.com/docs/api/crm/contacts
  • Clearbit API: https://clearbit.com/docs
  • Salesforce API: https://developer.salesforce.com/docs/apis
  • Lead Scoring Best Practices: https://blog.hubspot.com/marketing/lead-scoring

Última atualização: Janeiro 2025 Versão do tutorial: 1.0