Marketing Ops
Python
Webhook Trigger

Associate Deals to Marketing Event (Python)

Automatically link deals created within 3 months of contact marketing event attendance to the attended event using Python.

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 (Python)

associate-deal-marketing-event-python.py
Python
import datetime

def main(input_data, hs_client, http_client, secret, console):
    raw = input_data[0] if isinstance(input_data, list) and input_data else (input_data or {})
    deal_id = raw.get('deal_id') or raw.get('dealId') or raw.get('objectId') or raw.get('hs_object_id')
    contact_id = raw.get('contact_id') or raw.get('contactId')
    marketing_event_id = raw.get('marketing_event_id') or raw.get('marketingEventId')
    lookback_months = int(input_data.get('lookback_months', 3))
    remove_outside_window = bool(input_data.get('remove_outside_window', False))
    lookback_delta = datetime.timedelta(days=lookback_months * 30)
    now = datetime.datetime.utcnow()

    # Scenario A: Triggered when Deal is created
    if deal_id:
        deal = hs_client.crm.deals.basic_api.get_by_id(deal_id, properties=['dealname', 'createdate'])
        create_str = deal.properties.get('createdate')
        deal_created = datetime.datetime.fromisoformat(create_str.replace('Z', '+00:00')) if create_str else now

        contact_assocs = hs_client.crm.associations.v4.basic_api.get_page(
            from_object_type='deals',
            object_id=deal_id,
            to_object_type='contacts',
            limit=50
        )

        if not contact_assocs.results:
            return {"associatedDealsCount": 0, "associatedDealIds": [], "message": f"No associated contacts on Deal {deal_id}"}

        linked_events = []
        for c_assoc in contact_assocs.results:
            cid = c_assoc.to_object_id
            event_assocs = hs_client.crm.associations.v4.basic_api.get_page(
                from_object_type='contacts',
                object_id=cid,
                to_object_type='marketing_events',
                limit=50
            )
            for e_assoc in event_assocs.results:
                eid = e_assoc.to_object_id
                hs_client.crm.associations.v4.basic_api.create(
                    from_object_type='deals',
                    object_id=deal_id,
                    to_object_type='marketing_events',
                    to_object_id=eid,
                    types=[{"associationCategory": "HUBSPOT_DEFINED", "associationTypeId": 15}]
                )
                linked_events.append(eid)

        return {
            "associatedDealsCount": 1 if linked_events else 0,
            "associatedDealIds": [deal_id] if linked_events else [],
            "message": f"Linked Deal {deal_id} to {len(linked_events)} Marketing Event(s)"
        }

    # Scenario B: Triggered when Contact attends event
    if contact_id and marketing_event_id:
        deal_assocs = hs_client.crm.associations.v4.basic_api.get_page(
            from_object_type='contacts',
            object_id=contact_id,
            to_object_type='deals',
            limit=50
        )

        associated_deals = []
        for d_assoc in deal_assocs.results:
            did = d_assoc.to_object_id
            d_record = hs_client.crm.deals.basic_api.get_by_id(did, properties=['createdate'])
            create_str = d_record.properties.get('createdate')
            if create_str:
                d_created = datetime.datetime.fromisoformat(create_str.replace('Z', '+00:00'))
                if (now - d_created) <= lookback_delta:
                    hs_client.crm.associations.v4.basic_api.create(
                        from_object_type='deals',
                        object_id=did,
                        to_object_type='marketing_events',
                        to_object_id=marketing_event_id,
                        types=[{"associationCategory": "HUBSPOT_DEFINED", "associationTypeId": 15}]
                    )
                    associated_deals.append(did)

        return {
            "associatedDealsCount": len(associated_deals),
            "associatedDealIds": associated_deals,
            "marketingEventId": marketing_event_id,
            "message": f"Associated {len(associated_deals)} Deal(s) created within {lookback_months} months to Marketing Event {marketing_event_id}"
        }

    # Scenario C: Trigger-Agnostic / Scheduled / Manual Batch Auto-Discovery
    recent_deals = hs_client.crm.deals.basic_api.get_page(
        limit=25,
        properties=['dealname', 'createdate', 'amount']
    )
    all_associated = []
    for d in (recent_deals.results or []):
        did = d.id
        create_str = d.properties.get('createdate')
        if create_str:
            d_created = datetime.datetime.fromisoformat(create_str.replace('Z', '+00:00'))
            if (now - d_created) > lookback_delta:
                continue
        c_assocs = hs_client.crm.associations.v4.basic_api.get_page(
            from_object_type='deals',
            object_id=did,
            to_object_type='contacts',
            limit=20
        )
        for c in (c_assocs.results or []):
            cid = c.to_object_id
            e_assocs = hs_client.crm.associations.v4.basic_api.get_page(
                from_object_type='contacts',
                object_id=cid,
                to_object_type='marketing_events',
                limit=20
            )
            for e in (e_assocs.results or []):
                eid = e.to_object_id
                hs_client.crm.associations.v4.basic_api.create(
                    from_object_type='deals',
                    object_id=did,
                    to_object_type='marketing_events',
                    to_object_id=eid,
                    types=[{"associationCategory": "HUBSPOT_DEFINED", "associationTypeId": 15}]
                )
                if did not in all_associated:
                    all_associated.append(did)

    return {
        "associatedDealsCount": len(all_associated),
        "associatedDealIds": all_associated,
        "message": f"Trigger-agnostic scan: evaluated {len(recent_deals.results or [])} recent deals, linked {len(all_associated)} deal(s) within {lookback_months}-month window to marketing events"
    }

Configurable Parameters

ParameterTypeDefaultDescription
lookbackMonthsnumber3Number of months prior to deal creation that contact event attendance is eligible for attribution.
removeOutsideWindowbooleanfalseIf enabled, un-associates any existing deals created outside the attribution window.

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.