Revenue Ops
TypeScript
Webhook Trigger

Sync Lifecycle Stage to Customer on Deal Won

When a deal is marked Closed Won, automatically update all associated contacts and the primary company to Customer lifecycle stage.

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)

lifecycle-stage-sync-on-deal-win.ts
TypeScript
export default async function main(input: {
  dealId?: string
  [key: string]: unknown
}): Promise<{ updatedContacts: number; updatedCompany: boolean; scannedDeals?: number }> {
  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 || (rawInput.fields && (rawInput.fields as Record<string, unknown>).dealId)) as string | undefined

  let updatedContacts = 0
  let updatedCompany = false
  const targetDealIds: string[] = dealId ? [dealId] : []

  // Trigger-Agnostic Fallback: if no specific dealId provided, scan recent closed-won deals
  if (targetDealIds.length === 0) {
    const wonDeals = await hubspot.client.crm.deals.basicApi.getPage(
      25,
      0,
      ['dealname', 'dealstage', 'closedate'],
      undefined,
      undefined,
      [{ value: 'closedwon', propertyName: 'dealstage', operator: 'EQ' }]
    )
    for (const d of wonDeals.results) {
      targetDealIds.push(d.id)
    }
  }

  for (const dId of targetDealIds) {
    // 1. Fetch associated contacts
    const contactAssocs = await hubspot.client.crm.associations.v4.basicApi.getPage(
      'deals',
      dId,
      'contacts',
      50
    )

    for (const assoc of (contactAssocs.results || [])) {
      await hubspot.client.crm.contacts.basicApi.update(assoc.toObjectId, {
        properties: { lifecyclestage: 'customer' },
      })
      updatedContacts++
    }

    // 2. Fetch associated companies
    const companyAssocs = await hubspot.client.crm.associations.v4.basicApi.getPage(
      'deals',
      dId,
      'companies',
      5
    )

    if (companyAssocs.results && companyAssocs.results.length > 0) {
      await hubspot.client.crm.companies.basicApi.update(companyAssocs.results[0].toObjectId, {
        properties: { lifecyclestage: 'customer' },
      })
      updatedCompany = true
    }
  }

  return { updatedContacts, updatedCompany, scannedDeals: targetDealIds.length }
}

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.