Make.com test project · built on sample data

CourseSync

Your scenario, built and running end to end on a synthetic student. When a course is purchased in MindBody, the automation reads the booking state (it never books sessions itself), matches the right HubSpot segment by date range, updates the contact, logs an audit row, alerts the VA when the rules call for it, and drafts the confirmation for a human to approve. Where a person should decide, it stops and asks.

Built for the specification you posted publicly on Make's Hire a Pro board, then updated to match the revised spec. No prior MindBody or StartIntegrate client build is claimed, so rather than describe work you cannot verify, here is your scenario running on invented data.

Updated July 8 to match the revised specification.

Why a working build instead of a portfolio link

You asked whether respondents have built with the StartIntegrate / MAXMEL MindBody connector, how the automation would avoid double-processing the same student, and how it would pick the right segment when the segment names are inconsistent. The most honest answer is the build itself. Everything below runs on invented sample data. There are no live keys and no calls to your MindBody or HubSpot account. Where this design earns its keep is the read-only booking check, the deduplication, and the date-range segment match, and each is called out inline.

The sample scenario

A parent buys a five session course in MindBody. Sessions are still booked by hand in MindBody; the automation only reads whether a session has been booked, then keeps HubSpot and the records in step. The hard parts are getting the timing right, not processing the same purchase twice, and matching the one correct segment out of a set of inconsistently named lists.

Student
Maya Chen
MindBody client
100024816
Course
HS Study Skills and Strategies Course
Delivery
In-Person
Booked at check
1 session, 2026-07-13
Override checkbox
Not set
Trigger

A purchase in MindBody starts the scenario

gateway:CustomWebHook · receives clientSale.created

Connector boundary The StartIntegrate / MAXMEL connector cannot trigger on a purchase. Its only triggers are Watch New Clients and Watch Updated Clients, and it has no sale event. So a purchase is caught by subscribing to the MindBody Webhooks API event clientSale.created and pointing it at a Make custom webhook. Knowing this boundary is the difference between dragging modules and understanding the MindBody API.

