Build documentation · sample runbook
A module by module runbook for the MindBody purchase to enrollment scenario: what each module does, the field mappings, and how each step handles the things that go wrong. This is the sample of what full documentation with every build looks like.
This documents the blueprint built for the specification posted publicly on Make's Hire a Pro board. It runs on invented sample data. No prior StartIntegrate or MindBody client build is claimed. The MindBody steps are written with generic HTTP so the blueprint imports into any Make account without the connector installed; each connector boundary notes where the native StartIntegrate module swaps in.
| Connection | Used by | Notes |
|---|---|---|
| MindBody API | Steps trigger, 1 | API key or OAuth 2.0 with a staff user token. The webhook subscription is a separate MindBody Webhooks API setup. Three request headers on every call: Api-Key, SiteId, Authorization: Bearer. |
| HubSpot | Steps 2, 3, 6 | Private app token. Scopes needed: crm.objects.contacts.write, crm.lists.write, and marketing-email for the Single-Send API. Single-Send requires Marketing Hub Enterprise. |
| Google Sheets | Step 4 | Access to the target spreadsheet with an Enrollments tab. |
| Gmail or email | Step 5 | Any send-capable connection. Swap for Slack or a Trainual task if the VA prefers. |
Values to supply before the scenario runs: the MindBody SiteId, the HubSpot manual list id, the Google spreadsheet id, the HubSpot confirmation template id, and the VA recipient.
The StartIntegrate connector has no purchase trigger, so the scenario is fired by a MindBody webhook, not a connector poll.
The delivered envelope is { messageId, eventId, eventSchemaVersion, eventInstanceOriginationDateTime, eventData }. eventSchemaVersion is always 1. eventData carries siteId, clientId, clientUniqueId, saleId, and the purchased items.
| ID | Module | Purpose | Key mappings |
|---|---|---|---|
| 1 | gateway:CustomWebHook | Receive clientSale.created from MindBody. | parameters left empty; configure the webhook and signature after import |
| 2 | http:ActionSendData | GET the client's existing visits so the fill step knows what she already has. | GET /public/v6/client/clientvisits?clientId={{1.eventData.clientId}}; response at {{2.data.Visits}} |
| 3 | builtin:BasicRouter | Fan out to four independent routes: fill sessions, update records, alert VA, confirm. | no mapper; routes are not mutually exclusive |
| 4 | builtin:BasicFeeder | Iterate the course's five required session ClassIds. | array of ClassIds; in production resolved from the course via GET /class/classes |
| 5 | http:ActionSendData | POST add the student to a session, filtered to missing sessions only. | POST /public/v6/class/addclienttoclass; body ClientId, ClassId {{4.value}}; filter: existing set notcontain {{4.value}} |
| 6 | hubspotcrm:createOrUpdateAContact | Write the coursesync_ enrollment properties. | match on email; properties are coursesync_ only |
| 7 | hubspotcrm:makeApiCall | Add the contact to the correct manual list. | PUT /crm/v3/lists/{listId}/memberships/add; body ["{{6.id}}"] |
| 8 | google-sheets:addRow | Append one audit row per purchase. | Enrollments tab; timestamp, student, course, sessions added, sale id |
| 9 | google-email:ActionSendEmail | Conditionally alert the VA for a mid course join. | filter: length(existing visits) > 0 |
| 10 | hubspotcrm:makeApiCall | Send the confirmation from a HubSpot template. | POST /marketing/v4/email/single-send; emailId, message.to, customProperties |
The connector's only triggers are Watch New Clients and Watch Updated Clients. Neither fires on a sale. The correct pattern is a MindBody Webhooks API subscription to clientSale.created delivered to the custom webhook in module 1. If webhooks are not available on the account, the fallback is a scheduled poll of the Sale endpoint via a Make an API Call, storing the last seen sale id to avoid reprocessing.
The connector can add a client to a class but cannot return a client's existing visits. Module 2 fills that gap with GET /client/clientvisits. Module 5 then adds a session only when the existing set does not contain that session's ClassId. The filter is one condition: join(existing ClassIds) notcontain current ClassId. This is what makes re-runs and partial enrollments safe. A student already in sessions 1 and 2 is added to 3, 4, and 5, and never re-added to 1 or 2.
Your existing Appiant integration keeps MindBody and HubSpot contact records in sync. This scenario must not fight it. Two rules enforce that:
The result is a clean separation. Appiant owns identity. This scenario owns the enrollment state, and the two never write the same field.
| Situation | Handling |
|---|---|
| Signature does not match | Reject the bundle before any downstream module runs. A mismatch means the payload is not from MindBody. |
| Sale is not a course purchase | Filter the router's fill route on the item type or course id so unrelated sales do not trigger enrollment. |
| clientSale.created has no email | Resolve the email first with GET /client/clients on the clientId before the HubSpot steps. The sale payload carries the client id reliably; email is not guaranteed. |
| Session is full or waitlisted | addclienttoclass accepts a Waitlist flag. Decide per course whether to waitlist or skip, and surface the outcome to the audit row. |
| Student already in every session | The missing only filter blocks all adds. The rest of the scenario still records and confirms. No duplicate enrollments are created. |
| Re-delivery of the same webhook | MindBody may retry. Dedupe on messageId, or rely on the missing only filter, which makes a repeat run a no op for enrollments. |
| HubSpot list add returns a not found | Confirm the list id is the ILS id of a manual or snapshot list. The Lists v3 add endpoint requires that list type. |
| Transient API failure | Set the scenario's error handling to retry the HTTP and HubSpot modules. Route unrecoverable errors to a data store or a notification so nothing fails silently. |