Marketing Ops
TypeScript
Webhook Trigger

Associate Company to Marketing Event on Attendance

When a contact attends a marketing event (webinar, conference), look up their associated company and link the company to the marketing event for account-level event reporting.

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)

associate-company-marketing-event.ts
TypeScript
export default async function main(input: {
  contactId?: string
  email?: string
  marketingEventId?: string
  attendanceState?: 'ATTENDED' | 'REGISTERED' | 'NO_SHOW' | 'CANCELLED'
  [key: string]: unknown
}): Promise<{
  associated: boolean
  companyId?: string
  contactId?: string
  marketingEventId?: string
  processedCount?: number
  message: string
}> {
  const rawInput = (Array.isArray(input) ? input[0] : input) || {}
  let contactId = (rawInput.contactId || rawInput.contact_id || rawInput.objectId || (rawInput.object && (rawInput.object as Record<string, unknown>).objectId) || rawInput.hs_object_id || (rawInput.fields && (rawInput.fields as Record<string, unknown>).contactId)) as string | undefined
  let email = (rawInput.email || (rawInput.fields && (rawInput.fields as Record<string, unknown>).email) || (rawInput.properties && (rawInput.properties as Record<string, unknown>).email)) as string | undefined
  let marketingEventId = (rawInput.marketingEventId || rawInput.marketing_event_id || rawInput.eventId || rawInput.event_id || (rawInput.fields && (rawInput.fields as Record<string, unknown>).marketingEventId)) as string | undefined
  const attendanceState = (rawInput.attendanceState || rawInput.attendance_state) as string | undefined

  // Only link company if attendance state is not a non-attendance
  if (attendanceState && attendanceState !== 'ATTENDED') {
    return {
      associated: false,
      marketingEventId,
      message: `Skipped: contact attendanceState is "${attendanceState}" (not ATTENDED)`,
    }
  }

  // Trigger-Agnostic / Batch Fallback: if no specific contact/event is given, scan recent contacts
  if (!contactId && !email && !marketingEventId) {
    const recentContacts = await hubspot.client.crm.contacts.basicApi.getPage(
      25,
      0,
      ['email', 'firstname', 'lastname']
    )
    let linkedCount = 0
    for (const c of recentContacts.results) {
      const cAssoc = await hubspot.client.crm.associations.v4.basicApi.getPage('contacts', c.id, 'companies', 1)
      const compId = cAssoc.results?.[0]?.toObjectId
      if (!compId) continue

      const eAssoc = await hubspot.client.crm.associations.v4.basicApi.getPage('contacts', c.id, 'marketing_events', 5)
      for (const e of (eAssoc.results || [])) {
        await hubspot.client.crm.associations.v4.basicApi.create(
          'companies',
          compId,
          'marketing_events',
          e.toObjectId,
          [{ associationCategory: 'HUBSPOT_DEFINED', associationTypeId: 14 }]
        )
        linkedCount++
      }
    }
    return {
      associated: linkedCount > 0,
      processedCount: recentContacts.results.length,
      message: `Trigger-agnostic scan: evaluated ${recentContacts.results.length} contacts, created ${linkedCount} company-to-event association(s)`,
    }
  }

  // 1. Resolve contact ID if only email is passed
  if (!contactId && email) {
    const contactRes = await hubspot.client.crm.contacts.basicApi.getPage(
      1,
      0,
      ['email', 'associatedcompanyid'],
      undefined,
      undefined,
      [{ value: email, propertyName: 'email', operator: 'EQ' }]
    )
    if (contactRes.results.length > 0) {
      contactId = contactRes.results[0].id
    }
  }

  if (!contactId) {
    throw new Error('Could not determine contactId (provide contactId or valid email)')
  }

  // 2. Query contact associations to find the primary associated company
  const assocRes = await hubspot.client.crm.associations.v4.basicApi.getPage(
    'contacts',
    contactId,
    'companies',
    5
  )

  const companyId = assocRes.results[0]?.toObjectId

  if (!companyId) {
    return {
      associated: false,
      contactId,
      marketingEventId,
      message: `No associated company found for contact ID ${contactId}`,
    }
  }

  // If marketingEventId not specified, look up contact's attended marketing events
  if (!marketingEventId) {
    const eventAssocs = await hubspot.client.crm.associations.v4.basicApi.getPage(
      'contacts',
      contactId,
      'marketing_events',
      5
    )
    if (!eventAssocs.results || eventAssocs.results.length === 0) {
      return {
        associated: false,
        companyId,
        contactId,
        message: `No marketing events associated with contact ${contactId}`,
      }
    }
    marketingEventId = eventAssocs.results[0].toObjectId
  }

  // 3. Associate the company to the marketing event for ABM attribution
  await hubspot.client.crm.associations.v4.basicApi.create(
    'companies',
    companyId,
    'marketing_events',
    marketingEventId,
    [
      {
        associationCategory: 'HUBSPOT_DEFINED',
        associationTypeId: 14,
      },
    ]
  )

  return {
    associated: true,
    companyId,
    contactId,
    marketingEventId,
    message: `Successfully linked company ${companyId} to marketing event ${marketingEventId}`,
  }
}

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.