Skooh.io Logo
Back to blog
IntegrationDeveloper TutorialMarch 26, 20258 min

HubSpot Webhooks to a Private Lead Scoring Service Using Skooh

Build an internal lead scoring engine and sync scores back to HubSpot automatically.

Use Case

Your internal lead scoring uses proprietary signals:

  • Product usage data
  • Industry intelligence
  • Firmographic data from internal sources

You want to score leads internally and push scores to HubSpot for your sales team.

Architecture

HubSpot (contact.propertyChange) → Skooh → Lead Scoring Engine
                                                   ↓
                                            Internal Data Sources
                                                   ↓
                                            HubSpot API (update score)

Setup

1. Create Skooh Endpoint

Create "HubSpot Lead Scoring" in Skooh.

2. Run the Skooh Proxy

Using the Desktop App:

  1. Download the Skooh Desktop App
  2. Enter your API key and hook token
  3. Set forwarding URL (e.g., `http://localhost:3000/webhooks/hubspot\`)
  4. Click Connect

Using Docker:

docker run -d \\
  --name skooh-hubspot \\
  -e SKOOH_TOKEN=hubspot-leads \\
  -e SKOOH_API_KEY=your_api_key \\
  -e FORWARD_URL=http://lead-scoring:3000/webhooks/hubspot \\
  --restart unless-stopped \\
  skooh/proxy

3. Configure HubSpot Webhook

Note: See HubSpot's Webhook documentation for current setup instructions.

In HubSpot:

  1. Go to Settings → Integrations → Private Apps
  2. Create a private app with webhook subscriptions
  3. Subscribe to `contact.propertyChange` events
  4. Point to your Skooh URL

The Lead Scoring Handler

const hubspot = require('@hubspot/api-client');
const hubspotClient = new hubspot.Client({ accessToken: process.env.HUBSPOT_TOKEN });

app.post('/webhooks/hubspot', express.json(), async (req, res) => {
  const events = req.body;
  
  for (const event of events) {
    if (event.subscriptionType === 'contact.propertyChange') {
      await handleContactChange(event);
    }
  }
  
  res.status(200).send('OK');
});

async function handleContactChange(event) {
  const contactId = event.objectId;
  const propertyName = event.propertyName;
  const propertyValue = event.propertyValue;
  
  // Re-score on relevant property changes
  const scoreTriggers = ['email', 'company', 'jobtitle', 'industry'];
  
  if (scoreTriggers.includes(propertyName)) {
    await rescoreContact(contactId);
  }
}

The Scoring Engine

async function rescoreContact(contactId) {
  // Fetch contact from HubSpot
  const contact = await hubspotClient.crm.contacts.basicApi.getById(contactId, [
    'email', 'company', 'jobtitle', 'industry', 'website'
  ]);
  
  // Calculate score from multiple sources
  const score = await calculateLeadScore(contact.properties);
  
  // Update HubSpot with new score
  await hubspotClient.crm.contacts.basicApi.update(contactId, {
    properties: {
      lead_score: score.total.toString(),
      lead_score_breakdown: JSON.stringify(score.breakdown),
      lead_score_updated: new Date().toISOString()
    }
  });
  
  // High score? Notify sales
  if (score.total >= 80) {
    await notifySalesTeam(contact, score);
  }
}

async function calculateLeadScore(properties) {
  let score = 0;
  const breakdown = {};
  
  // 1. Firmographic score (from internal DB)
  const firmographic = await firmographicDb.lookup(properties.company);
  if (firmographic) {
    const firmScore = scoreFirmographic(firmographic);
    score += firmScore;
    breakdown.firmographic = firmScore;
  }
  
  // 2. Job title score
  const titleScore = scoreJobTitle(properties.jobtitle);
  score += titleScore;
  breakdown.title = titleScore;
  
  // 3. Industry fit
  const industryScore = scoreIndustry(properties.industry);
  score += industryScore;
  breakdown.industry = industryScore;
  
  // 4. Product usage (if they've signed up)
  const usage = await usageDb.findByEmail(properties.email);
  if (usage) {
    const usageScore = scoreUsage(usage);
    score += usageScore;
    breakdown.usage = usageScore;
  }
  
  // 5. Intent signals (from marketing data)
  const intent = await intentDb.findByDomain(extractDomain(properties.email));
  if (intent) {
    const intentScore = scoreIntent(intent);
    score += intentScore;
    breakdown.intent = intentScore;
  }
  
  return { total: Math.min(score, 100), breakdown };
}

function scoreJobTitle(title) {
  if (!title) return 0;
  const lower = title.toLowerCase();
  
  // Decision makers
  if (lower.includes('cto') || lower.includes('vp') || lower.includes('director')) {
    return 20;
  }
  // Technical leads
  if (lower.includes('architect') || lower.includes('lead') || lower.includes('manager')) {
    return 15;
  }
  // Engineers
  if (lower.includes('engineer') || lower.includes('developer')) {
    return 10;
  }
  return 5;
}

Verifying HubSpot Webhooks

const crypto = require('crypto');

function verifyHubSpotSignature(req) {
  const signature = req.headers['x-hubspot-signature-v3'];
  const timestamp = req.headers['x-hubspot-request-timestamp'];
  const uri = req.protocol + '://' + req.get('host') + req.originalUrl;
  
  const message = req.method + uri + JSON.stringify(req.body) + timestamp;
  const hash = crypto
    .createHmac('sha256', process.env.HUBSPOT_CLIENT_SECRET)
    .update(message)
    .digest('base64');
  
  return hash === signature;
}

Testing Lead Scoring

  1. Update a contact in HubSpot
  2. Find the webhook in Skooh
  3. Replay to test scoring
  4. Check that lead_score updated in HubSpot

Set up lead scoring →

Written by Skooh Team

Share:

Ready to try Skooh?

Start receiving webhooks in your local environment in under 5 minutes.

Skooh | HubSpot Webhooks to a Private Lead Scoring Service Using Skooh