Before the bundle is trusted, the scenario verifies the X-Mindbody-Signature header (HMAC-SHA256 of the raw body with the subscription's messageSignatureKey).

// clientSale.created (sample payload, faithful to the documented schema) { "messageId": "ASReXAmM8JmZfLBb2M2zBcp7Sm5aQ", "eventId": "clientSale.created", "eventSchemaVersion": 1, "eventInstanceOriginationDateTime": "2026-07-03T14:12:05Z", "eventData": { "siteId": 123456, "clientId": "100024816", "saleId": 987654, "client": { "name": "Maya Chen", "email": "maya.chen@example.com" }, "items": [{ "id": 5501, "type": "Service", "name": "HS Study Skills and Strategies Course", "amountPaid": 495.00 }] } } // header X-Mindbody-Signature: sha256=6f1e... verified against the subscription key

One honest production note, verified against the real MindBody sandbox: the live sale event may not carry the client email. When it does not, a GET /client/clients lookup on the clientId resolves it before the HubSpot steps. The sample event here includes it, so module 2 reads it directly.

0

Read the manual-enrollment override first

hubspotcrm:MakeAPICall · POST /crm/v3/objects/contacts/search

Before anything else, the scenario reads the contact by email to pull one checkbox: Manual enrollment - skip automation. Staff tick it on the contact before the purchase, so there is no race. The same read also pulls the current courses-taken list (so step 2 can append rather than overwrite) and the contact id.

// override read from the contact override = if( manual_enrollment_skip_automation = "true"; "true"; "false" ) // checked -> skip ONLY the step 1 booking check; steps 2-6 still run // unchecked -> run step 1 (the wait and booking check below)

This is the escape hatch for the cases where staff already know the enrollment is correct and just want the records updated. It never skips steps 2 through 6.

1

Wait, then check whether a session is booked (read only)

util:FunctionSleep ×2 then http GET /client/clientvisits

Connector boundary The connector has no module that returns a client's visits, so the scenario bridges the gap with a raw Make an API Call to GET /public/v6/client/clientvisits. It reads booking state only. It does not book, add, or change anything in MindBody. Session booking stays fully manual, exactly as the revised spec asks.

Parents usually book their sessions a few minutes after buying, so the scenario waits about ten minutes before it looks (two chained Sleep modules, because a single Sleep maxes out at 300 seconds). When the override is set, the wait collapses to one second and the booking check is skipped.

GET /public/v6/client/clientvisits?clientId=100024816&startDate=2026-07-08&endDate=2026-11-05

Parents book out of order, so the question is not "is session one booked" but "is any session booked for this course and week". The forward window returns future bookings; the scenario keeps only those whose status is Booked.

// keep future bookings whose AppointmentStatus is "Booked" // (real-sandbox trap: the field is AppointmentStatus, not Status, // and Missed is TRUE on future bookings, so never count via Missed) bookingCount = length( Visits where AppointmentStatus = "Booked" ) // 1 booked session found -> StartDateTime 2026-07-13 -> carry that date forward
Result in this sample: 1 booked session on 2026-07-13. That date drives the segment match below.

Zero booked is a normal outcome, not a failure. If nothing is booked after the wait, the scenario emails the parent to ask for their preferred dates and then stops. Staff complete the booking by hand; the scenario does not re-trigger itself. For the AI Workshop, a single-session course, no session at purchase is the usual case, so that path is the expected one there.

Dedup

Do not process the same student twice

hubspotcrm:MakeAPICall · GET /crm/v3/lists/{listId}/memberships

A parent can book several sessions of one course in a single sitting, and each booking can fire the purchase flow. Only the first should run steps 2 through 6. So before those steps, the scenario checks whether the student is already a member of the target segment for this course and week. If they are, it stops.

// is the contact already in the target list? // yes -> STOP (an earlier booking in the same purchase already processed it) // no -> proceed to steps 2-6 already_member = contains( target_list.memberships; contactId )

Membership is read from a static (manual or snapshot) list through the HubSpot v3 Lists API, which is immediate rather than lag prone. This is what makes a repeat delivery of the same purchase a safe no-op.

Match

Pick the one right segment by date-range containment

hubspotcrm lists index then segment resolver (date-range match)

Your segment lists are named inconsistently today. A tolerant match is the whole problem. The scenario pulls the lists index, then keeps the list whose parsed date range contains the booked session's date and whose tokens match the course level and delivery. Here is the same set of lists the resolver sees, and which one wins for a session on 2026-07-13, HS level:

list 5012
7/6-7/17/2026 HS Class
match

5012 wins: its range 7/6 to 7/17/2026 contains 7/13, and the name carries the HS token. 4488 is a different course and its range ends 6/13, before 7/13. 5090 contains 7/13 by date, but its level token is MS, not HS. Containment plus the level and delivery tokens leaves exactly one list.

Make as orchestrator, not the brain Parsing inconsistent list names (different order, optional course tokens, mixed date formats) is fragile inside Make formulas, so that logic lives in a small resolver in code, not in the scenario. Make orchestrates; the resolver decides. If it returns zero or more than one candidate, the scenario never guesses: it flags a human and stops. The build documentation shows a fully in-Make alternative (lists search, an Iterator, a per-list containment filter, and an Array aggregator) for anyone who wants no external code at all.
2

Append the course to the courses-taken property

hubspotcrm:upsertAContact

The course name is appended to the multi-select courses-taken property, so every prior course is kept and this one is added (and not duplicated if it is already there). Only coursesync_ properties are written.

{ "coursesync_enrollment_status": "enrolled", "coursesync_course_name": "HS Study Skills and Strategies Course", "coursesync_courses_taken": "Prior Course A;HS Study Skills and Strategies Course", "coursesync_last_sync": "2026-07-03T14:22:07Z" } // courses_taken BEFORE: "Prior Course A" -> AFTER: the prior course is kept, this one appended
Coexists with Appiant Every property this scenario writes is prefixed coursesync_. It never writes email, first name, last name, or the MindBody identity fields your Appiant sync owns. Email is used only to find the contact, never to overwrite it. That keeps this automation and the existing MindBody to HubSpot sync out of each other's way.
3

Add the contact to the matched segment

hubspotcrm:addMembersToAList

The contact is added to the list the resolver matched (list 5012 here), using HubSpot's native Add Members to a List module. This is the current supported replacement for the older add-to-list module HubSpot deprecated in September 2025, and this membership is also what the dedup check reads on any later booking of the same course.

MODULE Add Members to a List   listId: 5012   recordIds: ["{{contact id}}"]

Needs the crm.lists read and write scopes on the HubSpot connection. A contacts-only connection reads and writes contacts fine but returns a 403 here, which halts the run, so the connection carries those scopes explicitly.

4

Append an audit row to a Google Sheet

google-sheets:addRow

One row per processed purchase, so there is a plain record outside the two systems. The revised spec adds a column: MB Enrollment, which reads Automated when a booking was found or Manual for the override and zero-booking paths.

// row appended to the Enrollments tab Timestamp 2026-07-03 14:22:07 Student Maya Chen Course HS Study Skills and Strategies Course Segment 7/6-7/17/2026 HS Class MB Enrollment Automated SaleId 987654
5

Alert the VA, only when the rule matches

google-email:sendAnEmail · with a filter

This step is conditional. The VA is alerted only for the HWC three-session expiration extension check, which applies to MS or HS Study Skills and Strategies, in-person, in the summer. College, ES, Remote, Writing, Growth Strategies, and the AI Workshop do not page the VA.

// VA alert fires only when ALL are true vaEligible = (MS or HS Study Skills) AND In-Person AND session month is 06, 07, or 08 // Maya: HS Study Skills, In-Person, session 2026-07-13 -> VA alerted

The VA recipient is a placeholder, pending the hire. The channel is easy to point at whatever the VA uses, including a Slack message or a task.

6

Draft the confirmation for a human to approve

hubspotcrm:MakeAPICall · Single-Send template

The confirmation is built from your HubSpot template, with the course name and segment merged in, but it is delivered to the admin as a draft for review rather than sent straight to the family. The subject is prefixed DRAFT COURSE CONF: so it is obvious in the inbox and a human approves the wording before the family sees it.

POST /marketing/v4/email/single-send   { emailId, message.to: admin, customProperties }

The other paths the scenario handles

The primary walk above is the everyday case. The revised spec is really about the paths where the automation must be careful, so the router covers each explicitly.

Zero booking after the wait

No session booked and no override set: email the parent to ask for their dates, then stop. Staff finish the booking by hand and the scenario does not re-trigger. This is the normal case for the single-session AI Workshop.

Already processed (dedup hit)

The student is already a member of the target segment, because an earlier booking in the same purchase already ran. The scenario stops before steps 2 through 6 so nothing is written twice.

Ambiguous segment (flag, never guess)

The resolver returns zero or more than one matching list. The scenario emails the admin with the details and stops. No records are changed until a human resolves it. Flagging a person beats guessing the wrong segment.

Manual override

The Manual enrollment - skip automation checkbox is set, so the wait and booking check are skipped, but steps 2 through 6 still run to keep the records current.

The twelve course and delivery combinations

The routing reads the level and delivery from the purchased item and covers every course you sell. Five courses run both in-person and remote (ten combinations); Growth Strategies and the AI Workshop are in-person only (two more), for twelve in total.

CourseLevelFamilySessionsDelivery
MS Study Skills and Strategies CourseMSSSS5In-Person, Remote
HS Study Skills and Strategies CourseHSSSS5In-Person, Remote
College Study Skills and Strategies CourseCollegeSSS5In-Person, Remote
MS Writing: Start to FinishMSWriting5In-Person, Remote
HS Writing: Start to FinishHSWriting5In-Person, Remote
Growth Strategies Study Skills Course (GM / ES, grades 4-5)ESSSS5In-Person only
AI to Improve Executive Functions WorkshopAIWWorkshop1In-Person only

The AI Workshop is a single-session course, which is why a zero-booking result there is the expected path rather than an error. Only MS and HS Study Skills, in-person, in the summer, page the VA.

Take the build with you

The importable Make blueprint and a full module by module runbook. The blueprint's MindBody steps use generic HTTP and a custom webhook so it imports into any Make account without the StartIntegrate connector installed. Each connector boundary is labelled where the native action swaps in once it is present.