Revenue Ops
TypeScript
Cron Schedule

Deal Health Score Calculator

Calculate health scores (0-100) for open deals based on close date recency, stage velocity, and owner assignment, then sync back to HubSpot.

Why this automation is needed

Native HubSpot workflows often struggle with complex multi-object traversals, conditional lookback attribution windows, custom phone format standardizations, and custom scoring calculations.

With Workflood, this recipe runs in a secure gVisor container sandbox with pre-authenticated HubSpot SDK injection, avoiding HubSpot Data Hub execution limits, memory bottlenecks, and per-seat fees.

Action Source Code (TypeScript)

deal-health-score-sync.ts
TypeScript
export default async function main(input: {
  dealId?: string
  [key: string]: unknown
}): Promise<{ processed: number; scored: number; errors: string[] }> {
  const rawInput = (Array.isArray(input) ? input[0] : input) || {}
  const dealId = (rawInput.dealId || rawInput.deal_id || rawInput.objectId || (rawInput.object && (rawInput.object as Record<string, unknown>).objectId) || rawInput.hs_object_id) as string | undefined

  let dealList: Array<{ id: string; properties: Record<string, string> }> = []

  if (dealId) {
    const deal = await hubspot.client.crm.deals.basicApi.getById(
      dealId,
      ['dealname', 'amount', 'dealstage', 'closedate', 'hs_lastmodifieddate', 'hubspot_owner_id']
    )
    dealList = [deal]
  } else {
    // Fetch active open deals
    const deals = await hubspot.client.crm.deals.basicApi.getPage(
      100,
      0,
      ['dealname', 'amount', 'dealstage', 'closedate', 'hs_lastmodifieddate', 'hubspot_owner_id']
    )
    dealList = deals.results
  }

  const now = Date.now()
  const errors: string[] = []
  let scored = 0

  for (const deal of dealList) {
    try {
      let healthScore = 100

      // Inactivity penalty
      const lastModified = deal.properties.hs_lastmodifieddate
        ? new Date(deal.properties.hs_lastmodifieddate).getTime()
        : now
      if (now - lastModified > 30 * 86400 * 1000) {
        healthScore -= 20
      }

      // Past due close date penalty
      const closeDate = deal.properties.closedate
        ? new Date(deal.properties.closedate).getTime()
        : null
      if (closeDate && closeDate < now) {
        healthScore -= 35
      }

      // Unassigned penalty
      if (!deal.properties.hubspot_owner_id) {
        healthScore -= 25
      }

      healthScore = Math.max(0, Math.min(100, healthScore))

      await hubspot.client.crm.deals.basicApi.update(deal.id, {
        properties: {
          deal_health_score: healthScore.toString(),
          health_score_calculated_at: new Date().toISOString(),
        },
      })
      scored++
    } catch (err) {
      errors.push(`Failed to update deal ${deal.id}: ${err}`)
    }
  }

  return { processed: dealList.length, scored, errors }
}

Configurable Parameters

ParameterTypeDefaultDescription
staleDaysThresholdnumber30Deduct points if deal has not been modified in X days.
pastDuePenaltynumber35Past Due Close Date Penalty (Points)

How to deploy in Workflood

1

Connect your HubSpot Portal

Add your HubSpot Private App token once. Workflood securely encrypts it via libsodium XChaCha20 and injects pre-authenticated SDK clients.

2

Create Action from Template

Select this recipe in Workflood. Edit variables, customize logic in Monaco editor, or execute dry runs with sample JSON payloads.

3

Trigger via Webhook or Cron

Trigger from standard HubSpot workflows via Webhook action, or schedule recurring batch syncs with cron expressions and timezone handling.