Gym Lead Follow-Up
System in n8n
A practical, implementation-ready guide to building automated lead capture, instant WhatsApp messaging, and CRM tracking for gyms — using n8n, Google Sheets, and the WhatsApp Cloud API. Build it once. It runs forever.
01Why Gyms Lose Leads
Most gyms are not losing leads because they offer a bad product. They're losing leads because nobody responds fast enough — and in fitness, speed wins.
- Owner checks WhatsApp 3 hours after the inquiry arrives
- Nobody logged the lead — it lives only in someone's memory
- No follow-up message after day one — lead goes cold
- Competitor down the street replies in 4 minutes and closes
- Owner has no idea how many leads came in this month
- Reception staff handles inquiries inconsistently
- Lead gets a WhatsApp message in under 60 seconds
- Every inquiry is logged to Google Sheets automatically
- Owner gets an instant alert with the lead's full details
- Follow-up messages run on a schedule, no manual work
- Pipeline is visible — every lead has a trackable status
- Nothing falls through the cracks, ever
02The Workflow
Five nodes. One clean path. A lead submits a form and within seconds they receive a WhatsApp message, their data is logged, the owner is alerted, and the CRM is updated.
| Node | Type | What it does |
|---|---|---|
| Form Trigger | n8n Form Trigger | Receives the form submission and provides all field data to downstream nodes |
| Google Sheets | Google Sheets → Append Row | Logs the lead with name, phone, goal, and status "New" into your spreadsheet CRM |
| HTTP Request #1 | HTTP Request | Calls the WhatsApp Cloud API to send a welcome message to the lead's phone number |
| HTTP Request #2 | HTTP Request | Calls the WhatsApp Cloud API again to send an alert to the gym owner with lead details |
| Google Sheets #2 | Google Sheets → Update Row | Updates the lead's row status from "New" to "Contacted" with a timestamp |
03Before You Start
Set up these four accounts before opening n8n. Trying to configure credentials mid-build slows everything down.
- n8n — Cloud (n8n.cloud) or self-hosted. Free cloud trial available.
- Google account — For Google Sheets. You'll connect it via OAuth in n8n.
- Meta Developer account — For WhatsApp Cloud API. Go to developers.facebook.com.
- Meta Business account — Required to create a WhatsApp Business App.
- One n8n workflow with 6 nodes
- One Google Sheet with 9 columns for lead CRM
- One WhatsApp Business App in Meta Developer portal
- Two message templates (or one, in testing mode)
- One Tally.so or n8n native form (your choice)
04Step-by-Step Build
Follow these steps in order. Each step builds on the previous one. After each step, run a test to verify the node is working before moving to the next.
In n8n, create a new workflow. Click the "+" button and search for "n8n Form Trigger". This node creates a hosted form at a URL like https://your-n8n.cloud/form/XXXX. No external form tool needed.
Add the following fields in the node configuration:
| Field Name | Type | Required | Notes |
|---|---|---|---|
| name | Text | Yes | Full name of the lead |
| phone | Text | Yes | Collect with country code: 91XXXXXXXXXX for India |
| goal | Dropdown | Yes | Options: Weight Loss / Muscle Gain / General Fitness / Other |
| message | Textarea | No | Any additional inquiry or question from the lead |
Add a Google Sheets node and connect it to the Form Trigger. Before mapping fields, first create your spreadsheet manually in Google Sheets with these exact column headers in row 1:
A: Name B: Phone C: Goal D: Message E: Status F: Timestamp G: Source H: ContactedAt I: Notes
Back in n8n, configure the Google Sheets node:
- Operation: Append Row
- Spreadsheet: Select the sheet you just created
- Sheet: Sheet1
Map the columns to form data. Use these expressions in the mapping:
| Column | n8n Expression |
|---|---|
| Name | {{ $json.name }} |
| Phone | {{ $json.phone }} |
| Goal | {{ $json.goal }} |
| Message | {{ $json.message }} |
| Status | New (hardcoded text) |
| Timestamp | {{ new Date().toISOString() }} |
| Source | Website Form (hardcoded) |
| ContactedAt | (leave blank — filled later) |
| Notes | (leave blank — filled manually) |
Connect your Google account via n8n credentials. In the Credentials dropdown, click "Create New" and follow the OAuth flow. You'll need to allow n8n access to Google Sheets.
Add an HTTP Request node. This node calls the Meta WhatsApp Cloud API directly — no special WhatsApp node needed. Configure it as follows:
| Setting | Value |
|---|---|
| Method | POST |
| URL | https://graph.facebook.com/v19.0/YOUR_PHONE_NUMBER_ID/messages |
| Authentication | Header Auth |
| Header Name | Authorization |
| Header Value | Bearer YOUR_ACCESS_TOKEN |
| Send Body | JSON / Raw |
| Content-Type | application/json |
For the request body, use a template message (required for first-contact messages — see Section 08 for details):
{
"messaging_product": "whatsapp",
"to": "{{ $('Form Trigger').item.json.phone }}",
"type": "template",
"template": {
"name": "hello_world",
"language": { "code": "en_US" }
}
}to phone number must be a verified test number added in your Meta developer portal. The expression {{ $json.phone }} will only work in production after your business is verified. During testing, hardcode your own number as the recipient. See Section 06 for the full explanation.Once your app is in production and you have an approved custom template, the body will look like this instead:
{
"messaging_product": "whatsapp",
"to": "{{ $('Form Trigger').item.json.phone }}",
"type": "template",
"template": {
"name": "gym_lead_welcome",
"language": { "code": "en" },
"components": [{
"type": "body",
"parameters": [
{ "type": "text", "text": "{{ $('Form Trigger').item.json.name }}" }
]
}]
}
}Add a second HTTP Request node. The configuration is identical to Step 3 — same URL, same auth. The only difference is the recipient and message content.
The owner's phone number is hardcoded — it never changes. The message body contains the lead's details pulled dynamically from the form submission.
{
"messaging_product": "whatsapp",
"to": "91OWNERPHONENUMBER",
"type": "text",
"text": {
"body": "🆕 New Lead\nName: {{ $('Form Trigger').item.json.name }}\nPhone: {{ $('Form Trigger').item.json.phone }}\nGoal: {{ $('Form Trigger').item.json.goal }}\nMessage: {{ $('Form Trigger').item.json.message }}\nTime: {{ new Date().toLocaleString('en-IN') }}"
}
}type: "text") only work if the recipient has messaged your WhatsApp Business number in the last 24 hours. For owner alerts where this window doesn't exist, you have two options: (1) Create an owner alert template in Meta and use it here, or (2) Have the owner send a test message to the business number first during setup to open a session. In production, option (1) is the right approach.After the lead WhatsApp node, add an IF node. This checks whether the API call succeeded before continuing. If the WhatsApp call fails silently, you still want the flow to handle it gracefully instead of crashing.
Configure the IF condition:
- Value 1:
{{ $json.messages[0].id }} - Operation: Is not empty
The True path continues to the owner message and status update. The False path should log an error row to a separate "Errors" tab in the same spreadsheet with the lead phone number and current timestamp so you can manually follow up later.
Add a final Google Sheets node with Operation: Update Row. This updates the lead's row after successfully sending the WhatsApp messages.
To find the correct row to update, use the row number returned by the Append Row step in Step 2. The Append node returns an updatedRange value like Sheet1!A5:I5. Extract the row number:
{{ parseInt($('Google Sheets').item.json.updatedRange.match(/\d+/g).pop()) }}Update these columns in the row:
- Status →
Contacted - ContactedAt →
{{ new Date().toISOString() }}
With all nodes connected, run a full end-to-end test:
- Open the form URL from the Form Trigger node
- Submit it with your own name and a verified test phone number
- In n8n, click "Execute Workflow" manually
- Watch each node run and check for green checkmarks
- Verify: row added to Sheet, WhatsApp received, status updated
If any node shows a red error, click it to see the error details. Most failures are caused by wrong credentials, incorrect phone number format, or unverified test recipients in Meta.
919876543210, not +919876543210. Formatting errors are one of the most common causes of failed API calls during testing.Once testing is complete, activate the workflow using the toggle in the top right of the n8n editor. This keeps the workflow running continuously — no manual execution needed.
For full production deployment with real customer phone numbers, complete these steps in Meta:
- Add a real WhatsApp Business phone number to your Meta app (not the test number)
- Submit your business for Meta verification — required for messaging real users
- Create and submit message templates for approval — one for the lead welcome, one for the owner alert
- Wait for approval — templates can take minutes to days depending on Meta's queue
- Update the workflow with the real phone number ID and production access token
to field becomes dynamic — it uses the actual phone number the lead typed in the form. This is the core difference. See Section 06 for the full breakdown of what changes between environments.05Node Breakdown
A detailed reference for every node in the workflow — what it does, what data it needs, common mistakes, and how to test it.
| Node | What it does | Data it uses | Common mistake | How to test |
|---|---|---|---|---|
| Form Trigger | Creates a hosted form. Fires the workflow when a form is submitted. Provides all form data to downstream nodes as JSON. | No input. Outputs: name, phone, goal, message |
Forgetting to click "Listen for test event" before building. Without at least one test submission, there's no data to map. | Open the form URL in a browser and submit a test entry. Check the node output for the submitted values. |
| Google Sheets (Append) | Adds a new row to the spreadsheet with all lead fields. Acts as the intake log for every inquiry. Returns the row number of the added row. | Form fields: name, phone, goal, message. Also writes hardcoded values: Status="New", Source, Timestamp | Column mapping mismatch — if your sheet headers don't exactly match what n8n expects, data goes into wrong columns or triggers an error. | After mapping, click "Test step". Open the sheet and verify a new row appeared with all correct data. |
| HTTP Request #1 (Lead) | POSTs to the Meta WhatsApp Cloud API. Sends the welcome message to the lead's phone number using the approved template. | Phone from form: {{ $json.phone }}. Access token and Phone Number ID from Meta credentials. |
Wrong phone format (must be international without +). Unverified test recipient in demo mode. Wrong template name. | Use your own number as the recipient in test mode. Check the API response for an id field in the messages array. |
| HTTP Request #2 (Owner) | POSTs to the same Meta API. Sends a new-lead alert to the gym owner's hardcoded number with the lead's name, phone, and goal. | Hardcoded owner phone. Dynamic lead data from form: name, phone, goal, message. | Using free-form text type without an active 24h session window. Owner number not in correct format. | Send from your own test number. Confirm the message arrives on the owner's device with the correct lead details. |
| IF Node | Checks whether the WhatsApp API returned a success response. Routes to either the update path (success) or error logging (failure). | Response from HTTP Request #1: {{ $json.messages[0].id }} |
Checking the wrong field. The API always returns 200 status even on soft failures — you need to check for the message ID specifically. | Temporarily set the condition to always-false and verify the error path runs correctly. Then restore the real condition. |
| Google Sheets (Update) | Finds the lead's row using the row number from the Append step and updates the Status and ContactedAt columns. | Row number from Append step. Sets: Status="Contacted", ContactedAt=now | Row number extraction failing. The expression to parse the row number from updatedRange must be correct — test it in the expression editor first. |
After a full test run, check the sheet row. Status should change from "New" to "Contacted" and ContactedAt should be populated. |
06Demo Mode vs Production
This is the most misunderstood part of setting up WhatsApp with Meta. Read this carefully before testing — it will save significant debugging time.
- Meta gives you a temporary test phone number (not your real number)
- You can only message verified test recipients — numbers you manually add in the Meta developer portal
- All messages — both "lead" and "owner" — go to whichever number you added as a test recipient
- If you add only your own number, both messages arrive on your phone — this is expected, not a bug
- The
tofield in your API call must match a verified test number, otherwise the call fails with a "non-verified recipient" error - Template options are limited — usually only the default
hello_worldtemplate is available - Access token expires regularly — you must refresh it from the Meta portal
- You use a real WhatsApp Business phone number registered in your Meta app
- The lead's phone from the form becomes the dynamic recipient —
{{ $json.phone }} - The owner's phone is hardcoded and receives its own message separately — they go to completely different people
- First-contact messages (to new leads) still require approved message templates — Meta requires this
- After the lead replies, free-form text messages work for the next 24 hours
- The access token should be a permanent system user token, not the temporary developer token
- Business must be verified by Meta before messaging arbitrary users
Common Demo Mistakes
| Mistake | What happens | Fix |
|---|---|---|
Using dynamic {{ $json.phone }} in demo | API returns "recipient not in allowed list" error | Hardcode a verified test number while in demo mode |
Using type: "text" for first message | API returns error — not in a session window | Use type: "template" with hello_world for initial messages |
| Expecting the "lead" and "owner" to be different numbers | Both messages arrive on your own phone | Expected in demo. In production they'll be separate recipients |
| Using expired access token | API returns 401 Unauthorized | Regenerate from Meta portal or set up a permanent system user token |
| Phone number with "+" prefix | API fails with invalid number format | Remove the "+" — use 919876543210 not +919876543210 |
07Data Mapping
The Google Sheet is your CRM. Every field exists for a reason. Understanding what each field does will help you build better automations on top of this foundation.
| Column | Set By | Type | Why it exists |
|---|---|---|---|
| Name | Form → n8n | Text | Basic identification. Used in WhatsApp message personalisation and in the owner alert. |
| Phone | Form → n8n | Text (with country code) | WhatsApp recipient address. Must include country code. Used to send messages and to match rows for updates. |
| Goal | Form → n8n | Dropdown value | Tells the gym what the lead is looking for. Useful for routing — weight loss leads get different follow-ups than bulk inquiries. |
| Message | Form → n8n | Text / Textarea | Optional freeform inquiry. Useful context for the owner when reviewing new leads. |
| Status | n8n (auto) / Manual | Dropdown | The CRM core. Tracks where each lead is in the pipeline. Values: New, Contacted, Interested, Joined, Lost, Follow-up Due. |
| Timestamp | n8n (auto) | ISO datetime | When the lead submitted the form. Used for sorting, reporting, and measuring response time. |
| Source | n8n (hardcoded) | Text | Where this lead came from. Hardcode "Website Form" initially. Later, you can extend this with "Instagram", "Walk-in", "Referral" etc. |
| ContactedAt | n8n (auto after WA sent) | ISO datetime | When the WhatsApp message was sent. Lets you measure average response time and prove the system works. |
| Notes | Manual (owner/staff) | Text | Freeform field for the gym team. "Visited on Tuesday", "Interested in 3-month plan", "Asked about parking" etc. |
ContactedAt and the node maps to Contacted_At, the update will silently fail or create a new column. Create the sheet first, name the headers exactly as shown, then set up the n8n mapping.08WhatsApp Setup
The WhatsApp integration is the most technically involved part of this workflow. This section explains how the Meta Cloud API works, what its rules are, and what your alternatives are if you want to start faster.
Meta WhatsApp Cloud API
This is the official API — operated by Meta, the company that owns WhatsApp. It is the only fully supported, policy-compliant way to send WhatsApp messages programmatically at scale.
Here's what you need to know:
POST https://graph.facebook.com/v19.0/{PHONE_NUMBER_ID}/messages
Headers:
Authorization: Bearer {ACCESS_TOKEN}
Content-Type: application/jsonTemplate Messages
When you contact someone for the first time — or after 24 hours since they last messaged you — you must use an approved message template. You cannot send a freeform text to a stranger. Templates are pre-written messages you submit to Meta for review. Once approved, you can send them to any opted-in user.
Example approved template (submitted in Meta's message template manager):
Hi {{1}}, thank you for reaching out to us!
We've received your inquiry and will contact you
shortly to discuss your fitness goals. 💪In the n8n API call, {{1}} is replaced at send time with the lead's name using the parameters array in the template body.
The 24-Hour Conversation Window
Once a user sends you a message (they initiate), a 24-hour window opens. During this window, you can send any freeform text messages — you don't need templates. Once 24 hours pass from their last message, the window closes and you're back to templates only.
This is why the owner alert in this workflow needs special handling — the owner may not have messaged the business number before, so there's no open window. The clean solution is to create a separate owner-alert template.
Alternative WhatsApp Providers
If you need to start faster, test without Meta approval, or want fewer restrictions during development, these alternatives exist. Each has trade-offs.
09Error Handling
WhatsApp APIs fail. Networks fail. Credentials expire. Without error handling, a failed API call kills the entire workflow and you never find out. This section makes your system production-grade.
What Can Go Wrong
- WhatsApp API returns an error (wrong phone format, expired token, non-verified recipient)
- Google Sheets API times out or hits rate limits
- n8n server restarts mid-execution
- Lead submits an invalid phone number (letters, wrong length)
- Meta's servers are temporarily unreachable
The IF Node Error Route
After the lead WhatsApp node, the IF condition checks for a message ID in the response. The False path — where no ID was returned — should write an error record to Google Sheets:
Sheet tab: "Errors"
Columns: Phone | Name | Timestamp | ErrorType | Notes
ErrorType value: "WhatsApp Failed"
Phone: {{ $('Form Trigger').item.json.phone }}
Name: {{ $('Form Trigger').item.json.name }}
Timestamp: {{ new Date().toISOString() }}Marking Failed Leads in the Sheet
If the WhatsApp fails, also update the lead's row status to Error — Manual follow-up needed using the same Update Row logic as the success path. This means the Errors tab catches it AND the lead's own row reflects the problem. When the owner opens the sheet and sorts by status, failures are immediately visible.
n8n Workflow Error Settings
In n8n's workflow settings (the gear icon in the editor), you can set a Error Workflow — a separate n8n workflow that triggers whenever any node in the main workflow fails. Use this as a backstop to notify you of any unexpected errors beyond what the IF node catches.
10Mini CRM Logic
The Status column in Google Sheets turns a flat log into an operational CRM. Every lead has a status that tells the gym team exactly what has happened and what needs to happen next.
| Status | Meaning | Set by | Next action |
|---|---|---|---|
| New | Lead submitted the form. No contact made yet. | n8n automatically on form submit | Workflow runs — WhatsApp sent. Status moves to Contacted. |
| Contacted | WhatsApp message sent successfully. | n8n automatically after WA success | Wait for reply. If no reply in 24h, follow-up message (manual or automated). |
| Interested | Lead replied positively. Actively considering joining. | Owner/staff manually | Book a trial visit or call. Move to Joined if they sign up. |
| Joined | Lead converted to a paying member. | Owner/staff manually | Trigger onboarding sequence. Add to member CRM. |
| Lost | Lead is not interested or went elsewhere. | Owner/staff manually | Archive. Optionally re-engage in 3–6 months with a promotion. |
| Follow-up Due | Lead went silent after initial contact. Needs a nudge. | Owner/staff manually or automated | Send Day 3 follow-up message. If still no reply, move to Lost. |
11Production Upgrades
Once the base system is live, these upgrades convert it from a notification tool into a full lead nurturing system. Each is a separate addition to the workflow.
Day 1 → "Any questions about our plans?"
Day 3 → "Trial slots still open this week."
Day 7 → Final offer + urgency
→ Send PRIORITY alert to owner
→ Flag row as "Hot Lead" in sheet
→ Trigger immediate follow-up
→ Gemini answers FAQ
→ Auto-reply sent back
→ Escalate if confidence low
→ Read sheet
→ Count: New / Contacted / Joined
→ "Conversion this week: 14.8%"
→ Wait 6 days
→ "Your trial ends tomorrow"
→ Include discount code
→ IF expiry = 7 days: reminder
→ IF expiry = today: urgent
→ IF expired + 3 days: win-back
12Selling This System
If you're building this for clients, here's how to position the value, who to target, and what to say.
Ideal Clients
- Independent gyms — not chains. Owners are hands-on and feel the pain of missed leads personally.
- Salons and spas — same problem, same system. Just swap the form fields and message templates.
- Physio and dental clinics — high value per patient, same urgency around response time.
- Real estate agents — inquiry volume is high, response speed is critical.
- Fitness coaches and yoga studios — small teams, no CRM, exactly this problem.
What to Say in Your Pitch
Cold DM Script
What You Deliver
| Deliverable | What it is | How long |
|---|---|---|
| Inquiry Form | n8n or Tally form with all required fields | 15 min |
| Google Sheets CRM | Pre-configured spreadsheet with headers, dropdown validation | 10 min |
| WhatsApp Automation | Two HTTP Request nodes — lead welcome + owner alert | 20 min |
| Error Handling | IF node + error log sheet | 10 min |
| Status Update Logic | Auto-update after messages sent | 10 min |
| Testing + Handoff | Full end-to-end test with dummy data | 10 min |
| Total build time | ~75 min per client |
13Launch Checklist
Before you hand this off to a client or call it live, run through every item on this list. Each box should be confirmed by an actual test run, not assumed.
Comment the keyword below on the reel and I'll send you the exportable n8n JSON, the Google Sheets template, and the message template copy — ready to import and use.