The Closed-Loop Revenue System
| Item | v8.0 Issue | v8.1 Fix |
|---|---|---|
| Field count | Stated "95 fields" — overcounted | Corrected throughout: 76 custom fields. Verified: 8+16+8+19+13+7+5 = 76 ✓ |
| Zapier plan | Stated "$20/mo Starter" — wrong plan for scheduled Zaps | Professional ($49/mo) minimum. Starter/Core plans cannot run Zaps 12, 15, 17, 18, 22, 23, 24. |
| Zap 8 filter bug | snags_open compared as text "0" — Zap never cleared | Use: Number is less than 1 — not text match. Text "0" ≠ Number 0 in Zapier filters. |
| Zap 10 double-fire | No filter on first trigger — fires twice if field re-saved | Filter added: fire only if deposit_invoice_date is currently empty. |
| Duplicate logic order | Searched email before phone — creates duplicate on re-submit with new email | Search phone FIRST, then email. Same phone = same person, always. |
| Zap 4 tag trigger | Space before colon in tag name breaks GHL trigger | Exact tag format: Objection:Price — no space before colon. Test tag triggers with exact string. |
| Apple Mail open inflation | Not flagged — scores email opens from Apple prefetch as real engagement | Flag opens where user_agent contains "Apple". Score email clicks only — they are unambiguous. |
| Bland AI prerequisite | Mentioned but not enforced as a hard prerequisite | Pathway config (script + disposition map) MUST be complete before Zap 5 activates. Never activate blind. |
| PandaDoc/Stripe double-charge | Not explicitly separated — both can trigger payment | PandaDoc = PDF document record. Stripe = payment flow. Never use PandaDoc's built-in payment if Stripe is primary processor. |
| Automation alternatives | Zapier-only — no alternatives documented | Make (Integromat), n8n, and ActivePieces full equivalents added. See Phase 24 — Automation Alternatives. |
| Permit tracking | "File permit" task oversimplified — permit takes 2–8 weeks | Realistic note added: application ≠ issuance. Never promise start date that depends on permit issuance. |
| Zap 13 no-show loop | PM must clear site_visit_outcome or Zap fires repeatedly | Explicit requirement: PM updates site_visit_outcome after every site visit — blank field = Zap re-fires daily. |
lifecycle_stage = TOFU → engagement_status = NEW → intent_score = 0 → Opportunity created → Owner assigned → timestamp_created stored. Reporting starts here — not later.lifecycle_stage? Owner? Opportunity? Source? If anything missing → auto-fix. Duplicate check: if phone/email exists → merge, keep highest intent_score, keep latest activity. This prevents data fragmentation — the silent killer of CRM systems.first_response_timer starts at contact creation. When lead replies: first_response_time = NOW − created → engagement_status = ENGAGED → intent_score += 3. Response is not just action — it is measured behavior.intent_score and last_activity_timestamp: SMS reply (+3) · Email click (+2) · Page visit (+1) · Call activity (+3) · Booking created (+5) · Estimate viewed (+4). Score also feeds reporting in real-time: engagement rate, activity density, time-to-action.intent_score += 3 · Missed call → auto-SMS + callback task · Connected call → outcome tagging within 2min · Outbound no-answer → retry sequence. All calls tracked: response time, contact rate, call-to-book rate.lifecycle_stage update → entered_stage_timestamp set → time_in_stage reset. Logs: conversion progression, drop-off points, stage velocity. Pipeline is not visual — it is a data engine.intent_score += boost → instant response triggered. Tracking: reactivation rate, time-to-re-engage, revenue from recycled leads.| SMS reply | +3 |
| Email click | +2 |
| Email open | +1 ⚠ Flag opens where user_agent contains "Apple" — Apple Mail prefetches emails, inflating scores with fake opens. Score clicks only as reliable signal. |
| Page visit | +1 |
| Call connected | +3 |
| Booking created | +5 |
| Estimate viewed | +4 |
| Budget: Premium | +10 |
| Budget: Standard | +5 |
| Budget: Starter | +2 |
| Timeline: Urgent | +15 |
| Timeline: Near | +10 |
| Timeline: Long | +5 |
| Source: Referral | +8 bonus |
| Score 21–25 | BOFU · Path 1 · AI call <5min |
| Score 16–20 | BOFU · Path 2 · Rep call 4hr |
| Score 11–15 | MOFU · Path 3 · Education |
| Score 6–10 | MOFU · Path 4 · Long cycle |
| Score 0–5 | TOFU · Path 5 · Standard |
| intent_score ≥5 | → promote to BOFU |
| Inactivity 7d | → RECYCLE |
| Field | Type | Values | Written by |
|---|---|---|---|
| lifecycle_stage | Dropdown | TOFU / MOFU / BOFU / RECYCLE / CLIENT / PAST_CLIENT | System (auto) |
| engagement_status | Dropdown | NEW / ENGAGED / RESPONDING / STALLED / COLD / DNC | System (auto) |
| intent_score | Number | Running total — behavioral + form score. Resets on recycle. | System (auto) |
| first_response_time | Number | Minutes from contact creation to first reply | System (auto) |
| last_activity_timestamp | DateTime | Updated on every behavioral event | System (auto) |
| entered_stage_timestamp | DateTime | Set every time pipeline stage changes | System (auto) |
| time_in_stage | Number | Hours in current stage — drives stagnation detection | Calculated |
| recycle_count | Number | How many times this contact has been recycled | System (auto) |
const w = {sms_reply:3, email_click:2, email_open:1, page_visit:1, call_connected:3, booking:5, estimate_viewed:4};
const delta = w[inputData.event_type] || 0;
const newScore = parseInt(inputData.current_intent_score || 0) + delta;
const stage = newScore >= 5 ? 'BOFU' : newScore >= 3 ? 'MOFU' : 'TOFU';
output = [{newScore, stage, delta}];
| GHL | CRM hub · $97–297/mo · Day 1 non-negotiable. All contacts, pipelines, sequences, tasks. Single source of truth — every other tool writes back here. Starter → Pro at >1 rep. |
| Zapier | Professional ($49/mo) minimum. 24 Zaps. Starter/Core plans cannot run scheduled Zaps (12,15,17,18,22,23,24) or multi-step flows. See Phase 24 for Make/n8n/ActivePieces alternatives at lower cost. Do not start on a free or Starter plan — you'll rebuild everything when you upgrade. |
| Calendly | Bookings · Teams plan ($16/mo) for collective scheduling. Consultation event + one Site Visit event per estimator. |
| Bland AI | AI outbound calling · ~$0.09/min · Path 1 only. Pathway configuration (script + disposition map) MUST be complete before Zap 5 is activated. Never activate Zap 5 with an unconfigured pathway — it will call real leads with a blank script. Test on your own number minimum 10 times before go-live. |
| PandaDoc | E-sign · Business plan ($49/mo) for payment + CPQ. Estimates + contracts + invoices + change orders + welcome packets. |
| Stripe | Payments · 2.9%+$0.30 · payment.cleared is the ONLY Phase 3 trigger. Not payment.submitted. Webhooks via Zapier. |
| Google Sheets | Data layer · Free · Master Logs (8 tabs) + Territory Map + Materials DB + Schedule + Sub Directory + Compliance Calendar. |
| Looker Studio | Reporting · Free · Build LAST. 7 dashboards: Funnel, Marketing, Sales, Ops, Finance, AI, After-sales. |
| GA4 + GTM | Tracking · Full event taxonomy (11 events). UTM capture on all form fields. Server-side via CAPI. |
| Meta CAPI | Server-side conversions · Fire Lead on form submit + Purchase on deposit.cleared. Closes ad attribution loop. |
| CallRail | Call tracking · $45/mo · One tracking number per channel (Website / Google Ads / GMB / Facebook). Webhooks → GHL. |
| Xero / QuickBooks | Accounting · Sync via Zapier. Invoices, payments, reconciliation. Bridge: GHL invoice events → Xero. Critical Month 1. |
| Buildertrend | Project management + field crew. Phase updates → GHL field updates. Daily log → PM SMS content. Schedule delays → Slack. Month 1. Pro plan required for webhooks — Zaps 19–21 will not function on Basic plan. |
| CompanyCam | Photo documentation · $49/mo · 3+ estimators. Project milestones → GHL note with gallery link. Site visit photos auto-organized. |
| Slack | Internal alerts · Free OK · #high-value-leads / #manager-approvals / #project-alerts / #finance-alerts channels. |
| ZeroBounce | Email validation · API call in Zap 1 · Real-time — no invalid emails ever enter sequences. |
// 1. System fields: lifecycle_stage, engagement_status, intent_score, first_response_time, last_activity_timestamp
// 2. Marketing fields: utm_source through ad_creative_id (16 fields)
// 3. Phase 1 fields: lead_score, budget, timeline, path_assigned etc (8 fields)
// 4. Phase 2 fields: booking_sequence through loss_reason (7 fields)
// 5. Phase 3 fields: contract through final_payment_date (12 fields)
// 6. Finance fields: material_cost through change_order_3_amount (19 fields)
// 7. Scheduling + snag + survey + warranty fields (remaining)
lead_score → Zap errors on every run.Comfort Nurture · Home Value Nurture · Lifestyle Nurture · Protection Nurture
// Standard paths:
Urgent Nurture · Premium Education · Long Cycle · Standard Nurture
// Behavioral:
Price Objection · Social Proof · SMS Objection · Estimate Follow-Up
// Booking:
Site Visit Confirmation · Pre-Construction Welcome
// Post-sale:
Contract Win · Lost Deal Exit · 12-Month Passive Nurture · Past Client 24-Month
// Specialist:
Warranty Expiry · Review Follow-Up · Lost Price Track · Lost Timing Track
| Angle | Core fear/desire | Hook frame | LP headline style |
|---|---|---|---|
| Comfort | Home doesn't feel like home anymore | "The backyard you never use — what if you did?" | Transform how you feel about coming home |
| Home Value | Property not performing as investment | "Homeowners in [suburb] added $42K with this project" | Add $X–$Y to your property value before you list |
| Lifestyle | Not living the life they imagined in this home | "The outdoor space you planned when you bought the house" | Build the space you actually use — every single day |
| Protection | Weather / wear / neglect is a ticking clock | "Every season you wait costs more to fix" | Stop the damage before it becomes a $40K repair |
| Campaign type | Keywords / targeting | Match type | Budget split |
|---|---|---|---|
| Search — Brand | Company name + variations | Exact | 5% · protect from competitor bidding |
| Search — Service | "patio contractor [city]" · "deck builder near me" · "fence installation [suburb]" · "[service] company [city]" | Phrase + Exact | 60% · highest intent |
| Search — Problem | "fence falling down" · "deck needs replacing" · "outdoor renovation ideas" | Broad + negative list | 20% |
| GDN Retargeting | GA4 audience: LP visitors no convert | Display | 15% |
| Test cadence | 3 hooks per angle per quarter = 12 new creatives every 90 days |
| Variable order | 1st test: Hook text. 2nd test: Hero image. 3rd test: CTA button. 4th test: Offer. Test one variable at a time. |
| Win rule | CPL < target AND avg lead_score > 15 after 300+ impressions AND at least 10 leads → promote, scale budget 20% |
| Kill rule | CPL > 2× target after 500 impressions → kill. No exceptions. |
| Full-funnel rule | CPL alone is not the metric. Track CPL → avg lead_score → booking_rate → contract_rate. A cheap CPL from low-quality leads costs more than an expensive one from high-quality leads. |
| Tracking field | ad_creative_id passed via UTM content → stored on GHL contact → Looker "Creative Performance" report |
| Primary offer | Free 3D Design Concept OR Property Value Impact Report OR 10-Year Cost Comparison — one per angle. High-perceived-value deliverable, not a quote. |
| Secondary offer | "Book your design consultation — only [X] spots available in [month]." Capacity urgency is real and credible for construction. |
| Urgency (real) | Seasonal: "Book before [date] to guarantee [season] start." Pricing: "Lock in current pricing — materials increase Q[X]." Capacity: "We take [X] new projects/month." |
| Risk reversal | "If you don't love the design concept, you owe us nothing." Removes the single biggest barrier to form submission. |
| Delivery | Instant on form submit. Zap 1 fires lead magnet email within 2 minutes. Never "we'll get back to you" — deliver the offer immediately. |
// Update weekly. Sort by contract_rate not CPL.
| Parameter | Field in GHL | Values | Example |
|---|---|---|---|
| utm_source | utm_source | facebook / google / instagram / organic / referral / email | |
| utm_medium | utm_medium | cpc / cpm / email / social / call | cpc |
| utm_campaign | utm_campaign | prospecting_comfort / retargeting_warm / reengagement_lostdeals | prospecting_comfort |
| utm_content | ad_creative_id | creative_id (matches creative tracker sheet) | comfort_before_after_v3 |
| utm_term | utm_term | Google Ads keyword (auto-populated for search) | deck+builder+near+me |
| utm_angle | lead_angle | comfort / home-value / lifestyle / protection | comfort |
| first_touch | first_touch_source | Captured on first form submit — never overwritten | facebook/cpc/prospecting_comfort |
| last_touch | last_touch_source | Overwritten on every new form submit or re-entry | google/cpc/retargeting_warm |
| Event name | Trigger | Key parameters |
|---|---|---|
| page_view | All pages — GTM auto | page_path, page_title |
| lp_viewed | Landing pages only — GTM URL filter | lp_angle, utm_source, utm_campaign |
| form_started | First field interaction on form | form_name, lp_angle |
| form_submitted | Form submit (Zap 1 trigger) | budget, timeline, lead_score, utm_campaign |
| lead_scored | After Zap 1 Code step — server-side | lead_score, path_assigned, lifecycle_stage |
| booking_created | After Zap 3 fires | booking_date, assigned_rep, lead_score |
| estimate_sent | estimate_amount field updated | estimate_amount, estimate_option |
| contract_signed | Zap 7 fires (PandaDoc webhook) | contract_value, utm_source (first touch) |
| deposit_cleared | Stripe payment.cleared | deposit_amount, pm_assigned |
| project_complete | Zap 8 fires (final_payment_date set) | job_revenue, actual_margin_pct |
| referral_submitted | Referral form submit | referrer_id, referral_angle |
| Why CAPI matters | iOS14+ blocks ~40% of pixel events. CAPI sends conversions directly from your server — no browser required. Without it, Meta undercounts conversions and overspends. |
| Event: Lead | Fire on form_submitted. Include: email (hashed), phone (hashed), lead_score, utm_campaign, utm_content. This tells Meta the quality of the lead, not just that a form was submitted. |
| Event: Purchase | Fire on contract_signed (Zap 7). Value = contract_value. This closes the attribution loop — Meta now knows which ad generated a $40K contract. |
| Deduplication | Use event_id = GHL contact_id + event_name + timestamp. Without deduplication, browser pixel + CAPI will double-count every event. |
| Match rate target | Aim for >70% match rate in Meta Events Manager. Below 50% = add more PII parameters (first_name, last_name, city, ZIP). |
| GTM trigger | Meta Pixel event | Retargeting audience built |
|---|---|---|
| LP page load | ViewContent | LP visitors last 30d |
| Scroll 75% | Custom: scroll_75 | High-intent non-converters last 14d (hottest) |
| CTA button click | InitiateCheckout | Button clickers last 7d (closest to converting) |
| Video 50% watch | ViewContent (video) | Video watchers last 21d |
| Time on page 60s | Custom: engaged_reader | High-engagement visitors |
| Form submit | Lead | Form submitters last 180d (for suppression + re-engagement) |
window.dataLayer.push({
event: 'form_submitted',
lead_score: '{{lead_score}}', // from hidden field populated by URL param
budget: '{{budget}}',
timeline: '{{timeline}}',
lead_angle: '{{lead_angle}}',
utm_source: '{{utm_source}}',
utm_campaign: '{{utm_campaign}}',
utm_content: '{{utm_content}}'
});
| Setup | All categories listed · All services listed · All products (with prices) listed · 10+ photos minimum at launch · Q&A section seeded with 10 common questions (answered by you, not Google). |
| Weekly posts | Every completed project → GMB post with before/after photo + "[project type] just completed for a [suburb] homeowner." 150–300 words. Geotagged photos when possible. |
| Review response SOP | Every review (positive or negative) responded to within 48 hours. 5-star: "Thank you [name] — it was a pleasure working on your [project]." 1-star: Never defensive. Acknowledge → apologize → offer to make it right → take offline. |
| GHL integration | When project_status = complete → create task "POST TO GMB — {{first_name}} project — attach before/after photos from CompanyCam." Set gmb_posted = true when done. |
| Page structure | One page per [service] × [suburb]. URL: /deck-builder-[suburb]/ · Title: "Deck Builder in [Suburb] | [Company Name]" · 800+ words. Each page has its own form (same Zap 1 endpoint). |
| Priority order | Build pages for highest-revenue service first × highest-population suburbs first. 2 pages/week = 24 pages in 3 months. |
| Content formula | Intro (what you do in this suburb) → process (how you work) → gallery (projects in this suburb or nearby) → testimonials → pricing guide (ranges only) → CTA → FAQ (10 questions, keyword-rich). |
| Internal links | Every city/service page links to: the main service page, 2-3 related service pages, and the main contact/form page. |
| Video format | 60–90 seconds · Title: "[Project Type] Transformation in [Suburb] — Before and After" · Always include suburb in title and description. |
| Capture SOP | Estimator takes 60s walkthrough on phone at site visit (before). PM takes 60s walkthrough at completion (after). Sent to editor or assembled in CapCut in 20 minutes. |
| GHL integration | At site visit confirmed: task fires to estimator "Capture before walkthrough video." At project_status = complete: task fires to PM "Capture completion walkthrough video." Fields: video_captured (Boolean). |
| Description formula | First 2 lines: keyword-rich description of the project. Then: "We recently completed this [project] for a homeowner in [suburb]. [2-3 sentences about scope]. Ready to transform your outdoor space? Link to LP." |
| Day | Post type | Platform | Content |
|---|---|---|---|
| Monday | Project showcase | Instagram + Facebook | Before/after carousel. Tag suburb. Caption tells a short story. |
| Wednesday | Educational | Facebook + Instagram | "3 questions to ask before hiring a [contractor type]" OR "[project type] buying guide." Builds authority. |
| Friday | Social proof | All platforms | Screenshot or graphic of a 5-star review. Client quote. Always tag suburb. |
| SPF record | Add v=spf1 include:sendgrid.net include:mailgun.org ~all to DNS. Prevents spoofing. Required for inbox placement. |
| DKIM | Enable in GHL → Settings → Email Services. Provides cryptographic proof emails are from you. Google rejects unsigned emails. |
| DMARC | Start with p=none (monitoring only). Move to p=quarantine after 30 days clean data. Blocks spoofed emails sent in your domain's name. |
| Sending domain | Use a subdomain for sending: mail.yourcompany.com NOT yourcompany.com. If sending domain gets flagged, website domain stays clean. |
| Warm-up protocol | New sending account: Day 1–7 max 50 emails/day → Week 2 max 200/day → Week 3 max 500/day → Week 4+ normal volume. Never jump to bulk volume on a fresh domain. |
| Postmaster monitoring | Google Postmaster Tools + Microsoft SNDS. Check weekly. Spam rate alert: if >0.1% → pause sending and investigate immediately. |
| ZeroBounce API | Called in Zap 1 Step 0.5 — before contact is created. If result = invalid/catch-all/disposable → tag Status:InvalidEmail → do NOT add to email sequences → SMS only. |
| email_status field | Dropdown: active / cold / bounced / DNC / unsubscribed / invalid. Updated automatically by email events. |
| Quarterly clean | Export all contacts with email_status = cold (no open 60d) → run through ZeroBounce bulk verify → remove invalid addresses from GHL. |
| Month | Broadcast type | Segment | Subject line style |
|---|---|---|---|
| January | New year project planning | All leads in Nurturing | "Is this the year you finally do it?" |
| March | Spring booking push | All leads + past clients | "Spring slots are filling — here's what's left" |
| May | Capacity urgency | MOFU leads + stalled | "We have 2 project slots in June — want one?" |
| August | Fall booking push | All leads | "Book now — complete before the holidays" |
| October | Pre-winter protection | Protection angle leads | "Before the first freeze — here's what can't wait" |
| December | Lock in next year pricing | All leads + past clients | "2025 pricing locked if you book this month" |
| Monthly | Project showcase newsletter | All active contacts | "What we built in [month] + [tip]" |
| Engagement segments | Smart List A: Active (opened email last 30d). Smart List B: Warm (opened last 31–90d). Smart List C: Cold (no open 90d+). Only A+B receive broadcasts. C receives re-engagement SMS only. |
| Re-engagement flow | Cold 90d → SMS: "Still thinking about your project? Reply YES and we'll reach back out." YES → move to MOFU + rep task. No reply 7d → move to Passive Nurture. No reply 30d → tag Status:Cold. |
| Suppression list | Export DNC + bounced + unsubscribed monthly → import as suppression into any other email tools (Klaviyo, Mailchimp if used). Cross-platform suppression is legally required under CAN-SPAM/GDPR. |
| TCPA compliance | Explicit opt-in on form: "I agree to receive SMS messages from [Company]. Msg & data rates may apply. Reply STOP to opt out." Store sms_consent_timestamp. |
| Keyword responses | STOP → immediate unsubscribe + tag SMS:OptedOut. HELP → auto-reply with support number. YES → confirmation trigger in Zap. |
| Neighborhood SMS | After project completion (Zap 8): build Smart List of contacts within same ZIP ±2 codes. Broadcast SMS: "Just finished a [project_type] in your neighborhood — want to see the photos?" Links to GMB. |
| Budget: Premium | +10 pts · Path 1 or 2 |
| Budget: Standard | +5 pts |
| Budget: Starter | +2 pts |
| Budget: Unknown | 0 pts |
| Timeline: Urgent | +15 pts |
| Timeline: Near | +10 pts |
| Timeline: Long | +5 pts |
| Timeline: Researching | +2 pts |
| Source: Referral bonus | +8 pts added to base score |
| Path 1 · 21–25 | Priority:Highest · Senior rep · Bland AI <5min · Angle Nurture (same-day) · Urgent Nurture (D1) |
| Path 2 · 16–20 | High Priority · Rep call task 4hr · Angle Nurture + Urgent Nurture |
| Path 3 · 11–15 | Angle Nurture → Premium Education 12d |
| Path 4 · 6–10 | Angle Nurture → Long Cycle 21d → 12-Month Passive |
| Path 5 · 0–5 | Angle Nurture → Standard Nurture 30d → 12-Month Passive |
| Angle | lead_angle_opener value | First email subject | Rep call opener |
|---|---|---|---|
| comfort | making your home a place you never want to leave | "Your request about transforming how you feel at home" | "You mentioned you want your home to feel different — tell me what that looks like for you." |
| home-value | protecting and growing your biggest asset | "Your property value question — here's what the data says" | "You're thinking about this from an investment standpoint — smart. What's your timeline on the property?" |
| lifestyle | building the outdoor space you actually use | "The outdoor space you planned when you bought this house" | "You said you're not using your outdoor space the way you imagined — what does it look like right now?" |
| protection | making sure your home is ready for what comes next | "The issue you flagged — here's what we see most often" | "You reached out because something's not right — tell me what you're seeing." |
first_name last_name email phone
budget // Premium | Standard | Starter | Unknown
timeline // Urgent | Near | Long | Researching | Unknown
lead_angle // comfort | home-value | lifestyle | protection
zip_code // required for territory routing
project_type // deck | patio | fence | pergola | pool | landscaping | other
sms_consent // true | false — TCPA compliance
utm_source utm_medium utm_campaign utm_content
utm_source utm_medium utm_campaign ad_creative_id → {{utm_content}}
sms_opt_in → {{sms_consent}}
lifecycle_stage → TOFU
engagement_status → NEW
const b = {Premium:10, Standard:5, Starter:2};
const t = {Urgent:15, Near:10, Long:5, Researching:2};
score += b[inputData.budget] || 0;
score += t[inputData.timeline] || 0;
if (inputData.lead_angle === 'referral') score += 8;
const path = score>=21?'path1':score>=16?'path2':score>=11?'path3':score>=6?'path4':'path5';
const openers = {comfort:'making your home a place you never want to leave',
'home-value':'protecting and growing your biggest asset',
lifestyle:'building the outdoor space you actually use',
protection:'making sure your home is ready for what comes next'};
const seqMap = {path1:'Urgent Nurture',path2:'Urgent Nurture',path3:'Premium Education',path4:'Long Cycle',path5:'Standard Nurture'};
const angleSeq = inputData.lead_angle.charAt(0).toUpperCase()+inputData.lead_angle.slice(1)+' Nurture';
output = [{score, path, opener:openers[inputData.lead_angle]||'', seq:seqMap[path], angleSeq}];
path_assigned → {{path}}
intent_score → {{score}} // seeds the intent engine
lead_angle_opener → {{opener}}
first_touch_source → {{utm_source}}/{{utm_campaign}} // only if empty
last_touch_source → {{utm_source}}/{{utm_campaign}} // always overwrite
Pipeline Stage → Lead In
timestamp_created → {{now}}
// If sms_consent = true: SMS:Opted-In
// If sms_consent = false: SMS:OptedOut
{{angleSeq}} // e.g. "Comfort Nurture" — opens with angle language
// Sequence 2 (fires D1 — path-based):
{{seq}} // e.g. "Urgent Nurture" — path-specific timing
event_id → {{contact_id}}_lead_{{timestamp}} // dedup key
email → SHA256({{email}})
phone → SHA256({{phone}})
custom_data → {lead_score: {{score}}, path: {{path}}}
| Minute | Phase | What the rep does |
|---|---|---|
| 0:00–1:00 | Open with the angle | Use {{lead_angle_opener}} in first sentence. "I saw you were thinking about [angle opener] — tell me more about what's going on at the house." |
| 1:00–7:00 | Discovery — listen 80% | Questions: What does the space look like now? What needs to change? Who else uses this space? What's driven you to look at this now? Have you had anyone else look at it? What happened with that? |
| 7:00–12:00 | Pain + motivation depth | "If nothing changed in the next 12 months — what would that cost you?" (comfort/lifestyle leads). "What do you think this is doing to your property value right now?" (home-value leads). |
| 12:00–17:00 | Solution framing | Present the process — not the product. "Here's how we approach this..." Walk through site visit → design → estimate → timeline. Never name a price on this call. |
| 17:00–20:00 | Next step close | "Based on what you've shared, I'd like to have one of our design specialists visit the site. We have [day] or [day] available this week — which works better?" Offer 2 options only. Never ask "when's good for you?" |
booking_sequence → 1_consultation
Pipeline Stage → Appointment Scheduled
lifecycle_stage → BOFU
intent_score → {{current}} + 5 // booking is +5
engagement_status → ENGAGED
entered_stage_timestamp → {{now}}
📋 BRIEFING — {{first_name}} {{last_name}}
Score: {{lead_score}} | Path: {{path_assigned}} | Angle: {{lead_angle}}
Budget: {{budget}} | Timeline: {{timeline}} | Project: {{project_type}}
ZIP: {{zip_code}} | Source: {{utm_source}}/{{utm_campaign}}
🎯 OPEN WITH: "{{lead_angle_opener}}"
📌 CALL OBJECTIVES: Discovery → Pain depth → Site visit close
Due → {{booking_date}} − 1 hour
Note → See contact note for full briefing. Open with: "{{lead_angle_opener}}"
Score: {{lead_score}} | Budget: {{budget}} | Timeline: {{timeline}}
Booked: {{booking_date}} | Rep: {{assigned_rep}}
Source: {{utm_source}}/{{utm_campaign}}
| Phase | What the estimator must do |
|---|---|
| Before (24hr) | Read GHL contact note → know the angle, budget, project type, what was promised on the consultation call. Pull up any existing case studies matching their project type. |
| Before (1hr) | Confirm with client via SMS: "Just confirming I'll be there at [time]. Any gate codes or parking notes?" Reply = engagement confirmation. |
| On site — start | Take 60-second before walkthrough video on phone (for YouTube + CompanyCam). Reference the angle: "You mentioned [comfort/value/lifestyle/protection] — walk me through the space from that perspective." |
| On site — measure | All measurements documented in CompanyCam or Google Drive. Photo naming: YYYY-MM-DD_ClientName_Measurement_[location]. Every corner. Every obstacle. |
| On site — close | At the end: "I have everything I need to put together 3 options for you — I'll have those to you within 48 hours. Before I go — is there anything else about the space or the project that I should factor in?" |
| After (2hr) | Update site_visit_outcome in GHL. Upload photos to CompanyCam/Drive. Update measurements_folder URL. Start estimate. |
| Option | What it includes | Pricing | Purpose |
|---|---|---|---|
| A — Standard | Functional scope, standard materials, base warranty. Minimum acceptable quality for your brand. | Your minimum job size. Never discount from this. | Anchors the client upward. Most won't take this. |
| B — Premium | Full scope, upgraded materials, extended warranty, priority scheduling. This is the real recommendation. | +25–35% above Option A | The one you want them to choose. 70% will. |
| C — Luxury | Full scope + all upgrades + custom features + premium materials. Everything possible. | +50–70% above Option A | Makes Option B look reasonable. Some will take it. |
site_visit_date → {{event_start_time}}
booking_sequence → 2_site_visit_confirmed
site_visit_outcome after every visitsite_visit_outcome is never set (PM forgets), Zap 13 re-evaluates the same contact daily and fires the no-show SMS every day. Estimator must set site_visit_outcome to any non-empty value within 2 hours of every visit — even for successful visits. Add this to the Site Visit SOP as a mandatory after step.| Day | Action | Goal |
|---|---|---|
| D0 (send) | Walk through estimate in a video call or phone call. NEVER email and run. "Which of the 3 options resonates with you most?" | Get verbal commitment on option |
| D1 | Email: "Following up on the options I sent yesterday — did you have a chance to review?" CTA: Book a quick call to answer any questions. | Re-open conversation |
| D3 | Email: Case study of a project similar to theirs. "Here's a project we did in [nearby suburb] with a similar scope." Addresses implicit objection. | Build confidence |
| D7 | SMS: "Hi {{first_name}} — [rep_name] here. We have [month] slot availability opening soon. Happy to hold one for you — would that help?" | Urgency |
| D14 | Email: Final follow-up. "I want to make sure you have everything you need to make a decision. Is there a specific concern I haven't addressed?" | Surface the real objection |
| D21 | Move to Stalled. Rep task: "D21 stall — call {{first_name}} and ask what's holding it up." | Human decision point |
Estimate: ${{estimate_amount}} · Budget tag: {{budget}} · Project: {{project_type}}
Estimator: {{estimator_assigned}} · Rep: {{assigned_rep}}
GHL link: {{contact_url}}
Reply ✅ to approve · Reply ❌ to reject with note
Due → +24 hours
| Scope of work | Detailed — lists every included task. Prevents "I thought that was included" disputes. If it's not written, it wasn't agreed. |
| Material specifications | Brand · grade · colour · model number where applicable. Prevents substitution disputes. "Equivalent materials" is not acceptable language. |
| Payment schedule | Deposit (30–50%) at signing · Milestone (25–30%) at [specific stage] · Final (20–25%) at client walkthrough signoff. Each milestone described precisely. |
| Start date language | "Estimated start: [date range], subject to permit approval and material availability." Never commit to an exact start date. Weather + permit delays are outside your control. |
| Change order clause | "Any changes to scope must be documented in a written Change Order, signed by both parties, and paid before work proceeds on the changed scope." This sentence prevents 90% of scope creep disputes. |
| Warranty | Workmanship: [X] years from completion date. Materials: manufacturer's warranty (pass-through). Both warranties void if client modifies work without written consent. |
| Lien rights notice | Required in most jurisdictions. "Contractor reserves the right to file a mechanic's lien on the property if payment is not received per the payment schedule." Check local requirements. |
| Dispute resolution | Mediation first, then binding arbitration. Specifies jurisdiction. Prevents expensive litigation on small disputes. |
| HOA responsibility | "Client is responsible for obtaining all HOA approvals before work commences. Contractor not liable for delays or modifications required by HOA after work begins." |
| Sequence | Entry trigger | Length | Exit condition |
|---|---|---|---|
| Comfort Nurture | lead_angle = comfort + Zap 1 | 5 emails over 7d | Booking created OR 7d elapsed |
| Home Value Nurture | lead_angle = home-value + Zap 1 | 5 emails over 7d | Booking created OR 7d elapsed |
| Lifestyle Nurture | lead_angle = lifestyle + Zap 1 | 5 emails over 7d | Booking created OR 7d elapsed |
| Protection Nurture | lead_angle = protection + Zap 1 | 5 emails over 7d | Booking created OR 7d elapsed |
| Urgent Nurture | Path 1+2, after angle seq D1 | 3d, 4 emails + call task | Booking OR stall |
| Premium Education | Path 3, after angle seq D1 | 12 days, 6 emails | Booking OR end |
| Long Cycle | Path 4, after angle seq D1 | 21 days, 7 emails | Booking OR 12-Month enrol |
| Standard Nurture | Path 5, after angle seq D1 | 30 days, 8 emails | Booking OR 12-Month enrol |
| Price Objection | Tag Objection:Price added (Zap 2) | 5 emails, value reframe | Booking OR end |
| Social Proof | Gallery click (Zap 2) | 3 emails, projects + reviews | Booking OR end |
| SMS Objection | Negative keyword reply | 2 SMS, re-engage | Reply OR end |
| Estimate Follow-Up | estimate_amount field updated | D1/D3/D7/D14/D21→Stalled | Contract signed OR Stalled |
| Site Visit Confirmation | Zap 6 fires | Confirm + D-1 + 2hr + estimator # | Visit date passed |
| Pre-Construction Welcome | Zap 7 (contract signed) | D0 packet / D1 SMS / D2 email | project_status = active |
| Contract Win | Zap 7 (contract signed) | Deposit invoice + welcome SMS | payment.cleared |
| Lost Deal Exit | Pipeline = Closed Lost | D1 survey · D3 backup · D90 win-back | Re-submits OR 12-Month enrol |
| 12-Month Passive Nurture | Path 4/5 after Day 30 OR Recycle OR Lost after D90 | 12 emails, first of each month | Activity → re-activate OR end |
| Past Client 24-Month | project_status = complete (Zap 8) | 6 touchpoints over 24 months | Re-engages for new project |
| Warranty Expiry | 60d before warranty_expiry_date | 2 emails: reminder + renewal offer | warranty_expiry_date passed |
| Review Follow-Up | D10 no review after NPS ask | 2 touchpoints | Review posted OR end |
| Lost: Price Track | loss_reason = Price | Monthly, 12 months, ROI-focused | Re-submits OR end |
| Lost: Timing Track | loss_reason = Timing | Seasonal, March/September triggers | Re-submits OR 24-month |
| Month | Email theme | Content type |
|---|---|---|
| Month 1 | "Your [project_type] — what others in [city] have done" | Project showcase — matches their project type. No selling. Just inspiration. |
| Month 2 | "The 3 questions we wish clients asked us sooner" | Educational — builds authority. Positions expertise without pitching. |
| Month 3 (March) | "Spring is the most competitive booking season — here's why" | Seasonal urgency. Capacity is real — use it. |
| Month 4 | "Before and after: the project that changed how a family uses their home" | Story-led social proof. No features, no specs — just emotional outcome. |
| Month 5 | "How much does a [project_type] actually add to property value?" | Data-backed value content. Addresses the price objection indirectly. |
| Month 6 | "6 months since your enquiry — still thinking about it?" | Personal check-in from rep. Low-key re-activation offer. |
| Month 7–12 | Rotate: project showcase / educational / seasonal / social proof | One CTA per email. Never more than one. |
| Month | Touchpoint | Goal |
|---|---|---|
| Month 3 | Email: "3 months in — how is everything holding up?" | Check-in. Catches issues early. Builds trust. |
| Month 6 | Email: "Maintenance tips for your [project_type] — seasonally relevant" | Useful content. Keeps brand visible. |
| Month 12 | Email + Rep task: "1-year anniversary — warranty status + annual review offer" | Warranty reminder + soft upsell for next phase. |
| Month 18 | Email: "Many clients add [complementary project_type] at this stage" | Expansion upsell. Adjacent project type based on theirs. |
| Month 24 | Email + Rep task: "2-year check-in — know anyone who might need what you got?" | Referral re-ask at peak satisfaction point. |
| loss_reason = Price | Enrol in Lost:Price Track (monthly, 12 months). Content: ROI data, financing options, project value stories. Goal: change the frame over time. |
| loss_reason = Timing | Enrol in Lost:Timing Track. Trigger: March broadcast ("Spring is booking up") + September broadcast ("Last chance before winter"). These are natural re-entry moments. |
| loss_reason = Competitor | At 6 months → send "How did your project go?" email. This is the most effective re-entry: if they had a bad experience with the competitor, they're now your hottest prospect. |
| loss_reason = Spouse | Enrol in Standard Nurture with joint decision-maker framing. D30 rep task: "Call back with partner-friendly offer — ask to speak to both." |
| loss_reason = NotQualified | Enrol in 12-Month Passive only. Do not invest sales resources. Re-score if they re-engage. |
| loss_reason = NoResponse | 3× SMS re-engagement attempt over 7 days. If no reply → 12-Month Passive. Tag Status:NoResponse. |
contract_signed_date · booking_sequence = 6_installation · Pipeline = Closed Won · Tag Status:Contractedproject_status = active · lifecycle_stage = CLIENT · Pipeline = Deposit Pendingdeposit_received_date| Who sends it | PM — never the rep. PM uses Buildertrend daily log as source material. Zap 20 auto-drafts it from the log. |
| When | By 5pm on every day crew is on site. If not sent by 5:30pm → GHL workflow sends PM a reminder task. |
| What not to include | Never mention delays, problems, or issues in the daily SMS without a resolution plan in the same message. "Delay" without context = client calls immediately. |
| Delay message | "Hi {{first_name}} — we hit a [brief description] today. We've [resolution action]. This pushes completion by [X] days. I'll keep you updated daily." |
| Tab 1: Weekly schedule | Columns = Week dates (Mon–Fri). Rows = Crew members + subs. Cells = Project code + phase. One glance shows all resource allocation. |
| Tab 2: Project tracker | All active projects: job_id · client · project_type · crew_assigned · target_start · actual_start · target_complete · actual_complete · project_phase · status. |
| Tab 3: Material deliveries | job_id · material · supplier · order_date · expected_delivery · actual_delivery · PM_notified. PM receives task 2 days before each expected delivery. |
| Tab 4: Sub schedule | subcontractor_name · trade · project · start_date · end_date · status · insurance_expiry. |
| Capacity rule | If sum of crew_hours this week > available_hours → block that week in Calendly for new site visits. GHL workflow + Calendly API. Never overbooking. |
| inspection_required field | Boolean. Set by PM when permit requires inspection before next phase (e.g., footing inspection before concrete, framing before cladding). |
| inspection_scheduled_date | Set by PM when inspection is booked with council/building authority. |
| Gate rule | If inspection_required = true AND inspection_passed_date is empty → GHL workflow blocks project_phase from advancing beyond current phase. PM must update inspection_passed_date to proceed. |
| Failed inspection | If inspection fails → PM task "Inspection failed — rectify: [notes] → rebook." Track in snag_log. |
project_phase update. Zap 20: Buildertrend daily log → GHL note → auto-drafts daily SMS. Zap 21: Buildertrend schedule delay → GHL schedule_variance_days update → Slack alert if >3 days.| Logging tool | Google Form on PM's phone. Fields: project_id, snag_description, photo_url (CompanyCam link), responsible_party (Own crew / Sub / Supplier), target_resolution_date. |
| Zap 16 | Snag form submit → Google Sheets append to snag_log → GHL increment snag_count + snags_open → Create task assigned to crew lead: "[SNAG] {{description}} — {{target_date}}" → Slack #project-alerts |
| Zap 17 (daily) | Check snag_log: if target_date < today AND status ≠ resolved → escalation task to PM → if 2nd day overdue → Slack #project-alerts "OVERDUE SNAG — {{project_id}}" |
| Resolution flow | Crew resolves snag → PM updates snag_log status = resolved → snags_open decrements → if snags_open = 0 → PM task "All snags resolved — ready for final walkthrough" |
| Payment gate | final_payment_date cannot be set until: snags_open = 0 AND client_signoff_date is populated. GHL workflow enforces this. PM who sets final_payment_date without these = task fires to manager. |
| KPI | avg_snags_per_project · avg_snag_resolution_days · repeat_snag_patterns (same description recurring = training issue). Review monthly in Ops dashboard. |
| Step | What to do | What to say |
|---|---|---|
| Start | Walk every inch of the project with the client. PM leads. Client follows. | "I want to walk through everything together and make sure you're completely happy before we close this out." |
| Snag logging | If client spots anything → log it on the spot using the snag form. Don't promise fixes verbally — document them. | "Let me log that right now so it's on record and we can get it resolved." |
| Sign-off | Client digitally signs the walkthrough completion form (PandaDoc or Google Form). Sets client_signoff_date. | "If you're happy with everything, I'll need your signature here to confirm the project is complete." |
| Payment collection | Collect final payment on site (card reader or bank transfer). PM sets final_payment_date in GHL immediately. | "The final payment is [amount] — would you like to do that by card or bank transfer?" |
| Review ask | This is the exact moment. Satisfaction is at its peak. Phone out. 70% conversion on the spot. | "One last thing — reviews mean everything to a business like ours. Would you mind leaving us a Google review right now? I can stay on the line while you do it — it takes 60 seconds." |
| Video capture | PM takes 60-second after walkthrough video. Sets video_captured = true. Sends to editor. | "Do you mind if I take a quick video of the finished project? We love documenting our work." |
| Review platforms | Priority: Google (highest SEO value) → Houzz (construction-specific) → Facebook → BBB. Ask for Google first. Always. |
| Review monitoring | Birdeye or Podium aggregates all reviews in one dashboard. Daily alert for any new review (especially 1-2 star — respond within 2hr). |
| Negative response SOP | Never defensive. Template: "Thank you for sharing this, [name]. We take every experience seriously. [Owner name] will be in touch directly to make this right." Then take offline immediately. |
| GHL fields | review_platform (Dropdown) · review_rating (Number 1-5) · review_url (Text) |
| GMB cadence | Every completed project → GMB post within 48hr. Zap 8 creates task: "Post project to GMB + tag suburb." gmb_posted must = true before task closes. |
| Client VIP | referral_count ≥ 3 → vip_status=true → owner personal call → priority scheduling → annual check-in → VIP:Client tag |
| B2B partners | Separate pipeline: real estate agents / interior designers / architects / landscapers / pool companies / HOA managers. Each gets a unique referral tracking URL. |
| Partner fields | partner_type · partner_referrals_sent · partner_commission_rate (agreed % of contract value) |
| Partner nurture | Monthly newsletter: project updates + "your referral converted" updates + one featured project. Monthly task: PM/owner checks in with top 3 partners personally. |
| Commission | When contract signed from partner referral → finance task created for commission payment. Logged in finance_tracker. |
contract_signed_date)deposit_invoice_date is EMPTY (v8.1 fix — prevents double invoice on field re-save)contract_signed_date is re-saved for any reason (rep edits, Zap retry), Zap 10 fires again and creates a second invoice. The filter ensures it fires exactly once.client_name → {{first_name}} {{last_name}}
email → {{email}}
amount → {{contract_value}} × 0.40
due_date → {{contract_signed_date}} + 3 days
project_ref → {{contact_id}}
deposit_invoice_date = {{now}}milestone_trigger) — PM sets this manually when milestone is reacheddeposit_received_date is NOT empty (deposit cleared — don't invoice milestone if deposit not paid)due_date → {{now}} + 7 days
milestone_desc → {{milestone_trigger}} // PM writes what milestone was reached
milestone_invoice_date = {{now}}. SMS client: "Your milestone invoice is ready — {{amount}}. Pay here: {{link}}"const invoiceDate = new Date(inputData.invoice_date);
const daysOverdue = Math.floor((today - invoiceDate) / 86400000);
output = [{daysOverdue, contact_id: inputData.contact_id, amount: inputData.amount}];
| Field | Type | Set by | Drives |
|---|---|---|---|
| deposit_invoice_date | Date | Zap 10 | Zap 12 overdue check |
| deposit_received_date | Date | Stripe payment.cleared webhook | Phase 3 activation gate |
| milestone_invoice_date | Date | Zap 11 | Zap 12 overdue check |
| milestone_received_date | Date | Stripe webhook | Phase 4 activation gate |
| final_invoice_date | Date | PM (only after snags_open=0) | Zap 12 overdue check |
| final_received_date | Date | Stripe webhook → Zap 8 | Flywheel activation |
| payment_method | Dropdown | PM (card/bank/check/cash) | If check/cash: PM must manually set received_date — webhook won't fire |
| Rule (zero exceptions) | No work proceeds on changed scope until PandaDoc Change Order is signed by client AND payment link is opened. Not verbal agreement. Not "they said yes on a call." Signed CO + payment initiated. |
| CO trigger | PM creates CO in PandaDoc → document.completed webhook → GHL: change_order_count +1, change_orders_total += amount → update Stripe invoice for new total |
| CO fields | change_order_count (Number) · change_order_1_amount · change_order_2_amount · change_order_3_amount · change_orders_total |
| Revenue impact | Track change_orders_total separately from contract_value. In Looker Studio: actual_revenue = contract_value + change_orders_total. Many businesses lose track of CO revenue — it disappears into the job cost without being properly attributed. |
| Column | Type | Source | Formula |
|---|---|---|---|
| contact_id | Text | GHL | Primary key — joins to all other logs |
| client_name | Text | GHL | — |
| project_type | Text | GHL | — |
| contract_value | Currency | GHL | — |
| change_orders_total | Currency | GHL | — |
| actual_revenue | Currency | Calculated | =contract_value + change_orders_total |
| material_cost | Currency | PM enters | All materials purchased for this job |
| labour_cost | Currency | PM enters | Own crew hours × hourly rate |
| subcontractor_cost | Currency | PM enters | All sub invoices for this job |
| permit_cost | Currency | PM enters | Council/permit fees |
| equipment_cost | Currency | PM enters | Hire equipment, scaffolding, tools |
| disposal_cost | Currency | PM enters | Skip hire, waste removal |
| contingency_used | Currency | PM enters | Anything unplanned — track separately |
| total_cost | Currency | Calculated | =SUM(material:contingency) |
| gross_profit | Currency | Calculated | =actual_revenue − total_cost |
| actual_margin_pct | % | Calculated | =(gross_profit / actual_revenue) × 100 |
| estimated_margin_pct | % | From estimate | PM enters estimated margin at quote stage |
| margin_variance | % | Calculated | =actual_margin_pct − estimated_margin_pct — RED if negative |
| Dashboard | Audience | Key metrics |
|---|---|---|
| 1. Funnel | Owner + Sales lead | Lead volume by source/day · CPL by campaign · Lead score distribution · Time to first response · Stage conversion rates |
| 2. Marketing | Owner + Marketing | Revenue by campaign/angle · CPL → CPB → CPC (cost per contract) · Creative performance table · Organic vs paid mix |
| 3. Sales | Sales lead + Reps | Booking rate by rep · Site visit → estimate conversion · Estimate acceptance rate · Close rate by project type · Loss reason breakdown |
| 4. Operations | PM + Owner | Active projects by phase · Crew utilisation % · Average project duration · Snag count by PM · Schedule variance by project |
| 5. Finance | Owner + Accountant | Monthly revenue vs target · Actual margin by project/type · Accounts receivable aging · Change order revenue · Top 10 jobs by margin |
| 6. After-Sales | Owner + PM | NPS score by month · Review volume + avg rating · Referral count + source · Reactivation rate from nurture · Warranty claims open |
| 7. Task Health | Manager + Owner | Overdue tasks by owner · Task completion rate · Avg SLA compliance · Escalation count by week |
| Task type | Title format | SLA | Owner | SOP link |
|---|---|---|---|---|
| CALL | CALL — {{first_name}} — Score:{{lead_score}} | Score ≥16: same day · Score <16: next business day | Rep | → Consultation Call SOP |
| PREP | PREP — {{first_name}} — {{booking_date}} | 1 hour before booking | Rep | → Call Prep SOP |
| ESTIMATE | ESTIMATE — {{first_name}} — due {{date}} | 48 hours from site visit | Estimator | → Estimate Prep SOP |
| APPROVAL | APPROVAL — ${{amount}} — {{first_name}} | 24 hours from creation | Manager | → Approval Protocol |
| PM_SETUP | PM_SETUP — {{first_name}} — deposit cleared | Same day as deposit.cleared | PM | → Project Activation SOP |
| SNAG | SNAG — {{project_id}} — {{description}} | 72 hours resolution | Crew Lead | → Snag Resolution SOP |
| INVOICE | INVOICE — {{first_name}} — ${{amount}} — D+{{days}} | Per escalation schedule (D1/D3/D7/D14) | Rep → PM → Owner | → Invoice Follow-Up SOP |
| REVIEW | REVIEW — {{first_name}} — D10 no post | Same day as task creation | Rep | → Review Ask SOP |
| ESCALATION | ESCALATION — {{type}} — {{first_name}} | Same day, always | Owner/Manager | → Escalation SOP |
const grouped = {};
inputData.tasks.forEach(t => {
if (!grouped[t.owner]) grouped[t.owner] = [];
grouped[t.owner].push(t);
});
output = Object.entries(grouped).map(([owner, tasks]) => ({owner, count: tasks.length, tasks: tasks.map(t=>`${t.title} (${t.days_overdue}d)`).join('\n')}));
You have {{count}} overdue tasks:
{{task_list}}
// Plus post summary to #manager-approvals if any ESCALATION type is overdue
| Lead Response SOP | Who does what in the first 5min / 60min / 4hr. Exact message templates per path. Escalation if no response from rep. |
| Consultation Call SOP | 20-minute structure (Phase 07). 20 discovery questions written out. Angle opener by lead_angle. Next step close script. |
| Site Visit SOP | Before/during/after checklist. Measurement protocol. Photo naming. Client end-of-visit script. 2hr post-visit field update rule. |
| Estimate Presentation SOP | Never email and run. Walk-through structure. GBB presentation script. "I'll think about it" in-the-moment response. |
| Change Order SOP | Word-for-word client conversation script. How to present the CO as protecting them, not padding the invoice. What to do if they push back. |
| Daily Site SMS SOP | Template. What to include. What NOT to include. How to phrase delays. How to handle client replies. Time rule: by 5pm always. |
| Final Walkthrough SOP | Full 30-minute structure (Phase 14). Snag logging. Sign-off. Payment collection. Review ask script. Video capture. |
| Complaint Handling SOP | Who gets called first (PM always, not rep). What to say. What not to say. Documentation protocol. Escalation to owner at what threshold. |
| Role | Can do | Cannot do |
|---|---|---|
| Rep | Create/update contacts. Add notes. Change pipeline stage (Lead In → Appt Scheduled). Create CALL/PREP tasks. Enrol in sequences. Update lead_score. | Delete contacts. Change contract fields. Set project_status. Access finance_tracker. Override HiTL gate. |
| Estimator | Update site_visit_outcome. Upload to CompanyCam. Set estimate_amount. Create ESTIMATE tasks. Update measurements_folder. | Change pipeline stage beyond Proposal Sent. Override $15K approval gate. Set contract fields. |
| PM | Update all project delivery fields. Set project_phase. Create snag records. Set milestone_trigger. Create daily log. Set final_payment_date (only when gates pass). | Change sales fields (lead_score, path_assigned). Delete completed project records. Set final_payment_date before snags_open=0. |
| Owner | All above + approve estimates >$15K + access all dashboards + override any gate with audit note. | Cannot delete contact without documented reason. Every override logged. |
| Never duplicate a contact | Always search phone + email before creating. Duplicates create split histories — you lose the full picture of a lead's behaviour. |
| Never manually set lifecycle_stage | lifecycle_stage is system-controlled. Manual changes break the stagnation detection workflows that depend on stage history. |
| Never enrol in a sequence manually without checking current sequences | Double-enrolment sends the same person 2 emails per day. They unsubscribe. You lose the lead permanently. |
| Never delete a completed project contact | Past clients are in a 24-month sequence. Deletion removes them from the flywheel and from referral tracking. |
| Never set final_payment_date before snag sign-off | Triggers Zap 8 which activates the flywheel and closes the project. If snags remain, the system treats them as resolved. Client complaint + no paper trail. |
| Never bypass the $15K HiTL gate | Every bypassed approval is a mispriced large job waiting to happen. If the gate fires and it's inconvenient — that's information, not a bug. |
| Never rename pipeline stages | Zaps 1, 3, 6, 7, 8 reference exact stage names. Rename = all those Zaps fail silently. |
| Sub Directory tab | Google Sheet: sub_name · company · trade · license_number · insurance_policy_number · insurance_expiry · hourly_rate · performance_score · preferred (Boolean) |
| Zap 22 | Monthly check → any sub with insurance_expiry within 30 days → owner task "Collect updated COI — {{sub_name}} expires {{date}}" → email to sub: "Please send updated Certificate of Insurance before {{date}}" |
| Performance scoring | PM updates subcontractor_performance_score (1–5) after every project involvement. Threshold: 3 scores below 3 = removed from preferred list. Tracked in sub_directory sheet. |
| Assignment rule | Only preferred subs (performance_score ≥ 3.5 AND insurance current) get assigned to new projects. Zap 14 checks this during scheduling. |
| SMS (TCPA) | Explicit opt-in required on every form. Checkbox: "I agree to receive SMS messages from [Company]. Msg &data rates may apply. Reply STOP to opt out." Store sms_consent_timestamp — never overwrite. STOP keyword must halt all SMS sequences immediately and set sms_opt_in = false. |
| Email (CAN-SPAM / CASL) | Every broadcast email must include a working unsubscribe link. GHL handles this automatically for sequences. Verify for any external tools sending email on your behalf. If serving clients in or from the EU/UK — add GDPR privacy acknowledgement to your form. |
| Data security | API keys in Zapier's credential vault — never in Google Sheets, Slack messages, or GHL notes. Rotate GHL API keys every 90 days. Store in company password manager (1Password / Bitwarden) — not individual inboxes. |
| Lien rights | Check jurisdiction-specific requirements. Some states/provinces require lien notice language in contracts before work begins. Requirements vary significantly — legal review recommended before finalising contract template. |
| GHL technical note | GHL does not offer granular field-level permission locks. Role restrictions above are enforced through training, the Never-List, and manager oversight — not technical locks. This is a GHL platform limitation, not a system design choice. |
| Gap | Current status | Recommended next step |
|---|---|---|
| Construction ERP | Xero/QB bridge via Zapier only. No deep job cost sync. | At $1M+ revenue: evaluate Sage 100 Contractor or Acumatica for integrated job costing + accounting. |
| Permit tracking stages | Single task "file permit" — no multi-stage tracking. | Add permit_stage field: not submitted / submitted / under review / approved / rejected. Add tracking workflow. |
| Change order enforcement | Strict gate in contract; real-world verbal approval common. | Add verbal_approval_log note template + 48hr CO signature SLA reminder task. |
| Mobile field experience | PM uses Google Forms + Sheets on mobile. | Evaluate Buildertrend field app when 3+ simultaneous active projects. |
| Material procurement | "Order materials" task only — no PO tracking. | Add purchase_order tab to Master Schedule sheet. Track supplier, amount, expected delivery per line item. |
| Warranty service routing | Warranty claim fields exist; no service ticket routing. | Add warranty_claim → Slack #warranty-claims → PM task → resolution tracking workflow. |
| Crew payroll | Labour cost entered manually by PM. | At scale: evaluate Deputy or ClockShark for time tracking → automatic labour_cost calculation. |
| Zap | Trigger | Purpose | Critical note |
|---|---|---|---|
| Zap 1 | Webhooks: form submit | Validate → score → angle → path → sequence → log → CAPI | Most important Zap. Test with real submit. Build first. |
| Zap 2 | GHL: email event (click/open) OR SMS reply | Intent score update + engagement_status + stagnation detection + behavioural sequence triggers | Must run on EVERY email event. Set to "any email event" not specific sequence. |
| Zap 3 | Calendly: Invitee Created (Consultation) | GHL update + rep briefing note + PREP task + Slack if score ≥16 + booking_log | Calendly event name must match exactly. Case-sensitive. |
| Zap 4 | GHL: tag applied (Objection:Price, Objection:Timing) | Trigger relevant objection sequence + Slack rep alert | Tag names exact match. Space before colon = broken Zap. |
| Zap 5 | Bland AI: call completed | Outcome → GHL update → if booked → Zap 3. If negative → tag + sequence | Bland AI must have webhook configured in Pathway. Test extensively before live. |
| Zap | Trigger | Purpose | Critical note |
|---|---|---|---|
| Zap 6 | Calendly: Invitee Created (Site Visit) | ZIP → territory lookup → estimator capacity check → assign → confirm sequence | Separate Calendly event from Consultation. Do NOT combine events. |
| Zap 7 | PandaDoc: document.completed (contract template) | GHL contract fields → CAPI Purchase event → Contract Win sequence → Welcome Packet → invoice prep | PandaDoc filter: only fires for contract template, NOT estimate or CO templates. |
| Zap 8 | GHL: field updated (final_payment_date) | project_status=complete → warranty date → 24-month track → NPS → GMB task → Sheets → Flywheel | Gates: verify snags_open=0 AND client_signoff_date populated before this Zap acts. Add filter. |
| Zap 9 | GHL: field updated (estimate_amount >15000) | HiTL gate → Slack #manager-approvals → approval task → block until approved | Filter on estimate_amount > 15000. Number field comparison, not text. |
| Zap | Trigger | Purpose | Critical note |
|---|---|---|---|
| Zap 10 | GHL: field updated (contract_signed_date) | Auto-create deposit invoice in PandaDoc/Stripe → deposit_invoice_date | Filter: fire only once (if deposit_invoice_date is currently empty) |
| Zap 11 | GHL: field updated (milestone_trigger) | Create milestone invoice → send to client → milestone_invoice_date | Filter: deposit_received_date must NOT be empty. Don't invoice milestone if deposit unpaid. |
| Zap 12 | Zapier Schedule: daily 9am | Overdue invoice check → D1 SMS / D3 task / D7 PM task / D14 owner escalation | Professional Zapier plan required for scheduled Zaps. Starter plan blocks this. |
| Zap | Trigger | Purpose | Critical note |
|---|---|---|---|
| Zap 13 | Zapier Delay: site_visit_date + 45min | No-show detection → SMS client + rep task if outcome empty | Delay by Zapier — set exact delay from field value. PM must update site_visit_outcome or this fires every day. |
| Zap 14 | GHL: field updated (scheduled_start_date) | Crew schedule conflict check in Master Schedule → PM task if conflict | Sheets lookup: count bookings for assigned crew this week. Threshold = weekly_capacity. |
| Zap 15 | Zapier Schedule: daily 6am | OpenWeather API check for active project ZIPs → PM task if rain/extreme | Loop through all active project ZIP codes. One API call per ZIP per day. |
| Zap 16 | Webhooks: snag form submit | Append to snag_log → GHL snag_count+1 + snags_open+1 → crew task → Slack | Google Form webhook. Each submission = one snag. PM must log each item separately. |
| Zap 17 | Zapier Schedule: daily 8am | Overdue snag check → if target_date < today AND status ≠ resolved → PM escalation task | Reads snag_log sheet. Status must be exact match: "resolved" lowercase. |
| Zap | Trigger | Purpose | Critical note |
|---|---|---|---|
| Zap 18 | Zapier Schedule: daily 9am | Overdue task digest → group by owner → Slack DM per person | GHL task search filter: due_date < today, status ≠ complete. Multi-step with code step. |
| Zap 19 | Buildertrend: job phase updated (webhook) | Sync project_phase → GHL. If phase=Final → PM walkthrough scheduling task | Buildertrend webhook setup in account settings. Must map BT phase names to GHL dropdown values. |
| Zap 20 | Buildertrend: daily log submitted (webhook) | GHL note with log content → auto-drafts PM daily SMS content | Maps BT daily log fields to GHL note template. PM reviews draft before sending. |
| Zap 21 | Buildertrend: schedule delay (webhook) | Update schedule_variance_days in GHL → if >3 → Slack #project-alerts | Requires Buildertrend Pro for webhooks. Not available on Basic plan. |
| Zap 22 | Zapier Schedule: monthly, day 1 | Sub insurance expiry check → owner task + sub email if <30 days | Reads sub_directory sheet. Date comparison: expiry_date − today < 30. |
| Zap 23 | Zapier Schedule: monthly, day 1 | Compliance document expiry check → owner task + Slack if <60 days | Same pattern as Zap 22. Reads compliance_calendar sheet tab. |
| Zap 24 | Zapier Schedule: monthly, day 1 | GHL contact export → save to Google Drive with date stamp | GHL export API. Save to Drive folder "ASMC Backups/YYYY-MM". Monthly data insurance. |
| Field name | API key | Type | Values / Notes |
|---|---|---|---|
| lifecycle_stage | lifecycle_stage | Dropdown | TOFU / MOFU / BOFU / RECYCLE / CLIENT / PAST_CLIENT — System only. Never manual. |
| engagement_status | engagement_status | Dropdown | NEW / ENGAGED / RESPONDING / STALLED / COLD / DNC |
| intent_score | intent_score | Number | Running total. Seeded by Zap 1. Updated by WF-3. Resets on recycle. |
| first_response_time | first_response_time | Number | Minutes from contact creation to first reply. Written once. |
| last_activity_timestamp | last_activity_timestamp | DateTime | Updated by WF-3 on every behavioral event. |
| entered_stage_timestamp | entered_stage_timestamp | DateTime | Set every pipeline stage change. Drives time_in_stage calculation. |
| time_in_stage | time_in_stage | Number (hours) | Calculated: NOW − entered_stage_timestamp. Drives stagnation detection. |
| recycle_count | recycle_count | Number | Increments each time lifecycle_stage = RECYCLE. Identifies chronic non-converters. |
| Field name | API key | Type | Written by |
|---|---|---|---|
| utm_source | utm_source | Text | Zap 1 (form URL param) |
| utm_medium | utm_medium | Text | Zap 1 |
| utm_campaign | utm_campaign | Text | Zap 1 |
| utm_content | utm_content | Text | Zap 1 (→ ad_creative_id) |
| utm_term | utm_term | Text | Zap 1 (Google keyword) |
| lead_angle | lead_angle | Dropdown | Zap 1 (form field) comfort / home-value / lifestyle / protection |
| lead_angle_opener | lead_angle_opener | Text | Zap 1 Code step — angle-specific opening line |
| first_touch_source | first_touch_source | Text | Zap 1 — only if currently empty. Never overwrite. |
| last_touch_source | last_touch_source | Text | Zap 1 — always overwrite. |
| ad_creative_id | ad_creative_id | Text | Zap 1 (from utm_content) — matches creative tracker sheet |
| gclid | gclid | Text | Zap 1 (Google Click ID) — for offline conversion import |
| offer_claimed | offer_claimed | Boolean | Zap 1 (if lead magnet was offered) |
| content_downloaded | content_downloaded | Dropdown | none / value-calculator / planning-guide / questions-guide |
| sms_opt_in | sms_opt_in | Boolean | Zap 1 (form consent field) |
| sms_consent_timestamp | sms_consent_timestamp | DateTime | Zap 1 — TCPA compliance record |
| email_status | email_status | Dropdown | active / cold / bounced / DNC / unsubscribed / invalid |
| Field name | API key | Type | Written by / Notes |
|---|---|---|---|
| lead_score | lead_score | Number | Zap 1 Code step. Static snapshot of score at entry. Not updated after entry — use intent_score for live behavioural score. |
| path_assigned | path_assigned | Dropdown | Zap 1 Code step — path1 / path2 / path3 / path4 / path5 |
| budget | budget | Dropdown | Form field / Zap 1 — Premium / Standard / Starter / Unknown |
| timeline | timeline | Dropdown | Form field / Zap 1 — Urgent / Near / Long / Researching / Unknown |
| project_type | project_type | Dropdown | Form / Zap 1 — deck / patio / fence / pergola / pool / landscaping / other |
| zip_code | zip_code | Text | Form / Zap 1. Store as TEXT not Number — leading zeros (e.g. 07001) are stripped if stored as Number. ZIP-based territory lookup breaks silently. |
| inbound_call_date | inbound_call_date | Date | CallRail webhook Zap — on inbound call event |
| estimate_option_selected | estimate_option_selected | Dropdown | A_standard / B_premium / C_luxury — Written by estimator after GBB presentation. Tracks which option the client chose. |
| Field name | API key | Type | Written by / Notes |
|---|---|---|---|
| contract_value | contract_value | Number | Zap 7 (from PandaDoc document.completed webhook). May differ from estimate_amount after negotiation. |
| change_order_count | change_order_count | Number | CO webhook (PandaDoc document.completed filtered to CO template only) |
| change_order_1_amount | change_order_1_amount | Number | CO webhook |
| change_order_2_amount | change_order_2_amount | Number | CO webhook |
| change_order_3_amount | change_order_3_amount | Number | CO webhook |
| change_orders_total | change_orders_total | Number | Calculated by CO Zap (sum of CO amounts). actual_revenue = contract_value + change_orders_total |
| deposit_invoice_date | deposit_invoice_date | Date | Zap 10. Gate: Zap 10 must check this is EMPTY before creating invoice — prevents double invoice on field re-save. |
| deposit_received_date | deposit_received_date | Date | Stripe payment.cleared webhook. PM enters manually for cash/check. Gates Phase 3 activation. |
| milestone_invoice_date | milestone_invoice_date | Date | Zap 11 |
| milestone_received_date | milestone_received_date | Date | Stripe webhook. Gates next phase advancement. |
| final_invoice_date | final_invoice_date | Date | PM — only after snags_open = 0 AND client_signoff_date is populated. System check runs on write. |
| final_received_date | final_received_date | Date | Stripe webhook / PM manual. Gates Flywheel activation (Zap 8). |
| material_cost | material_cost | Number | PM enters — all materials purchased for this job |
| labour_cost | labour_cost | Number | PM enters — own crew hours × hourly rate |
| subcontractor_cost | subcontractor_cost | Number | PM enters — all sub invoices for this job |
| permit_cost | permit_cost | Number | PM enters — all council/permit fees |
| equipment_cost | equipment_cost | Number | PM enters — hired equipment, scaffolding, tools |
| disposal_cost | disposal_cost | Number | PM enters — skip hire, waste removal |
| actual_margin_pct | actual_margin_pct | Number | Calculated in finance_tracker sheet: =(gross_profit/actual_revenue)×100. PM updates after all costs entered. |
| Field name | API key | Type | Written by / Notes |
|---|---|---|---|
| crew_assigned | crew_assigned | Text | PM / Zap 14 (conflict check output) |
| project_phase | project_phase | Dropdown | PM / Zap 19 (Buildertrend webhook sync) — pre-construction / mobilization / rough-work / finishes / punch-list / complete |
| inspection_required | inspection_required | Boolean | PM — set when permit requires inspection before next phase. Gate: if true AND inspection_passed_date is empty → project_phase cannot advance. |
| inspection_scheduled_date | inspection_scheduled_date | Date | PM |
| inspection_passed_date | inspection_passed_date | Date | PM — gate field. Required to advance phase when inspection_required = true. |
| video_captured | video_captured | Boolean | PM — set true after final walkthrough video taken. Feeds YouTube + GMB posting workflow. |
| target_start_week | target_start_week | Text (YYYY-WW) | PM — set at deposit clearance. Drives capacity calendar and Zap 14 scheduling conflict check. |
| snag_count | snag_count | Number | Zap 16 — increments on each snag form submit. Total snags ever logged on this project. |
| snags_open | snags_open | Number | Zap 16 (increment) / Zap 17 (decrement on resolution). Zap 8 filter must use "Number is less than 1" — not text match "0". |
| final_walkthrough_date | final_walkthrough_date | Date | PM |
| client_signoff_date | client_signoff_date | Date | PM — set at final walkthrough sign-off. Gate for final_invoice_date and Zap 8. |
| gmb_posted | gmb_posted | Boolean | PM — must = true before Zap 8 task closes. Drives organic SEO via project completion posts. |
| review_platform | review_platform | Dropdown | Rep after review posted — Google / Houzz / Facebook / BBB |
| review_rating | review_rating | Number 1–5 | Rep — drives After-Sales Looker dashboard avg rating metric |
| Field name | API key | Type | Written by / Notes |
|---|---|---|---|
| warranty_expiry_date | warranty_expiry_date | Date | Zap 8 — set to final_payment_date + 12 months automatically |
| warranty_claim_date | warranty_claim_date | Date | Client warranty claim form (linked in post-completion email) |
| warranty_claim_description | warranty_claim_description | Text | Client warranty claim form |
| warranty_resolution_date | warranty_resolution_date | Date | PM — after resolution. Drives avg warranty resolution time KPI. |
| mid_project_sat_score | mid_project_sat_score | Number | Mid-project survey form (optional). Score < 3 → PM escalation task same day. |
| consult_sat_score | consult_sat_score | Number | Post-consultation survey form. Score < 3 → manager task immediately. Catches rep performance issues. |
| loss_followup_date | loss_followup_date | Date | System (D90 trigger after Closed Lost). Next scheduled human touchpoint after automated sequences end. |
| Field name | API key | Type | Written by / Notes |
|---|---|---|---|
| referral_count | referral_count | Number | Zap 8 / referral form submission. Drives VIP threshold. |
| referral_source | referral_source | Text | Zap 8 / referral form (name of referrer). Rep sees this before first call with referred lead. |
| vip_status | vip_status | Boolean | Zap 8 — set true when referral_count ≥ 3 → triggers owner personal phone call task. |
| partner_type | partner_type | Dropdown | realtor / designer / architect / landscaper / pool / HOA — B2B partner contacts only |
| partner_commission_rate | partner_commission_rate | Number | Agreed % of contract value — B2B partner contacts only. Drives commission finance task on contract signed. |
🔴 Zap 1 fires but contact not in GHL
Cause: Zapier sample data used during build (not a real form submit). Sample data often has different field names than real submissions.
Fix: Delete test contacts. Submit the actual form. Use real test data. Check Zapier task history — look at the exact payload the webhook received. Does field name match exactly what GHL expects?
🔴 Sequences not sending
Cause 1: Sequence shell was created but never had messages added.
Cause 2: Contact's email_status = bounced or unsubscribed — GHL silently skips them.
Cause 3: Sending domain not verified. Check GHL → Settings → Email Services → domain authentication status.
Fix: (1) Check sequence has at least one published message. (2) Check contact's email field. (3) Verify SPF/DKIM in GHL settings.
🟠 Lead score correct but wrong path assigned
Cause: Budget/timeline field values in the form don't exactly match the code step's string comparison. "premium" ≠ "Premium".
Fix: In Zap 1 Code step — print console.log(inputData.budget, inputData.timeline) and check Zapier logs. Match capitalization exactly.
🟠 Zap 8 fires but client NOT moved to Archive
Cause: Filter check — snags_open was 0 and client_signoff_date was populated, but the field comparison in the Zap used text "0" not number 0.
Fix: In Zapier filter, use "Number is less than 1" not "Text exactly matches 0".
🟠 Deposit invoice sends but client says they didn't receive it
Cause 1: Email went to spam. Check client's spam folder first.
Cause 2: PandaDoc sent to wrong email (GHL had old email).
Cause 3: DKIM not set up — emails flagged as unauthenticated.
Fix: Verify DKIM in GHL. Check PandaDoc sent document list. Resend manually from PandaDoc dashboard.
🟠 Territory routing assigns wrong estimator
Cause: ZIP code in form doesn't match territory_map exactly. Leading zeros stripped (e.g., ZIP 07001 becomes 7001).
Fix: In territory_map sheet, set ZIP column as TEXT format (not Number). In Zap 6, ensure ZIP field is treated as text in the Sheets lookup.
🟠 Intent score not updating on email opens
Cause: Apple Mail Privacy Protection. Apple prefetches emails, triggering opens without real engagement. This inflates open counts and intent scores.
Fix: Don't score email opens from Apple Mail devices (flag: user_agent contains "Apple"). Score email clicks only — they're unambiguous intent signals.
🟠 Daily scheduled Zaps (12,15,18) not running
Cause: Zapier plan is Starter or Professional — scheduled Zaps require Professional or higher.
Secondary cause: Zapier account was over task quota — scheduled Zaps paused.
Fix: Check Zapier plan. Check task usage dashboard. If at quota, upgrade or audit which Zaps are generating excessive tasks.
🟠 Duplicate contacts appearing weekly
Cause: GHL Find Contact step in Zap 1 searches by email but contact exists with different email + same phone. Two records created.
Fix: Add a second Find Contact step in Zap 1 — search by phone BEFORE searching by email. If phone match found, use that record ID for all subsequent updates. Never let two records have the same phone number.
🟢 Looker Studio showing no data
Cause: Looker was built before enough data existed in Sheets. Data connection is correct but tables have <5 rows.
Not a bug — build Looker after 30 days of live system data. The dashboards need historical data to show trends. Before then, look at raw Sheets data directly.
| Step 1 | Open Zapier → Zap History. Find the failed run. Click into it and read the exact error message — don't guess. |
| Step 2 | Check the input data. Did the field arrive with the expected value? Empty fields are the #1 cause of Zap errors. |
| Step 3 | Check if the GHL field still exists. Renamed fields break the mapping — field shows as "unmapped" in Zapier action. |
| Step 4 | Check Zapier account health: task quota, plan level, connected app auth tokens (GHL tokens expire — reconnect periodically). |
| Step 5 | Re-test with a clean data submission. Never fix a Zap based on the broken run alone — the test confirms the fix works. |
| Tool | Endpoint | Used for |
|---|---|---|
| GHL Contact Update | PUT /contacts/{id} | All field updates from Zapier. Requires Bearer token from GHL settings → Integrations → API Keys. |
| GHL Task Create | POST /contacts/{id}/tasks | All task creation. Body: title, dueDate, assignedTo, description. |
| Meta CAPI | POST graph.facebook.com/v18.0/{pixel_id}/events | Lead event (Zap 1) and Purchase event (Zap 7). Requires access_token from Meta Business Manager. |
| OpenWeather | GET api.openweathermap.org/data/2.5/weather?zip={zip}&appid={key} | Zap 15 weather check. Free tier = 60 calls/min. Plenty for daily checks. |
| ZeroBounce | GET api.zerobounce.net/v2/validate?email={email}&api_key={key} | Zap 1 step 0.5. Returns: valid / invalid / catch-all / unknown / disposable. |
| Calendly | GET api.calendly.com/event_invitees | If polling needed (webhook not available in specific plans). |
| Bland AI | POST api.bland.ai/v1/calls | Zap 5. Trigger outbound AI call. Body: phone_number, pathway_id, request_data (angle, name, opener). |
| GHL ↔ Xero | Zapier bridge: contract_signed → create Xero invoice. payment.cleared → mark Xero invoice paid. Monthly reconciliation task fires automatically. |
| GHL ↔ Buildertrend | Zaps 19–21. Phase updates flow both ways. Buildertrend is the field source of truth. GHL is the client communication source of truth. Never duplicate data — each owns its domain. |
| GHL ↔ CompanyCam | Zap: project milestone → trigger CompanyCam project creation with same job_id. CompanyCam → GHL note on photo milestone with gallery URL. |
| GHL ↔ CallRail | Inbound call → CallRail assigns tracking number → webhook → Zap → find GHL contact → note call + recording URL. Missed call → SMS via GHL workflow. |
| GHL ↔ Birdeye/Podium | Review posted → Birdeye webhook → Zap → GHL update review_platform + review_rating + review_url → if 1-2 star → manager alert task. |
| 50+ leads/month | Add a second rep. Split pipeline by rep. Add rep_assigned field to all Zap 1 routing logic. Add rep-specific KPI tab to Sales dashboard. |
| 20+ active projects | Add a second PM. Reassign Zap routing by project geography. Split PM dashboard by PM name. |
| 3+ estimators | Move site visit scheduling from Calendly to a dedicated scheduling tool (Servicetitan / Acuity with resource booking). Capacity management becomes critical at this scale. |
| $2M+ annual revenue | Dedicated accountant or bookkeeper. Move from Xero → dedicated construction accounting (Sage 100 Contractor). Separate entity for each project (liability protection). |
| 2 locations | Separate GHL sub-accounts per location. Shared reporting in Looker Studio using combined data source. Territory routing becomes location-aware. |
| Platform | Best for | Cost at 10K tasks/mo | GHL support | Key difference |
|---|---|---|---|---|
| Zapier Professional | Non-technical teams, fastest setup, maximum GHL native support | ~$49/mo | ✅ Full native | Most expensive at volume. Auto-manages GHL OAuth. Best for fast launch. |
| Make (Integromat) | Cost-conscious teams comfortable with visual flow builders | ~$9/mo | ✅ Full native | ~80% lower cost than Zapier at scale. Visual canvas. Routers = Zapier filters. Iterators loop arrays. |
| n8n | Developer on team, self-hosting, maximum control | ~$20/mo cloud or free self-hosted | ⚠ HTTP nodes only | Most powerful. Self-hosted on $5/mo VPS = unlimited. Requires Bearer token management. |
| ActivePieces | Open-source priority, self-hostable, moderate technical comfort | Free self-hosted | ⚠ HTTP actions only | Cleaner UI than n8n. Growing integrations library. Simpler than n8n for non-developers. |
score = ({{budget}} = "Premium" ? 10 : {{budget}} = "Standard" ? 5 : {{budget}} = "Starter" ? 2 : 0)
+ ({{timeline}} = "Urgent" ? 15 : {{timeline}} = "Near" ? 10 : {{timeline}} = "Long" ? 5 : {{timeline}} = "Researching" ? 2 : 0)
path = (score >= 21 ? "path1" : score >= 16 ? "path2" : score >= 11 ? "path3" : score >= 6 ? "path4" : "path5")
"node": "HTTP Request",
"method": "PUT",
"url": "https://rest.gohighlevel.com/v1/contacts/{{contactId}}",
"headers": {
"Authorization": "Bearer {{$credentials.ghlApiKey}}",
"Content-Type": "application/json"
},
"body": { "customField": { "field_api_key": "value" } }
}
const inputData = items[0].json;
let score = 0;
const budgetMap = { Premium: 10, Standard: 5, Starter: 2, Unknown: 0 };
const timelineMap = { Urgent: 15, Near: 10, Long: 5, Researching: 2, Unknown: 0 };
score += budgetMap[inputData.budget] || 0;
score += timelineMap[inputData.timeline] || 0;
if (inputData.utm_source === 'referral') score += 8;
const path = score>=21?'path1':score>=16?'path2':score>=11?'path3':score>=6?'path4':'path5';
return [{ json: { ...inputData, score, path, opener: openers[inputData.lead_angle]||'' } }];
-v ~/.n8n:/home/node/.n8n \
n8nio/n8n
# Production: use docker-compose with PostgreSQL + Nginx + SSL
# SQLite default not suitable for production use
| What it is | Open-source Zapier alternative. Self-hostable (free unlimited) or cloud ($100/mo). Cleaner UI than n8n for non-developers. GHL not natively integrated — use HTTP action for all GHL calls. |
| GHL HTTP pattern | Action: HTTP (POST/PUT/GET) URL: https://rest.gohighlevel.com/v1/contacts/{{contactId}} Headers: Authorization: Bearer [stored credential] | Content-Type: application/json Body: { "customField": { "field_api_key": "value" } } |
| Scheduled triggers | Use "Schedule" trigger piece. Supports cron expressions. Equivalent to Zapier Schedule trigger. Zaps 12, 15, 17, 18, 22, 23, 24 all use scheduled triggers. |
| Webhook triggers | Use "Webhook" trigger piece — generates unique URL. Equivalent to Zapier Catch Hook. Used by Zaps 1, 7, 8, 16. |
| Code step | JavaScript (Node.js runtime). Same scoring code from Zap 1 runs without modification. |
| When to choose over n8n | If team wants simpler visual interface than n8n and is comfortable with HTTP actions for GHL API calls. Lower learning curve, slightly fewer capabilities. |
| Self-host cost | Free on any $10–20/month VPS. Docker-based same pattern as n8n. Cloud plan starts at $100/month. |
| Logic portability | The workflow logic is identical across all platforms. Only the UI and connection method differs. Zap recipes translate directly to Scenarios (Make) or Workflows (n8n) with the same trigger → action structure. |
| Zapier → Make | Rebuild Scenarios one at a time. Start with non-critical Zaps (18, 22, 23) to learn Make before migrating the revenue spine (1, 3, 7, 8). Run both in parallel for 1 week before deactivating Zapier. |
| Zapier → n8n | Longer migration — requires HTTP node setup for all GHL calls. Allocate 1–2 weeks for developer. High payoff: unlimited tasks at zero cost on self-hosted. |
| When to migrate | Zapier costs >$200/month (approximately 8,000+ tasks) → Make costs ~$29/month for equivalent volume. ROI on migration time < 3 months. |
| What not to do | Never run half the system on Zapier and half on Make simultaneously. Debugging becomes impossible and webhook conflicts arise. Commit to one platform fully. |