# Personal Training Acquisition Pipeline

## Summary

Using Middle and HubSpot workflows, you can automate deal pipelines that track intro session bookings, attendance, and final acquisition through training package purchases (both PIF and recurring packages). Middle consolidates member, event, purchase, and recurring service data into a single Deal object using custom properties and pipeline. This process gives trainers and sales teams clear, close-to-real-time visibility into the personal training acquisition journey for both new and existing members.

## Action Checklist

The below is needed to setup this integration:

* [ ] [Create Hubspot Deal properties](#hubspot-deal-properties-to-create)
* [ ] [Create Hubspot workflows](#hubspot-workflow-configuration)
* [ ] [Create the Deal pipeline powering your PT acquisition pipeline](#pipeline-stages) (take note of the internal pipeline ID, as this needs to be entered in Middle workflows)
* [ ] Define which ABC Ignite events will power the Deal pipeline (typically based on the `eventName` in ABC Ignite)
* [ ] Define which PIF and recurring packages will close-win the deal (typically based on the package name and the recurring service name)
* [ ] Connect [ABC Ignite](https://docs.middle.app/middle-docs/guides/app-reference/abc-ignite) and [Hubspot](https://docs.middle.app/middle-docs/guides/app-reference/hubspot) to Middle and turn on the following syncs:
  * [ ] Member
  * [ ] Sales
  * [ ] Member Recurring Service
  * [ ] Recurring Service Plan
  * [ ] Event Attendance
* [ ] [Copy and paste](https://docs.middle.app/middle-docs/building-workflows/create-or-copy-a-workflow) the workflow templates in this article
* [ ] Create [triggers](https://docs.middle.app/middle-docs/building-workflows/workflow-triggers) to automate your workflows

## Middle workflows

### Event Trigger

Central to the integration is an introductory event, such as a new member workout or free a introductory session, that introduces members to the gym’s training programs. Middle workflows identify these events based on the event name. When the event is booked, Middle either updates an existing Deal to a Booked stage or creates a new Deal with a Booked status. Subsequent changes to the event status, such as an attendance, cancellation, or no-show, automatically advance the Deal through the pipeline.

#### Event status normalization

ABC Ignite events have two statuses: a class or appointment status and a status for the attendee. Middle’s integration takes these two statuses into account and compensates for when events are not marked correctly by the trainer. The four normalized values for an intro event are Attended, Canceled, No Show, and Pending. This status is passed directly to the HubSpot deal to help power the pipeline stage and communications.

#### Default event normalization

Middle's suggested event status normalization is as follows (this may differ based on your gym's operations):

| Attended | If attendedStatus = “Attended”                                                    |
| -------- | --------------------------------------------------------------------------------- |
| Canceled | If attendedStatus = “Canceled”                                                    |
| No Show  | If eventTimestamp is in the past and attendedStatus != “Attended” or “Canceled”   |
| Pending  | If eventTimestamp is in the future and attendedStatus != “Attended” or “Canceled” |

{% hint style="info" %}
This logic can be customized in the workflow template in step 3 in the probable\_status definition.
{% endhint %}

#### Multiple bookings

If a member has more than one intro event in the past 30 days, such as when they need to rebook after a cancellation, Middle deciphers which event should be given priority based on the normalised event status and event timestamp. Preference is first given to the normalised event status in the following order:

1. Attended
2. Pending
3. Canceled & No Show

From there, preference is given to the earliest event. By determining which event should receive priority, Middle can reconcile an array of event data into a single Deal.

## Package Purchased

When Middle sees a training package purchase, a checkbox flag is set on the Deal to indicate that the member purchased a deal-eligible package. The setting of this flag should automatically mark the Deal as closed-won.

The amount of the Deal is set by the ABC Ignite package total.

### Recurring services versus paid-in-full packages

Training packages can be configured as recurring services or paid-in-full packages in ABC Ignite. It’s important to understand if only one or both of these package types should influence whether a Deal is closed-won. Recurring services can be pulled from ABC Ignite’s [members/recurringservices](https://abcfinancial.3scale.net/docs/members#!/Members/getRecurringServicesUsingGET) and [transactions/pos](https://abcfinancial.3scale.net/docs/clubs#!/Clubs/getPOSTransactionsUsingGET) endpoints. Paid-in-full package purchases show up in the [transactions/pos](https://abcfinancial.3scale.net/docs/clubs#!/Clubs/getPOSTransactionsUsingGET) endpoint.

Filters can be applied to Middle workflows to include or exclude purchases and services.

### Templates

#### Event attendance

```json
{"name": "PT Acquisition - ABC Event Attendance to Deal", "description": "When a deal is closed-loss, Hubspot needs to clear the GUID", "steps": [{"step_number": 0, "description": "Begin workflow on an ABC Ignite Event Attendance", "node_type": "root_node", "root": {"next_step_number": 1, "input_attribute": {"type": "object", "is_array": false, "children": [{"key": "input_record", "display_name": "Event Attendance", "type": "foreign_key", "is_array": false, "foreign_key_entity_uuid": "42c3702b-5810-4762-ac1c-0ee24143b896"}]}}}, {"step_number": 1, "description": "Workflow configuration", "node_type": "set_variable_node", "setvariable": {"next_step_number": 2, "output_type": {"type": "object", "is_array": false, "children": [{"key": "create_deal", "display_name": "Should a deal be created if one is not found?", "type": "boolean", "is_array": false}, {"key": "deal_name", "display_name": "Deal Name", "description": "What should the deal be named if Middle creates a new deal?", "type": "string", "is_array": false}, {"key": "eligible_event_names", "display_name": "Eligible Event Names (one per line)", "description": "Not case sensitive", "type": "string", "is_array": false}, {"key": "pipeline", "display_name": "Pipeline", "description": "The pipeline ID", "type": "string", "is_array": false}]}, "output_np": {"type": "construct_object", "expression": "{}", "params": [{"key": "eligible_event_names", "type": "template"}, {"key": "pipeline", "type": "template"}, {"key": "create_deal", "type": "boolean", "expression": "BOOLEAN_IS_FALSE"}, {"key": "deal_name", "type": "template"}]}}}, {"step_number": 2, "description": "Is this an eligible event?", "node_type": "filter_node", "filter": {"filter_condition": {"type": "comparison", "expression": "STRING_IS_IN", "params": [{"type": "upper", "params": [{"type": "global_var_selector", "expression": "WORKFLOW:0", "props": ["input_record", "eventName"]}]}, {"type": "splitlines", "params": [{"type": "upper", "params": [{"type": "global_var_selector", "expression": "WORKFLOW:1", "props": ["eligible_event_names"]}]}]}]}, "next_step_number_on_true": 3}}, {"step_number": 3, "description": "Get all eligible events, and figure out their probable status.", "node_type": "set_variable_node", "setvariable": {"next_step_number": 4, "output_type": {"type": "object", "is_array": false, "children": [{"key": "events", "display_name": "All relevant events for this member", "type": "object", "is_array": true, "children": [{"key": "abc_attended_status", "display_name": "ABC attendedStatus", "description": "Attended, Pending, Did Not Attend", "type": "string", "is_array": false}, {"key": "abc_created_timestamp", "display_name": "ABC createdTimestamp", "type": "iso_datetime", "is_array": false}, {"key": "abc_event_timestamp", "display_name": "ABC eventTimestamp", "type": "iso_datetime", "is_array": false}, {"key": "abc_status", "display_name": "ABC status", "description": "Completed, Pending, Canceled-No Charge, Canceled-Charge", "type": "string", "is_array": false}, {"key": "event_key", "display_name": "Event Key", "type": "string", "is_array": false}, {"key": "probable_status", "display_name": "The most probable status, in reality, of this event", "description": "Options are: \"Attended\", \"Canceled\",  \"No Show\", \"Pending\"\n\nABC has some two fields representing data about the event's status, and some of it conflicts (for example, a small number of events have an \"Attended\" status but are also \"Canceled\", a contradiction). This makes passing data straight from ABC into Hubspot impossible. We interpret data on the event to collapse into one of these statuses.", "type": "string", "is_array": false}]}]}, "output_np": {"type": "construct_object", "expression": "{}", "params": [{"key": "events", "type": "construct_array", "params": [{"output_name": "Eligible Events", "type": "map_array", "params": [{"output_name": "Events", "type": "filter_array", "params": [{"type": "global_var_selector", "expression": "WORKFLOW:0", "props": ["input_record", "member", "Event Attendances"]}, {"type": "and", "params": [{"type": "comparison", "expression": "STRING_IS_IN", "params": [{"type": "upper", "params": [{"type": "parent_np_selector", "expression": "Events:value", "props": ["eventName"]}]}, {"type": "splitlines", "params": [{"type": "upper", "params": [{"type": "global_var_selector", "expression": "WORKFLOW:1", "props": ["eligible_event_names"]}]}]}]}, {"type": "comparison", "expression": "DATETIMELIKE_AFTER", "params": [{"type": "parent_np_selector", "expression": "Events:value", "props": ["eventTimestamp"]}, {"type": "math_expression", "expression": "{}-{}", "params": [{"type": "global_var_selector", "expression": "NOW_REAL"}, {"type": "construct_timedelta", "expression": "days", "params": [{"type": "math_expression", "expression": "30"}]}]}]}]}]}, {"type": "construct_object", "params": [{"key": "probable_status", "type": "switch", "params": [{"type": "comparison", "expression": "STRING_EQUALS", "params": [{"type": "parent_np_selector", "expression": "Eligible Events:value", "props": ["status"]}, {"type": "template", "expression": "Completed"}]}, {"type": "template", "expression": "Attended"}, {"type": "comparison", "expression": "STRING_INCLUDES", "params": [{"type": "parent_np_selector", "expression": "Eligible Events:value", "props": ["status"]}, {"type": "template", "expression": "Canceled"}]}, {"type": "template", "expression": "Canceled"}, {"type": "comparison", "expression": "DATETIMELIKE_BEFORE", "params": [{"type": "parent_np_selector", "expression": "Eligible Events:value", "props": ["eventTimestamp"]}, {"type": "global_var_selector", "expression": "NOW_REAL"}]}, {"type": "template", "expression": "No Show"}, {"type": "template", "expression": "Pending"}]}, {"key": "abc_created_timestamp", "type": "parent_np_selector", "expression": "Eligible Events:value", "props": ["createdTimestamp"]}, {"key": "abc_event_timestamp", "type": "parent_np_selector", "expression": "Eligible Events:value", "props": ["eventTimestamp"]}, {"key": "abc_attended_status", "type": "parent_np_selector", "expression": "Eligible Events:value", "props": ["attendedStatus"]}, {"key": "abc_status", "type": "parent_np_selector", "expression": "Eligible Events:value", "props": ["status"]}, {"key": "event_key", "type": "template", "expression": "{}", "params": [{"type": "parent_np_selector", "expression": "Eligible Events:value", "props": ["eventAttendanceKey"]}]}]}]}]}]}}}, {"step_number": 4, "description": "Sort events to find the one most relevant to the Deal.\n\nWe consider the canonical event attendance to be the one with this priority: Attended > Pending > Canceled = No-Show. Within the same priority, we then order by the event's date, sorting in order of past to future, so that the next pending event is prioritized. This means that the earliest cancellation in the past 30 days would be picked if they have multiple cancellations.\n\nSince older dates are lexicographically last, this list is organized in reverse priority. (The most relevant is last.) We reverse the list in order to use the FIRST node parameter in the next step of this workflow.  ", "node_type": "set_variable_node", "setvariable": {"next_step_number": 5, "output_type": {"type": "object", "is_array": false, "children": [{"key": "events_sorted", "display_name": "Sorted Events", "type": "object", "is_array": true, "children": [{"key": "abc_attended_status", "display_name": "ABC attendedStatus", "description": "Attended, Pending, Did Not Attend", "type": "string", "is_array": false}, {"key": "abc_created_timestamp", "display_name": "ABC createdTimestamp", "type": "iso_datetime", "is_array": false}, {"key": "abc_event_timestamp", "display_name": "ABC eventTimestamp", "type": "iso_datetime", "is_array": false}, {"key": "abc_status", "display_name": "ABC status", "description": "Completed, Pending, Canceled-No Charge, Canceled-Charge", "type": "string", "is_array": false}, {"key": "event_key", "display_name": "Event Key", "type": "string", "is_array": false}, {"key": "probable_status", "display_name": "The most probable status, in reality, of this event", "description": "Options are: \"Attended\", \"Canceled\",  \"No Show\", \"Pending\"\n\nABC has some two fields representing data about the event's status, and some of it conflicts (for example, a small number of events have an \"Attended\" status but are also \"Canceled\", a seeming contradiction). This makes passing data straight from ABC into Hubspot impossible. We interpret data on the event to collapse into one of these statuses.", "type": "string", "is_array": false}]}]}, "output_np": {"type": "construct_object", "params": [{"key": "events_sorted", "type": "sort_array", "params": [{"type": "global_var_selector", "expression": "WORKFLOW:3", "props": ["events"]}, {"type": "boolean", "expression": "BOOLEAN_IS_FALSE"}, {"type": "template", "expression": "{}:{}", "params": [{"type": "switch", "params": [{"type": "comparison", "expression": "STRING_EQUALS", "params": [{"type": "parent_np_selector", "expression": "None:value", "props": ["probable_status"]}, {"type": "template", "expression": "Attended"}]}, {"type": "math_expression", "expression": "1"}, {"type": "comparison", "expression": "STRING_EQUALS", "params": [{"type": "parent_np_selector", "expression": "None:value", "props": ["probable_status"]}, {"type": "template", "expression": "Pending"}]}, {"type": "math_expression", "expression": "2"}, {"type": "math_expression", "expression": "3"}]}, {"type": "parent_np_selector", "expression": "None:value", "props": ["abc_event_timestamp"]}]}]}]}}}, {"step_number": 5, "description": "Grab the first event from our sorted list; This should be the one we pass on to Hubspot", "node_type": "set_variable_node", "setvariable": {"next_step_number": 6, "output_type": {"type": "object", "is_array": false, "children": [{"key": "abc_attended_status", "display_name": "ABC attendedStatus", "description": "Attended, Pending, Did Not Attend", "type": "string", "is_array": false}, {"key": "abc_created_timestamp", "display_name": "ABC createdTimestamp", "type": "iso_datetime", "is_array": false}, {"key": "abc_event_timestamp", "display_name": "ABC eventTimestamp", "type": "iso_datetime", "is_array": false}, {"key": "abc_status", "display_name": "ABC status", "description": "Completed, Pending, Canceled-No Charge, Canceled-Charge", "type": "string", "is_array": false}, {"key": "event_key", "display_name": "Event Key", "type": "string", "is_array": false}, {"key": "probable_status", "display_name": "The most probable status, in reality, of this event", "description": "Options are: \"Attended\", \"Canceled\",  \"No Show\", \"Pending\"\n\nABC has some two fields representing data about the event's status, and some of it conflicts (for example, a small number of events have an \"Attended\" status but are also \"Canceled\", a seeming contradiction). This makes passing data straight from ABC into Hubspot impossible. We interpret data on the event to collapse into one of these statuses.", "type": "string", "is_array": false}]}, "output_np": {"type": "construct_object", "expression": "{}", "params": [{"key": "probable_status", "type": "first", "props": ["probable_status"], "params": [{"type": "global_var_selector", "expression": "WORKFLOW:4", "props": ["events_sorted"]}]}, {"key": "abc_created_timestamp", "type": "first", "props": ["abc_created_timestamp"], "params": [{"type": "global_var_selector", "expression": "WORKFLOW:4", "props": ["events_sorted"]}]}, {"key": "abc_event_timestamp", "type": "first", "props": ["abc_event_timestamp"], "params": [{"type": "global_var_selector", "expression": "WORKFLOW:4", "props": ["events_sorted"]}]}, {"key": "abc_attended_status", "type": "first", "props": ["abc_attended_status"], "params": [{"type": "global_var_selector", "expression": "WORKFLOW:4", "props": ["events_sorted"]}]}, {"key": "abc_status", "type": "first", "props": ["abc_status"], "params": [{"type": "global_var_selector", "expression": "WORKFLOW:4", "props": ["events_sorted"]}]}, {"key": "event_key", "type": "first", "props": ["event_key"], "params": [{"type": "global_var_selector", "expression": "WORKFLOW:4", "props": ["events_sorted"]}]}]}}}, {"step_number": 6, "description": "Update a deal or create a deal if no open deals were found and the user has configured this workflow to create new deals\n\nWe'll update the most recently created deal", "node_type": "action_node", "action": {"action_input": {"type": "construct_object", "expression": "{}", "params": [{"key": "idProperty", "type": "template", "expression": "abc_guid"}, {"key": "idPropertyValue", "type": "template", "expression": "{}:{}", "params": [{"key": "idPropertyValue", "type": "global_var_selector", "expression": "WORKFLOW:1", "props": ["pipeline"]}, {"key": "idPropertyValue", "type": "global_var_selector", "expression": "WORKFLOW:0", "props": ["input_record", "memberId"]}]}, {"key": "create_if_deal_not_found", "type": "global_var_selector", "expression": "WORKFLOW:1", "props": ["create_deal"]}, {"key": "raise_exception_if_deal_not_found", "type": "boolean", "expression": "BOOLEAN_IS_FALSE"}, {"key": "pipeline", "type": "global_var_selector", "expression": "WORKFLOW:1", "props": ["pipeline"]}, {"key": "dealname", "type": "global_var_selector", "expression": "WORKFLOW:1", "props": ["deal_name"]}, {"key": "key_value_strings", "type": "construct_array", "params": [{"type": "construct_object", "params": [{"key": "string_key", "type": "template", "expression": "abc_guid"}, {"key": "string_value", "type": "template", "expression": "{}:{}", "params": [{"type": "global_var_selector", "expression": "WORKFLOW:1", "props": ["pipeline"]}, {"type": "global_var_selector", "expression": "WORKFLOW:0", "props": ["input_record", "memberId"]}]}]}, {"type": "construct_object", "params": [{"key": "string_key", "type": "template", "expression": "member_id"}, {"key": "string_value", "type": "global_var_selector", "expression": "WORKFLOW:0", "props": ["input_record", "memberId"]}]}, {"type": "construct_object", "params": [{"key": "string_key", "type": "template", "expression": "intro_event_status"}, {"key": "string_value", "type": "global_var_selector", "expression": "WORKFLOW:5", "props": ["probable_status"]}]}, {"type": "construct_object", "params": [{"key": "string_key", "type": "template", "expression": "intro_session_key"}, {"key": "string_value", "type": "global_var_selector", "expression": "WORKFLOW:0", "props": ["input_record~ID"]}]}]}, {"key": "key_value_dts", "type": "construct_array", "params": [{"type": "construct_object", "params": [{"key": "is_date", "type": "boolean", "expression": "BOOLEAN_IS_FALSE"}, {"key": "dt_key", "type": "template", "expression": "intro_event_timestamp"}, {"key": "dt_value", "type": "global_var_selector", "expression": "WORKFLOW:5", "props": ["abc_event_timestamp"]}]}]}]}, "action_external_id": "f1146072-761e-4e73-9ea9-3f915a8a4636", "app_connection_id": 352}}]}
```

#### Member Recurring Service

```json
{"name": "PT Acquisition - ABC Recurring Member Service to Deal", "description": "", "steps": [{"step_number": 0, "description": "Begin workflow on an ABC Ignite Member Recurring Service record", "node_type": "root_node", "root": {"next_step_number": 1, "input_attribute": {"type": "object", "is_array": false, "children": [{"key": "input_record", "display_name": "Input record", "type": "foreign_key", "is_array": false, "foreign_key_entity_uuid": "10929d0d-c50f-4be0-812a-ff5f72ea6afa"}]}}}, {"step_number": 1, "description": "Set the list of eligible services and the pipeline ID", "node_type": "set_variable_node", "setvariable": {"next_step_number": 2, "output_type": {"type": "object", "is_array": false, "children": [{"key": "eligible_services", "display_name": "List of eligible recurring service plans", "description": "One per line. Not caps sensitive.", "type": "string", "is_array": false}, {"key": "pipeline_id", "display_name": "Pipeline ID", "description": "The internal pipeline ID in Hubspot", "type": "string", "is_array": false}]}, "output_np": {"type": "construct_object", "expression": "{}", "params": [{"key": "eligible_services", "type": "template"}, {"key": "pipeline_id", "type": "template"}]}}}, {"step_number": 2, "description": "Only include active Personal Training packages.", "node_type": "filter_node", "filter": {"filter_condition": {"type": "and", "params": [{"type": "comparison", "expression": "STRING_IS_IN", "params": [{"type": "lower", "params": [{"type": "global_var_selector", "expression": "WORKFLOW:0", "props": ["input_record", "serviceItem"]}]}, {"type": "splitlines", "params": [{"type": "lower", "params": [{"type": "global_var_selector", "expression": "WORKFLOW:1", "props": ["eligible_services"]}]}]}]}, {"type": "comparison", "expression": "STRING_EQUALS", "params": [{"type": "global_var_selector", "expression": "WORKFLOW:0", "props": ["input_record", "recurringServiceStatus"]}, {"type": "template", "expression": "active"}]}]}, "next_step_number_on_true": 3}}, {"step_number": 3, "description": "Set the \"PT Packaged Purchase\" flag to \"Yes\" for all deals with this member ID.", "node_type": "action_node", "action": {"action_input": {"type": "construct_object", "expression": "{}", "params": [{"key": "raise_exception_if_deal_not_found", "type": "boolean", "expression": "BOOLEAN_IS_FALSE"}, {"key": "create_if_deal_not_found", "type": "boolean", "expression": "BOOLEAN_IS_FALSE"}, {"key": "idProperty", "type": "template", "expression": "unique_guid"}, {"key": "idPropertyValue", "type": "template", "expression": "{}:{}", "params": [{"type": "global_var_selector", "expression": "WORKFLOW:1", "props": ["pipeline_id"]}, {"type": "global_var_selector", "expression": "WORKFLOW:0", "props": ["input_record", "memberId", "memberId"]}]}, {"key": "pipeline", "type": "global_var_selector", "expression": "WORKFLOW:1", "props": ["pipeline_id"]}, {"key": "amount", "type": "global_var_selector", "expression": "WORKFLOW:0", "props": ["input_record", "invoiceTotal"]}, {"key": "key_value_strings", "type": "construct_array", "params": [{"type": "construct_object", "params": [{"key": "string_key", "type": "template", "expression": "pt_package_purchased"}, {"key": "string_value", "type": "template", "expression": "yes"}]}]}]}, "action_external_id": "f1146072-761e-4e73-9ea9-3f915a8a4636", "app_connection_id": 352}}]}
```

#### POS Sale

```json
{"name": "PT Acquisition - POS Sale to Deal", "description": "", "steps": [{"step_number": 0, "description": "Begin on an ABC Ignite Sale", "node_type": "root_node", "root": {"next_step_number": 1, "input_attribute": {"type": "object", "is_array": false, "children": [{"key": "input_record", "display_name": "Sale", "type": "foreign_key", "is_array": false, "foreign_key_entity_uuid": "43dfecc5-ce4d-483a-a792-2e4ca82919ce"}]}}}, {"step_number": 1, "description": "Set the list of eligible purchases and the pipeline ID", "node_type": "set_variable_node", "setvariable": {"next_step_number": 2, "output_type": {"type": "object", "is_array": false, "children": [{"key": "eligible_services", "display_name": "List of eligible recurring service plans", "description": "One per line. Not caps sensitive.", "type": "string", "is_array": false}, {"key": "pipeline_id", "display_name": "Pipeline ID", "description": "The internal pipeline ID in Hubspot", "type": "string", "is_array": false}]}, "output_np": {"type": "construct_object", "expression": "{}", "params": [{"key": "eligible_services", "type": "template"}, {"key": "pipeline_id", "type": "template"}]}}}, {"step_number": 2, "description": "Only include sales of eligible package purchases", "node_type": "filter_node", "filter": {"filter_condition": {"type": "and", "params": [{"type": "comparison", "expression": "STRING_IS_IN", "params": [{"type": "lower", "params": [{"type": "global_var_selector", "expression": "WORKFLOW:0", "props": ["input_record", "name"]}]}, {"type": "splitlines", "params": [{"type": "lower", "params": [{"type": "global_var_selector", "expression": "WORKFLOW:1", "props": ["eligible_services"]}]}]}]}, {"type": "global_var_selector", "expression": "WORKFLOW:0", "props": ["input_record", "sale"]}]}, "next_step_number_on_true": 3}}, {"step_number": 3, "description": "Set the \"PT Packaged Purchase\" flag to \"Yes\" for all deals with this member ID.", "node_type": "action_node", "action": {"action_input": {"type": "construct_object", "expression": "{}", "params": [{"key": "raise_exception_if_deal_not_found", "type": "boolean", "expression": "BOOLEAN_IS_FALSE"}, {"key": "create_if_deal_not_found", "type": "boolean", "expression": "BOOLEAN_IS_FALSE"}, {"key": "idProperty", "type": "template", "expression": "unique_guid"}, {"key": "idPropertyValue", "type": "template", "expression": "{}:{}", "params": [{"type": "global_var_selector", "expression": "WORKFLOW:1", "props": ["pipeline_id"]}, {"type": "global_var_selector", "expression": "WORKFLOW:0", "props": ["input_record", "member", "memberId"]}]}, {"key": "pipeline", "type": "global_var_selector", "expression": "WORKFLOW:1", "props": ["pipeline_id"]}, {"key": "amount", "type": "global_var_selector", "expression": "WORKFLOW:0", "props": ["input_record", "subtotal"]}, {"key": "key_value_strings", "type": "construct_array", "params": [{"type": "construct_object", "params": [{"key": "string_key", "type": "template", "expression": "pt_package_purchased"}, {"key": "string_value", "type": "template", "expression": "yes"}]}]}]}, "action_external_id": "f1146072-761e-4e73-9ea9-3f915a8a4636", "app_connection_id": 352}}]}
```

## HubSpot Deal configuration

### Pipeline stages

Core to the integration are Deal pipeline stages. Below are the assumed pipeline stages:<br>

1. Not booked: An initial stage where a prospective training client is identified, but has not yet booked an introduction session
2. Booked: The prospect booked an introsession
3. No show: The prospect booked an intro session and didn’t show up
4. Missed: The prospect attended the intro session but has not yet converted to a training client
5. Closed Won: The prospect converted to a training client
6. Closed Lost: The prospect either fell off or communicated they are not interested

### Hubspot Deal Properties to Create

Below are the properties that should be created to power the pipeline stage, communications, troubleshooting, and idempotency:

#### Idempotency properties

* `abc_guide` (Unique single-line text): A primary identifier for the deal to keep duplicates from being created. The Middle workflow is set up to pass in a concatenation of pipeline ID and member ID: {pipeline}:{member\_id}.

#### Event properties

* `intro_event_status` (Dropdown): Normalized to Attended, Canceled, Pending, or No Show
* `intro_event_timestamp` (Datetime): It’s important to tailor communications to whether or not this timestamp is in the future of the past
* `intro_session_key` (Unique single-line text): To identify the preferred event (corresponding to the ABC Ignite event attendance key). This will help keep duplicate deals from being created if a deal is closed-lost and a Middle ABC Ignite Event workflow is unintentionally triggered.

#### Package purchase properties

* `pt_package_purchased` (Checkbox): A flag to indicate whether the member purchased a training package
* `pt_package_key` (Single-line text): To identify the data point that indicated a training package purchase (either the recurring service ID or POS purchase ID)

#### Member properties

* `member_id`: The primary identifier for the member. Used to associate the deal to the correct contact.

## HubSpot workflow configuration

Using Deal segments to trigger HubSpot workflows, you can automatically set deal stages based on the ABC Ignite data Middle sends to HubSpot.

{% hint style="info" %}
Since these types of deals can go back a stage, it’s important to configure workflows for re-enrollment.
{% endhint %}

### Intro Session - Booked

**Segment filter logic:** If the Intro event status is “Pending,” the Intro event timestamp is in the future, and PT Package Purchased? is no or unknown, then mark the pipeline stage as Booked.

### Intro Session - No Show:&#x20;

**Segment filter logic:** If the Intro event status is “No Show” or “Canceled,” or the Intro event timestamp is in the past and the Intro event status is “Pending,” and PT Package Purchased? is no or unknown, then mark the pipeline stage as No Show.

### Mark Deal as Missed:&#x20;

**Segment filter logic:** If the Intro event status is “Attended” and PT Package Purchased? is no or unknown, then mark the pipeline stage as Missed.

### Mark Deal as Closed-Won:

**Segment filter logic:** If PT Package Purchased? is yes, then mark the pipeline stage as Closed-Won.

### Clear the idempotency key if Closed-Lost:

If you want Middle to create new deals for existing members, then the GUID property will need to be cleared to allow for a new deal to be created in the future.

### Associate Deal using member ID:

A workflow will need to associate Deals to Contacts using a match on member ID.
