{
  "name": "CourseSync: MindBody purchase to enrollment fill, HubSpot, Sheet, VA, confirmation",
  "flow": [
    {
      "id": 1,
      "module": "gateway:CustomWebHook",
      "version": 1,
      "parameters": {
        "maxResults": 1
      },
      "mapper": {},
      "metadata": {
        "designer": {
          "x": 0,
          "y": 0,
          "name": "MindBody webhook: clientSale.created"
        },
        "restore": {
          "parameters": {
            "hook": {
              "data": {
                "editable": "true"
              },
              "label": "MindBody clientSale.created"
            }
          }
        },
        "parameters": [
          {
            "name": "hook",
            "type": "hook:gateway-webhook",
            "label": "Webhook",
            "required": true
          },
          {
            "name": "maxResults",
            "type": "number",
            "label": "Maximum number of results"
          }
        ],
        "notes": "The connector cannot trigger on a purchase (its only triggers are Watch New Clients and Watch Updated Clients). Subscribe to the MindBody Webhooks API event clientSale.created and point it here. Verify X-Mindbody-Signature (HMAC-SHA256 of the raw body with the subscription messageSignatureKey) before trusting the bundle. Payload envelope: {messageId, eventId, eventSchemaVersion, eventInstanceOriginationDateTime, eventData}."
      }
    },
    {
      "id": 2,
      "module": "http:ActionSendData",
      "version": 3,
      "parameters": {},
      "mapper": {
        "url": "https://api.mindbodyonline.com/public/v6/client/clientvisits",
        "method": "get",
        "headers": [
          {
            "name": "Api-Key",
            "value": "{{connection.mindbodyApiKey}}"
          },
          {
            "name": "SiteId",
            "value": "{{connection.mindbodySiteId}}"
          },
          {
            "name": "Authorization",
            "value": "Bearer {{connection.mindbodyStaffToken}}"
          },
          {
            "name": "Content-Type",
            "value": "application/json"
          }
        ],
        "qs": [
          {
            "name": "clientId",
            "value": "{{1.eventData.clientId}}"
          },
          {
            "name": "startDate",
            "value": "{{formatDate(addDays(now; -120); \"YYYY-MM-DD\")}}"
          },
          {
            "name": "endDate",
            "value": "{{formatDate(addDays(now; 120); \"YYYY-MM-DD\")}}"
          }
        ],
        "parseResponse": true
      },
      "metadata": {
        "designer": {
          "x": 300,
          "y": 0,
          "name": "MindBody GET /client/clientvisits (existing enrollments)"
        },
        "notes": "Returns the client's booked visits so we can compare against the course's required sessions. The StartIntegrate connector has no equivalent get-visits action, which is why a raw Make-an-API-Call / HTTP GET is the correct bridge here. Response array reachable as {{2.data.Visits}}, each with a ClassId."
      }
    },
    {
      "id": 3,
      "module": "builtin:BasicRouter",
      "version": 1,
      "parameters": {},
      "mapper": null,
      "metadata": {
        "designer": {
          "x": 600,
          "y": 450,
          "name": "Route: fill / record / notify"
        }
      },
      "routes": [
        {
          "flow": [
            {
              "id": 4,
              "module": "builtin:BasicFeeder",
              "version": 1,
              "parameters": {},
              "mapper": {
                "array": "{{split(\"30401,30402,30403,30404,30405\"; \",\")}}"
              },
              "metadata": {
                "designer": {
                  "x": 900,
                  "y": 0,
                  "name": "Iterator: required session ClassIds"
                },
                "notes": "Iterates the course's 5 required session ClassIds. The literal list is a placeholder for the demo; in production resolve it from the purchased course via GET /class/classes (or a course-to-session lookup) keyed on the sale's serviceId."
              }
            },
            {
              "id": 5,
              "module": "http:ActionSendData",
              "version": 3,
              "parameters": {},
              "filter": {
                "name": "Only sessions the student is not already in",
                "conditions": [
                  [
                    {
                      "a": "{{join(2.data.Visits[].ClassId; \",\")}}",
                      "b": "{{4.value}}",
                      "o": "text:notcontain"
                    }
                  ]
                ]
              },
              "mapper": {
                "url": "https://api.mindbodyonline.com/public/v6/class/addclienttoclass",
                "method": "post",
                "headers": [
                  {
                    "name": "Api-Key",
                    "value": "{{connection.mindbodyApiKey}}"
                  },
                  {
                    "name": "SiteId",
                    "value": "{{connection.mindbodySiteId}}"
                  },
                  {
                    "name": "Authorization",
                    "value": "Bearer {{connection.mindbodyStaffToken}}"
                  },
                  {
                    "name": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "bodyType": "raw",
                "contentType": "application/json",
                "data": "{\"ClientId\":\"{{1.eventData.clientId}}\",\"ClassId\":{{4.value}},\"RequirePayment\":false,\"SendEmail\":false,\"Test\":false}",
                "parseResponse": true
              },
              "metadata": {
                "designer": {
                  "x": 1200,
                  "y": 0,
                  "name": "MindBody POST /class/addclienttoclass (missing only)"
                },
                "notes": "SWAP FOR STARTINTEGRATE: Add a Client to a Class. The filter passes a session only when the existing-visits set does not already contain its ClassId, so sessions 1 and 2 are skipped and only 3, 4, 5 are added."
              }
            }
          ]
        },
        {
          "flow": [
            {
              "id": 6,
              "module": "hubspotcrm:createOrUpdateAContact",
              "version": 2,
              "parameters": {
                "__IMTCONN__": "<HUBSPOT_CONNECTION_ID>"
              },
              "mapper": {
                "email": "{{1.eventData.client.email}}",
                "properties": {
                  "coursesync_enrollment_status": "enrolled_all_sessions",
                  "coursesync_last_sync": "{{now}}",
                  "coursesync_course_name": "{{1.eventData.items[].name}}",
                  "coursesync_sessions_added": "{{2.data.__ADDED_COUNT__}}"
                }
              },
              "metadata": {
                "designer": {
                  "x": 900,
                  "y": 300,
                  "name": "HubSpot: update contact (namespaced property)"
                },
                "notes": "Writes ONLY coursesync_-namespaced properties. It never touches email, firstname, lastname or the MindBody identity fields Appiant owns, so this coexists with the existing Appiant sync. email is used as the match key only. If clientSale.created does not carry the email, resolve it first with a GET /client/clients lookup on clientId."
              }
            },
            {
              "id": 7,
              "module": "hubspotcrm:makeApiCall",
              "version": 2,
              "parameters": {
                "__IMTCONN__": "<HUBSPOT_CONNECTION_ID>"
              },
              "mapper": {
                "url": "/crm/v3/lists/{{connection.coursesyncListId}}/memberships/add",
                "method": "PUT",
                "headers": [
                  {
                    "name": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "body": "[\"{{6.id}}\"]"
              },
              "metadata": {
                "designer": {
                  "x": 1200,
                  "y": 300,
                  "name": "HubSpot: add to segment (Lists v3 memberships/add)"
                },
                "notes": "Adds the contact record id to the correct MANUAL list via the current Lists v3 endpoint PUT /crm/v3/lists/{listId}/memberships/add. The legacy native Add-to-List module was deprecated in Sept 2025, so Make-an-API-Call to Lists v3 is the correct current path. Body is a JSON array of record ids."
              }
            },
            {
              "id": 8,
              "module": "google-sheets:addRow",
              "version": 2,
              "parameters": {
                "__IMTCONN__": "<GOOGLE_CONNECTION_ID>"
              },
              "mapper": {
                "from": "drive",
                "mode": "select",
                "spreadsheetId": "<SPREADSHEET_ID>",
                "sheetId": "Enrollments",
                "includesHeaders": true,
                "insertDataOption": "INSERT_ROWS",
                "valueInputOption": "USER_ENTERED",
                "values": {
                  "0": "{{now}}",
                  "1": "{{1.eventData.client.name}}",
                  "2": "{{1.eventData.items[].name}}",
                  "3": "{{2.data.__ADDED_COUNT__}} of 5 (missing sessions)",
                  "4": "{{1.eventData.saleId}}"
                }
              },
              "metadata": {
                "designer": {
                  "x": 1500,
                  "y": 300,
                  "name": "Google Sheets: append enrollment row"
                },
                "notes": "One audit row per purchase into the Enrollments tab."
              }
            }
          ]
        },
        {
          "flow": [
            {
              "id": 9,
              "module": "google-email:ActionSendEmail",
              "version": 1,
              "parameters": {
                "__IMTCONN__": "<GMAIL_CONNECTION_ID>"
              },
              "filter": {
                "name": "Only alert VA for mid-course joins",
                "conditions": [
                  [
                    {
                      "a": "{{length(2.data.Visits)}}",
                      "b": "0",
                      "o": "number:greater"
                    }
                  ]
                ]
              },
              "mapper": {
                "to": [
                  "va@thestudypro.example.com"
                ],
                "subject": "Catch-up needed: {{1.eventData.client.name}} joined mid-course",
                "html": "{{1.eventData.client.name}} bought {{1.eventData.items[].name}} after sessions had already started. Please send the catch-up packet and book a 1:1."
              },
              "metadata": {
                "designer": {
                  "x": 900,
                  "y": 600,
                  "name": "VA alert (conditional): mid-course join"
                },
                "notes": "Fires only when the student already had visits before this purchase (a mid-course join). Clean from-scratch enrollments skip the VA. Swap the recipient / channel (Slack, Trainual task) to fit the VA's workflow."
              }
            }
          ]
        },
        {
          "flow": [
            {
              "id": 10,
              "module": "hubspotcrm:makeApiCall",
              "version": 2,
              "parameters": {
                "__IMTCONN__": "<HUBSPOT_CONNECTION_ID>"
              },
              "mapper": {
                "url": "/marketing/v4/email/single-send",
                "method": "POST",
                "headers": [
                  {
                    "name": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "body": "{\"emailId\":{{connection.confirmationEmailId}},\"message\":{\"to\":\"{{1.eventData.client.email}}\"},\"customProperties\":{\"course_name\":\"{{1.eventData.items[].name}}\",\"sessions_added\":\"{{2.data.__ADDED_COUNT__}}\"}}"
              },
              "metadata": {
                "designer": {
                  "x": 900,
                  "y": 900,
                  "name": "HubSpot: confirmation email (single-send template)"
                },
                "notes": "Sends the confirmation from a HubSpot marketing email template via the Single-Send API POST /marketing/v4/email/single-send. emailId is the template id; customProperties merge into the template tokens."
              }
            }
          ]
        }
      ]
    }
  ],
  "metadata": {
    "instant": true,
    "version": 1,
    "scenario": {
      "roundtrips": 1,
      "maxErrors": 3,
      "autoCommit": true,
      "autoCommitTriggerLast": true,
      "sequential": false,
      "slots": null,
      "confidential": false,
      "dataloss": false,
      "dlq": false,
      "freshVariables": false
    },
    "designer": {
      "orphans": []
    },
    "zone": "eu2.make.com"
  }
}
