SOP Guide

Aurea Growth SOP — Building Websites & Landing Pages with Codex/Claude Code, GHL Logic, and External Hosting

Last updated: 2026-05-13 (revised 2026-05-13: added GHL iframe performance/reliability pattern, Cloudflare cache headers, robots.txt note, GHL custom code production pattern with absolute URLs, CTA/form tracking hook pattern, mobile sticky CTA note, expanded skill stack and best-practice invocation order)

Use this SOP whenever Aurea Growth builds a client website, paid-search landing page, or campaign page with Codex/Claude Code while using GoHighLevel for CRM, forms, automations, calendars, and follow-up.

Core principle: do not let GHL page-builder limitations force the page to look cheap. Use the best front-end build for the visitor and the best GHL logic for the backend.


0. Page Type Classification

Identify which type you are building before starting. Different rules apply.

Type 1 — Campaign / Paid-Search Landing Page

  • Single treatment or offer.
  • Single primary CTA.
  • Strict qualification gate before calendar access.
  • Free consultation as the offer.
  • GHL handles CRM, tags, automations, calendar, follow-up.
  • Hosted on client domain at a treatment-specific URL.
  • Optimised for Google Ads Quality Score.
  • Does not need ongoing CMS management — it is a campaign asset.

Type 2 — Website Rebuild / Redesign

  • Full homepage plus existing treatment pages.
  • Softer CTA — consultation enquiry or WhatsApp, not a strict suitability gate.
  • SEO preservation is a hard constraint — existing URLs, Rank Math scores, and indexed pages cannot be thrown away without a redirect plan.
  • Ongoing CMS management required — migrate to Framer after Claude Code locks the design, or use a WordPress theme that a non-developer can edit.
  • GHL form is lighter — name, concern, contact — not a 6-step qualification gate.
  • Automation is often already in place via the clinic's existing GHL account.

Type 3 — SEO Treatment Page

  • A single page targeting one treatment keyword for organic search.
  • Not driven by ad spend, so Google Ads Quality Score is not the main concern.
  • Long-form copy, FAQ schema, local intent, internal links to homepage and related pages.
  • Usually part of a website rebuild, not a standalone build.
  • Can be built in WordPress natively or as a standalone HTML page added to an existing site.

Choose the type before opening a file. The structure, CTA logic, form type, and hosting decision all follow from this.


1. Default Recommendation

Best Production Setup

Ad / Search / Campaign Traffic
→ Client domain landing page
→ Custom HTML/CSS built with Codex or Claude Code
→ GHL-powered form / external form tracking
→ GHL calendar or conditional thank-you page
→ GHL workflows, tags, pipeline, tasks
→ GA4 + Google Ads conversion tracking

Why This Is The Default

  • Best for Google Ads Quality Score: page lives on the client domain, with a relevant URL, fast load, strong content match, and trust signals.
  • Best for trust: patients see the real clinic domain, not a generic funnel URL.
  • Best for design quality: Codex/Claude Code can build a premium page without fighting GHL layout constraints.
  • Best for backend operations: GHL still handles CRM, tagging, automations, internal tasks, calendar, SMS/email follow-up, and reporting.

QS vs Attribution — Important Distinction

When pitching the move from GHL subdomain to main client domain, be precise about what actually improves.

Quality Score improves from:
- Better page content relevance to the keyword (this is the main lever)
- Faster page load speed
- Cleaner design and user experience
- Not from the domain change alone

Attribution improves from:
- Having the landing page and thank-you step on the same domain as the ad click (eliminates the cross-domain referral break in GA4)
- Keeping the session alive through form submission without handing off to api.leadconnectorhq.com or page-builder.leadconnectorhq.com as a referral source
- Means Google Ads sees more conversions attributed to google / cpc instead of (direct) or GHL referral

The domain move helps most on attribution. Page content, copy relevance, and load speed help most on QS. Both are worth doing together. Do not over-claim the QS benefit when pitching the domain move — "better page + faster load = better QS" is correct. "Moving to the main domain alone fixes QS" is not.


2. Decision Tree

Option A — Custom Page On Client Domain + GHL Logic

Use this when:

  • We have WordPress, SiteGround, Webflow, Wix, Framer, Cloudflare, or server access.
  • The page needs to look premium.
  • Google Ads Quality Score matters.
  • The offer is high-ticket, medical/aesthetic, dental, or trust-sensitive.
  • The client already has domain authority or a known clinic website.

Recommended for:

  • Google Ads landing pages
  • SEO-ready treatment pages
  • Premium clinic offers
  • High-ticket consultation funnels
  • Pages the client may inspect closely

Option B — GHL Page/Funnel Built Natively

Use this when:

  • Website/domain access is delayed.
  • The page must be live immediately.
  • The form/calendar/workflow logic is more important than perfect design.
  • The page is for internal testing or a fast pilot fallback.

Recommended for:

  • Temporary pilot pages
  • Quick MVP funnels
  • Fallback before a client meeting
  • Simple lead capture pages where design stakes are lower

Option C — Full Custom HTML Pasted Into GHL Custom Code

Primary use: visual testing, fast internal preview, showing the concept to the team.

Also validated as a temporary production approach when DNS/subdomain access is blocked and the existing GHL URL must stay untouched. This keeps the same final URL, the same post-form GHL logic, and avoids any DNS complexity or CNAME negotiation with the client.

Required fixes before pasting into GHL:

  1. Replace all relative assets/ paths with absolute Cloudflare Pages URLs — GHL has no local file context so relative paths silently fail. Catch src=, href=, srcset=, imagesrcset=, and comma-separated srcset values — srcset is easy to miss and will break the hero image.
sed \
  -e 's|href="assets/|href="https://your-project.pages.dev/assets/|g' \
  -e 's|src="assets/|src="https://your-project.pages.dev/assets/|g' \
  -e 's|srcset="assets/|srcset="https://your-project.pages.dev/assets/|g' \
  -e 's|imagesrcset="assets/|imagesrcset="https://your-project.pages.dev/assets/|g' \
  -e 's|, assets/|, https://your-project.pages.dev/assets/|g' \
  index.html | pbcopy
  1. Add a GHL width escape to the outermost page div. GHL wraps pasted code in a constrained container. Without this the page renders narrower than intended:
.page {
  position: relative;
  width: 100vw;
  left: 50%;
  transform: translateX(-50%);
}

Permanent limitations of Option C (still apply even for production use):

  • Not editable section-by-section in GHL — copy/image changes require re-pasting the full HTML.
  • GHL adds a small script/wrapper overhead on top of the page.
  • PageSpeed score will be slightly lower than a pure Cloudflare-hosted version.
  • Not suitable if Camille or the client needs to make copy edits without John.

3. GHL Form Strategy

Best Form Strategy On GHL Pages

Use native GHL forms or surveys.

Benefits:

  • Clean CRM capture.
  • Native custom fields.
  • Tags and workflow triggers work predictably.
  • Conditional logic/disqualification can be configured inside GHL.
  • Calendar routing is easier.

Best Form Strategy On External Pages

Prefer a DOM-compatible form that GHL can track through External Form Tracking.

Requirements:

  • The form exists directly in the page DOM.
  • It uses a real <form> element.
  • Inputs have valid name attributes.
  • The form can be mapped into GHL fields.
  • Success/thank-you states can be tracked for Google Ads conversions.

Iframe Form Embeds

The standard GHL embed often looks like this:

<iframe
  src="https://api.leadconnectorhq.com/widget/form/FORM_ID"
  style="width:100%;height:100%;border:none;border-radius:1px"
  id="inline-FORM_ID"
  data-layout="{'id':'INLINE'}"
  data-trigger-type="alwaysShow"
  data-form-name="Clinic Assessment Form"
  data-height="1437"
  data-form-id="FORM_ID"
  title="Clinic Assessment Form">
</iframe>
<script src="https://link.msgsndr.com/js/form_embed.js"></script>

This is acceptable for quick embedding, but it is not the ideal tracking-critical setup.

Use iframe embeds when:

  • Speed matters more than perfect external tracking.
  • The form is simple.
  • We are using GHL-native submission data and not relying on DOM-level external form tracking.
  • We can still fire conversions from a thank-you page or redirect.

Avoid iframe embeds when:

  • We need precise external form tracking.
  • We need custom JavaScript to inspect field-level answers.
  • We need maximum Google Ads conversion clarity.
  • The form sits inside a custom page and must behave like a native component.

Embed Test Learning — Ayadi 2026-05-11

When testing GHL form embeds inside a custom HTML page:

  • Test through https://, not file://. GHL embed scripts may not behave correctly from a local file.
  • Cloudflare Pages is a good temporary HTTPS preview environment.
  • The GHL POLITE_SLIDE_IN embed is designed to appear as a slide-in overlay. It may render as blank/hidden inside a normal page section because its iframe often starts with display:none and waits for the GHL script to reveal it.
  • For a form that should sit inside the landing page's assessment section, use the INLINE embed mode.
  • If two test URLs look identical after converting both to INLINE, that is expected. The original slide-in version has effectively been changed into the same inline form experience.
  • If replacing a custom/native form with a GHL iframe, remove or guard any JavaScript that expects the old form elements to exist. Otherwise page scripts can crash before scroll-reveal or layout JavaScript runs, making the page look empty between header and footer.

Safe JavaScript pattern:

const form = document.getElementById("suitabilityForm");
const nextBtn = document.getElementById("nextBtn");

function initNativeSuitabilityForm() {
  if (!form || !nextBtn) return;
  // Native form logic here.
}

function initReveal() {
  document.querySelectorAll(".reveal").forEach((element) => {
    element.classList.add("in");
  });
}

initReveal();
initNativeSuitabilityForm();

Production note: for the final client domain version, the inline iframe embed can be acceptable if speed matters and conversion tracking is handled on the thank-you/redirect step. For maximum external tracking visibility, use a DOM-native form with GHL External Form Tracking or webhook submission instead of iframe.

GHL Iframe Performance Pattern — Nurse Kelsey 2026-05-13

GHL iframe embeds can behave like a performance trap on custom landing pages.

What happened:

  • A custom Cloudflare landing page scored around 90 on mobile when the GHL form was not loaded initially.
  • Loading the GHL iframe immediately made the form visible, but dropped mobile performance to around 59.
  • The iframe pulled in the LeadConnector/GHL stack, including third-party JavaScript, Facebook resources, phone-number libraries and extra main-thread work.
  • Over-optimising the form with generic lazy loading / reveal animations / content-visibility caused the form to disappear or render unreliably.

Recommended pattern for campaign pages:

<iframe
  data-src="https://api.leadconnectorhq.com/widget/form/FORM_ID"
  style="width:100%;height:1019px;border:none;display:block;"
  id="inline-FORM_ID"
  data-layout="{'id':'INLINE'}"
  data-form-id="FORM_ID"
  title="Clinic Assessment Form">
</iframe>
const ghlFrame = document.getElementById("inline-FORM_ID");

function loadGhlForm() {
  if (!ghlFrame || ghlFrame.src) return;
  ghlFrame.src = ghlFrame.dataset.src;
}

if (ghlFrame) {
  if (window.location.hash === "#assessment") {
    loadGhlForm();
  } else if ("IntersectionObserver" in window) {
    const formObserver = new IntersectionObserver((entries) => {
      if (entries.some(entry => entry.isIntersecting)) {
        loadGhlForm();
        formObserver.disconnect();
      }
    }, { rootMargin: "900px 0px" });

    formObserver.observe(ghlFrame);
  } else {
    loadGhlForm();
  }

  document.querySelectorAll('a[href="#assessment"]').forEach(link => {
    link.addEventListener("click", loadGhlForm, { once: true });
  });
}

Rules:

  • Do not put reveal animation classes on the form card or form iframe container.
  • Do not use content-visibility on the section containing a GHL iframe.
  • Avoid loading="lazy" on a GHL iframe if the form fails to render reliably.
  • Prefer intent-based loading: load when the user opens #assessment, clicks a CTA, or scrolls near the form.
  • When the iframe is intent-loaded, push a lightweight analytics event at the exact moment src is assigned. GTM/GA4 can then track "form became available" even if the form itself lives inside LeadConnector and cannot be inspected from the parent page.
  • If using GHL's form_embed.js, test carefully. A plain iframe can be more reliable if the height is fixed and the form displays correctly.
  • Expect PageSpeed to penalise immediate GHL iframe loading. This is not necessarily a page-design issue; it is the third-party form stack.

Use this pattern only when an iframe is acceptable. If attribution and field-level visibility are business-critical, build a DOM-native form and submit to GHL instead.

Optional tracking hook pattern for external pages:

window.dataLayer = window.dataLayer || [];

function trackEvent(eventName, properties = {}) {
  window.dataLayer.push({
    event: eventName,
    page_type: "treatment_assessment_landing_page",
    ...properties
  });

  if (typeof window.gtag === "function") {
    window.gtag("event", eventName, properties);
  }
}

document.querySelectorAll("[data-track-cta]").forEach(link => {
  link.addEventListener("click", () => {
    trackEvent("cta_clicked", {
      cta_location: link.dataset.trackCta,
      cta_text: link.dataset.trackLabel || link.textContent.trim()
    });
  });
});

function loadGhlForm() {
  if (!ghlFrame || ghlFrame.src) return;
  ghlFrame.src = ghlFrame.dataset.src;
  trackEvent("assessment_form_loaded", {
    form_id: ghlFrame.dataset.formId,
    form_location: "assessment_section"
  });
}

This does not replace real GA4/GTM setup. It gives GTM a clean dataLayer event contract once the container is added, and it still works harmlessly if gtag is not present.


Step 1 — Gather Client Context

Read the client/prospect file first.

Minimum inputs:

  • Client name, domain, address, phone.
  • Treatment/service focus.
  • Offer and price anchor.
  • Primary audience.
  • Top objections.
  • Proof points.
  • Booking system.
  • GHL sub-account status.
  • Access owner.
  • Existing website/brand style.
  • Required assets.
  • Tracking state: GA4, Google Tag, GTM, Google Ads.

For Google Ads, also define:

  • Primary keyword group.
  • Secondary keyword group.
  • Negative keyword themes.
  • Campaign goal.
  • Conversion event.
  • Budget.
  • Landing page URL.

Step 2 — Choose The Build Path

Use this hierarchy:

  1. Client domain custom page if access exists.
  2. Client domain custom page prepared locally if access is expected soon.
  3. GHL native page fallback if access is blocked.
  4. Full custom HTML pasted into GHL only for temporary preview/testing.

Step 3 — Build The Page As A Source Of Truth

With Codex/Claude Code:

  • Create a local folder under the relevant client/prospect folder or tools/.
  • Build index.html.
  • Keep assets in /assets.
  • Use real images where possible.
  • Compress images.
  • Use patient/customer-facing copy only.
  • Avoid internal language such as:
  • "Quality Score"
  • "lead quality"
  • "campaign"
  • "filter"
  • "GHL"
  • "Vagaro"
  • "Google"
  • "client diary"
  • Any reference to the client by first name in an operational context
  • Any reference to your team member or VA by name

Important: the page must read like the end customer will see it, not like an internal setup guide.

Step 4 — Landing Page Structure

Use this structure for most paid-search clinic campaigns:

  1. Hero
  2. Treatment/service keyword in H1.
  3. Location in H1 or subhead.
  4. Clear outcome.
  5. One CTA.
  6. Real image or clinic/device visual.

  7. Trust strip

  8. Years of experience.
  9. Location.
  10. Founder-led / clinician-led care.
  11. Technology/device proof.
  12. Review or rating if available.

  13. Problem / fit

  14. Who this is for.
  15. What concerns it helps with.
  16. Avoid insecurity-led copy.

  17. Benefits

  18. 3-5 outcome-driven blocks.
  19. Keep claims careful and compliant.

  20. Treatment/service explanation

  21. What it is.
  22. How it works.
  23. What happens during consultation.
  24. Why a course/plan may be needed.

  25. Why this clinic

  26. Local differentiation.
  27. Founder/clinician experience.
  28. Environment.
  29. Proof.

  30. Form / suitability check

  31. Short enough to complete.
  32. Strict enough to protect client time.
  33. One question per step if multi-step.
  34. On long mobile pages, keep a restrained sticky CTA visible after the first screen so users do not need to scroll thousands of pixels back to the form. It should use the same primary CTA, be hidden on tablet/desktop, respect safe-area insets, and never cover footer content.

  35. FAQ

  36. Handle objections before final CTA.
  37. Include timeline, price, suitability, pain, downtime, what happens next.

  38. Final CTA

  39. Same primary CTA.
  40. Clear next step.
  41. Address/location.

Step 5 — Copy Rules

Every visible word must be customer-facing.

Use:

  • "Request an assessment"
  • "Check suitability"
  • "Choose a consultation time"
  • "Your clinician will explain..."
  • "A full course can require..."
  • "This helps keep the consultation transparent..."

Avoid:

  • "Lead"
  • "Filter"
  • "Disqualify"
  • "Pipeline"
  • "Workflow"
  • "GHL"
  • "Quality Score"
  • "Google will learn"
  • "We train Google"
  • "Protect the client's time"
  • "This campaign..."

Step 6 — Form Logic

For high-ticket free consultation funnels, use qualification questions that map to routing.

Example:

1. What are you looking to improve?
2. When would you like to start?
3. Are you open to a course-based plan?
4. Is the likely investment within range?
5. Can you attend the clinic location?
6. Name, phone, email

Routing:

  • Suitable answer path → calendar / consultation booking.
  • Not suitable answer path → polite thank-you page without calendar.
  • Qualified but no booking → GHL task + call/message follow-up.
  • Booked in GHL → manual or automated sync into clinic diary if needed.

Step 7 — Calendar / Booking Logic

Choose one source of truth for patient-facing reminders.

Recommended:

  • GHL handles form, qualification, calendar booking, internal tasks, non-booker follow-up.
  • Clinic booking system handles official appointment confirmation/reminders once duplicated or synced.

Avoid duplicate reminders from both systems unless intentionally configured.

For manual sync:

GHL booking created
→ Internal task assigned
→ Team duplicates appointment into clinic booking system
→ Clinic system sends official confirmation/reminder

Step 8 — Tracking Plan

Minimum tracking:

  • GA4 page_view on landing page.
  • Google Ads click ID capture where possible.
  • Conversion event for suitable form submission.
  • Conversion event for booked consultation.
  • Thank-you page or success state that can be tracked.
  • GHL source fields: campaign, keyword, ad group, GCLID, landing page URL, form answers.

For Google Ads:

  • Primary conversion: suitable assessment request or booked consultation.
  • Secondary conversion: form start, form complete, calendar view.
  • Do not optimise to raw page views or low-quality submissions.

Step 9 — Hosting

WordPress / SiteGround

Best when the client site is WordPress.

Implementation options:

  • Create a custom page template.
  • Use a lightweight custom HTML block if theme allows.
  • Use Elementor only if needed, but avoid bloated widgets.
  • Upload assets to WordPress media library.
  • Add page-specific CSS/JS if supported.

Recommended URL format:

https://clientdomain.com/treatment-name-location/
https://clientdomain.com/treatment-name-assessment/

WordPress + ACF pattern (recommended for campaign pages that need ongoing copy management):

Use Advanced Custom Fields (ACF) to separate design from content. Build the page template in code once, expose only the editable text fields and images as ACF fields in the WordPress admin. The VA or client edits copy through a simple form in WP admin with no HTML exposure. The design and layout are locked.

When to use this pattern:

  • The page needs to look premium (full code control) but the VA or client needs to update headline, body copy, or photos without touching HTML.
  • The alternative would be training a VA on Claude Code just to edit a landing page.
  • The client is on WordPress and Elementor would compromise design quality.

What to expose as ACF fields (typical campaign page):

  • Hero headline
  • Hero subheadline / lede
  • CTA button text
  • Stat strip (4 stats: number + label)
  • Section headlines
  • Body paragraphs
  • Hero image, founder image, treatment/clinic images
  • FAQ questions and answers

What to lock in the template (never editable via ACF):

  • Layout, grid, spacing
  • Typography scale and fonts
  • Colour palette
  • Section order
  • Button styles
  • Scroll reveal logic

Cloudflare Pages

Use when:

  • Client domain access is not ready.
  • We need a fast temporary preview.
  • We need to host a guide or internal client-facing planning doc.
  • Sharing a v2 mockup with a client before committing to full implementation.

For live ads, prefer client domain/subdomain over Aurea-owned domains.

Cloudflare preview note:

  • Deploy the whole project folder, including index.html, test pages, and /assets.
  • Use the root https://project.pages.dev URL if the hashed preview URL has temporary SSL issues immediately after deploy.
  • Hash preview URLs and root Pages URLs may cache differently; after fixes, redeploy and test the root URL first.
  • Add a valid robots.txt file. Otherwise Cloudflare Pages may return the HTML page for /robots.txt, causing Lighthouse/SEO audits to report a malformed robots file.
  • Add a _headers file for long-lived static asset caching:
/assets/*
  Cache-Control: public, max-age=31536000, immutable

/*
  X-Content-Type-Options: nosniff
  Referrer-Policy: strict-origin-when-cross-origin
  • Keep _headers conservative. Avoid adding broad Link: <...>; rel=preconnect headers unless needed, because duplicate/unused preconnects can hurt Lighthouse diagnostics.

Cloudflare Pages deploy gotcha — project must exist before deploying:

wrangler pages deploy with --project-name will fail with "Project not found" if the project does not already exist. wrangler pages project create can also fail with a generic API error (code 8000000) for unclear reasons.

Workaround: create the project directly via the Cloudflare API, then deploy normally.

ACCOUNT_ID="your-account-id"
TOKEN="your-oauth-token-from-~/.wrangler/config/default.toml"

curl -s -X POST "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/pages/projects" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"project-name","production_branch":"main"}'

After that succeeds, deploy normally:

wrangler pages deploy ./folder --project-name project-name --branch main

The account ID is visible in wrangler whoami. The OAuth token is in ~/Library/Preferences/.wrangler/config/default.toml (macOS).

Framer

Use for website rebuilds where the client or a non-developer needs to manage content after launch.

  • Claude Code / Codex locks the design and page structure.
  • Migrate the final design to Framer for CMS-level editing (text, images, pages).
  • Framer handles hosting, CDN, and SSL automatically.
  • GHL still handles forms, CRM, automations, and calendar — same as any other setup.
  • Connect GHL form via JS embed or use a Framer form that posts to a GHL webhook.

Do not use Framer for campaign landing pages where Google Ads Quality Score or custom tracking logic is critical. Use WordPress or a static HTML page for those.

GHL

Use when:

  • External hosting access is blocked.
  • Speed to launch matters.
  • Native form logic is the main priority.

Build natively in GHL, section-by-section. Do not paste the whole page into one code block for production.


5. GHL Native Fallback Build Process

If building in GHL:

  1. Create a new funnel/page in the client sub-account.
  2. Rebuild sections with native rows, columns, headings, images, buttons.
  3. Put global CSS in page custom CSS/header settings.
  4. Use GHL native form or survey.
  5. Configure conditional logic:
  6. Good fit → calendar.
  7. Poor fit → polite thank-you page.
  8. Add tags:
  9. Campaign name.
  10. Suitable.
  11. Not suitable.
  12. Booked.
  13. No booking.
  14. Follow-up required.
  15. Add hidden fields:
  16. UTM source.
  17. UTM medium.
  18. UTM campaign.
  19. UTM term.
  20. UTM content.
  21. GCLID if available.
  22. Landing page URL.
  23. Test workflow triggers.
  24. Test mobile layout.
  25. Test form → contact → tags → pipeline → calendar → reminders.

6. QA Checklist Before Showing Client

Copy QA

  • [ ] No internal terms visible.
  • [ ] H1 matches search intent.
  • [ ] Location is visible above the fold.
  • [ ] CTA is clear and repeated.
  • [ ] Price/investment expectation is transparent if needed.
  • [ ] Claims are careful and compliant.
  • [ ] FAQ answers real objections.

Design QA

  • [ ] Looks like the client brand.
  • [ ] Does not look like a cheap funnel template.
  • [ ] Uses real clinic/device/team assets where possible.
  • [ ] Mobile layout is clean.
  • [ ] Buttons are tappable.
  • [ ] No text overlap.
  • [ ] No oversized desktop-only typography breaking mobile.

Technical QA

  • [ ] Page loads on target URL.
  • [ ] Images load from real hosted URLs, not local paths.
  • [ ] No base64 production images unless unavoidable.
  • [ ] Form submits.
  • [ ] Suitable path works.
  • [ ] Unsuitable path works.
  • [ ] Calendar works.
  • [ ] GHL contact is created/updated.
  • [ ] Tags apply correctly.
  • [ ] Pipeline stage updates.
  • [ ] Internal task fires where needed.
  • [ ] GA4 sees page view.
  • [ ] Google Ads conversion fires on correct event.
  • [ ] Privacy policy link works.

7. Common Mistakes To Avoid

  • Pasting the entire custom page into one GHL custom HTML block and expecting it to be editable.
  • Pasting into GHL with relative assets/ paths — images will silently fail. Always replace with absolute Cloudflare URLs first (see Option C sed command). Do not forget srcset= and imagesrcset= attributes — src= alone is not enough.
  • Forgetting the GHL width escape CSS on .page — the page renders narrower than the design without it.
  • Building a premium page visually but leaving internal setup copy visible.
  • Using GHL iframe embeds and assuming external form tracking has full field visibility.
  • Sending all form submissions to Google Ads as conversions, including low-fit leads.
  • Using discount language for premium/high-ticket clinic offers.
  • Letting two systems send appointment reminders.
  • Hosting a paid-search page on an unrelated Aurea domain when the client domain is available.
  • Forgetting privacy policy, address, business identity, and contact details.
  • Waiting for perfect automation before launching a manual-sync pilot.

8. Handoff To Builder / VA

When handing to a builder or VA, include:

  • Client file path.
  • Final page URL target.
  • Local mockup path.
  • Assets folder.
  • Page structure.
  • Form questions.
  • Routing logic.
  • Tags/custom fields.
  • Workflow list.
  • Calendar link.
  • Reminder ownership.
  • Tracking events.
  • Launch checklist.

Template:

Client:
Page purpose:
Target URL:
Local mockup:
Assets:
Primary CTA:
Form type:
Qualified path:
Not suitable path:
Calendar:
Booking system:
Reminder owner:
GHL tags:
Custom fields:
Tracking events:
Open access needed:
Client review date:

9. Design Standards

Master Skill

For future client landing-page builds, use the Codex skill:

client-landing-page-builder

Use it when John gives a client/prospect name and asks for a landing page, campaign page, website demo, GHL form test, or externally hosted client-domain page. The skill is located at:

/Users/johntossou/.codex/skills/client-landing-page-builder/SKILL.md

It is the orchestration layer for this SOP. It should read the client file, gather local assets, inspect/scrape the website where appropriate, use the relevant design/conversion skills, decide GHL vs external hosting, build the page, add form logic, test via HTTPS, and return the preview path/URL plus open items.

Skills To Invoke

Invoke skills by phase. Do not build from scratch without loading the relevant framework first.

Use the full stack for paid traffic, premium clinic offers, externally hosted pages, or any page that will be sent to a client. Use a lighter stack only for internal mockups, wireframes, or quick one-hour tests.

Phase 1 — Plan (before writing any code)

Task Skill
Orchestrate the whole client landing-page workflow /client-landing-page-builder
Define page structure and conversion logic /landing-page
Establish design system — colour palettes, font pairings, UX patterns /ui-ux-pro-max
Positioning context for repeat clients or multi-page builds /product-marketing-context
Customer language, review mining, objections, and decision triggers /customer-research

Phase 2 — Build (while writing HTML/CSS)

Task Skill
Aesthetic guardrails — what to do and what never to do /impeccable
Premium/luxury visual direction and agency-level restraint /high-end-visual-design
Taste pass for less template-like frontend execution /design-taste-frontend
Copy and value proposition /copywriting
Edit existing copy for clarity, concision, and premium tone /copy-editing
Conversion psychology — framing, anchoring, social proof, objection handling /marketing-psychology
Page-level CRO — offer, proof, objections, CTA hierarchy, section order /page-cro
Pricing/course framing, value anchoring, deposits, packages /pricing-strategy

Phase 3 — Refine (after first draft exists)

Task Skill
Typography — scale, pairing, leading, optical corrections /typeset
Spacing and visual rhythm — fix monotonous grids, weak hierarchy /layout
Remove excess cards, claims, badges, or noisy complexity /distill
Tone down pages that feel too salesy, loud, or synthetic /quieter
UX microcopy, form labels, reassurance copy, and unclear instructions /clarify
Motion — purposeful entrances, hover states, micro-interactions /animate
Colour — add warmth or expressiveness if design feels flat /colorize
Shadows — polished elevation on cards and panels (Tailwind projects) /beautiful-shadows
Tasteful brand moments where appropriate /delight

For premium aesthetic/medical pages, prefer /distill, /quieter, /typeset, and /layout before reaching for louder visual skills. /bolder, /delight, and /overdrive are optional, not default. Use them only when the brand or brief can carry them.

Phase 4 — QA (before client review)

Task Skill
Form friction, field order, qualification logic, reassurance, embed behaviour /form-cro
Metadata, headings, crawlability, local SEO, and Quality Score support /seo-audit
Structured data — local business, service, FAQ, offer where relevant /schema-markup
AI search/entity optimisation for authoritative treatment pages /ai-seo
Events, GA4/GTM contracts, CTA clicks, form load, submission, thank-you conversion /analytics-tracking
UX audit — visual hierarchy, cognitive load, anti-pattern detection /critique
Responsive / mobile pass — breakpoints, touch targets, fluid layout /adapt
Performance — image weight, lazy third-party embeds, animation cost /optimize
Technical QA — accessibility, contrast, errors, responsive checks /audit

Phase 5 — Finish

Task Skill
Final polish — micro-details, consistency, last-mile quality /polish
Intentional A/B test plan for traffic-backed iterations /ab-test-setup
Cloudflare deploy and Pages/Workers checks /cloudflare:wrangler
Browser inspection of local/live page, CTA clicks, mobile states /browser-use:browser

Minimum for every client-facing build: /client-landing-page-builder + /landing-page + /ui-ux-pro-max + /impeccable + /copywriting + /marketing-psychology + /page-cro in Phase 1–2, then /form-cro + /seo-audit + /analytics-tracking + /critique + /adapt + /optimize before sending to client.

Premium/luxury clinic stack: add /high-end-visual-design, /design-taste-frontend, /typeset, /layout, /distill, /quieter, /clarify, and /polish. The desired feeling is expensive, calm, clinically credible, and easy to act on — not decorative, glossy, or over-animated.

Documentation best practice: when a page ships, record the skills used, deploy URL, tracking hooks, GHL form/calendar IDs, and open test items in the project notes or handoff. This prevents the next pass from guessing which quality layers were already applied.

Key distinction — /impeccable vs /ui-ux-pro-max: /impeccable is philosophy and guardrails (what never to do, banned CSS patterns, font reject list). /ui-ux-pro-max is the reference catalogue (161 colour palettes, 57 font pairings, 99 UX guidelines, 50+ styles). Use both together — one sets the rules, the other gives you the options.

Font Selection

Do not use fonts from the impeccable skill reject list. Key banned fonts include: Cormorant Garamond, Playfair Display, Fraunces, DM Sans, DM Serif Display, Inter, Plus Jakarta Sans, Instrument Sans, Instrument Serif, Space Grotesk, Syne, IBM Plex Sans, Lora, Crimson Pro, Newsreader, Outfit.

Process:
1. Write 3 words that describe the brand voice (e.g. warm, clinical, precise).
2. List the fonts you would normally reach for — reject them.
3. Find alternatives on Google Fonts, Pangram Pangram, or Future Fonts.
4. Pair a distinctive display/serif with a clean body sans.

Colour

Use OKLCH throughout, not hex or HSL. OKLCH is perceptually uniform — equal lightness steps look equal.

:root {
  --ink:   oklch(17.5% 0.008 52);   /* near-black, warm tinted */
  --muted: oklch(47% 0.013 57);     /* secondary text */
  --cream: oklch(95% 0.019 82);     /* warm background */
  --green: oklch(33% 0.065 196);    /* brand accent example */
}

Tint all neutrals toward the brand hue at low chroma. Pure black (#000) and pure white (#fff) should not appear.

Banned CSS Patterns

These patterns mark a page as AI-generated and must never appear:

  • border-left: Xpx solid [colour] on cards, alerts, or list items — any width greater than 1px
  • background-clip: text with a gradient (gradient text fill)
  • Glassmorphism used decoratively everywhere (blur + glow borders on every card)
  • Identical card grids (same icon + heading + text repeated 3-6 times)

Spacing

Use a 4pt spacing scale with semantic tokens:

--sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px;  --sp-4: 16px;
--sp-5: 20px; --sp-6: 24px; --sp-8: 32px;  --sp-12: 48px;
--sp-16: 64px; --sp-24: 96px;

Vary spacing for hierarchy. Do not apply the same padding to every section.


10. Post-Launch Edit Ownership

Before choosing the hosting setup, answer: who needs to make copy or image changes after launch, and how often?

This question drives the hosting and CMS decision more than anything else.

Who edits How often Right setup
John via Claude Code Occasionally, on request Static HTML on Cloudflare Pages or client domain. Redeploy with one command. No CMS needed.
VA (Camille) for copy changes Regularly, without involving John WordPress + ACF. VA edits text fields in WP admin. Design stays locked.
VA (Camille) for layout/section changes Regularly GHL native builder. VA stays in her trained environment. Accept design constraints.
Client (Kelsey/Tracey) self-service Occasionally WordPress + Elementor or Framer. Accept some design compromise for the independence.
Nobody — campaign page frozen after launch Never Static HTML anywhere. No CMS overhead needed.

The Bottleneck Problem

Custom HTML pages built with Claude Code are fast to update (describe the change, Claude edits, redeploy in 60 seconds) but only if John is the person making the change. If Camille is supposed to handle landing page copy updates and she does not have Claude Code access, John becomes the bottleneck for every copy edit.

Rule: if the plan is for Camille to own copy updates on a page, do not build it in plain HTML. Use WordPress + ACF or GHL native so she can edit without code.

Rule: if the plan is for John to own the page with Claude Code, static HTML is fine and is actually faster than any CMS for iterating.

When Pitching The Custom Build To A Client

Be upfront about edit access before they commit. State clearly:

  • The page looks better and loads faster than a GHL builder page.
  • Copy or photo changes go through you (John), not self-service.
  • Turnaround for a text change is a few minutes.
  • If they want self-edit access, the option is WordPress + Elementor or ACF, with some trade-off on design quality.

Do not let a client assume they are getting a GHL-style drag-and-drop editor and then discover they cannot log in and change a headline.


11. File Organisation

Single Source Of Truth Rule

One file per project. Do not maintain two copies of the same page in different folders.

Correct location: client or prospect folder.

Clients/Prospects/[ClientName]/[project-folder]/index.html
Clients/Prospects/[ClientName]/[project-folder]/assets/

Do not duplicate into tools/ or .tmp/ for production files. Temporary previews in .tmp/ are disposable and should never become the source of truth.

If two copies exist, reconcile immediately: pick one, delete the other, update the client file and session log.


12. Demo-First Sales Pattern (Website Rebuilds)

For prospects who need to see before they commit:

  1. Build a premium one-page demo using Claude Code or Codex.
  2. Deploy to Cloudflare Pages for a shareable preview link.
  3. Send the link via WhatsApp with a short message: what you built, what can be changed, optional mention of ads if relevant.
  4. Wait for the prospect's reaction before scoping further.
  5. Once they respond positively, send pricing.
  6. Close on setup fee + monthly retainer.
  7. After agreement, rebuild properly with real assets and correct brand copy.

Do not send pricing before sending the demo. The demo anchors the value before the number lands.

The Cloudflare preview is a sales tool, not the final deliverable. The final build goes on the client's domain.


13. SEO Preservation (Website Rebuilds)

Before rebuilding any site that has existing indexed pages:

  1. Audit existing URLs — list every published page and its Rank Math score if available.
  2. Identify which pages Google has indexed — check Google Search Console if accessible, or run site:domain.com.
  3. Keep or redirect every indexed URL. Do not delete a page that Google has already indexed without a 301 redirect to an equivalent page.
  4. Preserve the homepage keyword target — if the current homepage ranks for a core term, the new homepage must target the same term.
  5. Rebuild treatment pages as SEO pages with: H1 containing the keyword, FAQ schema, local intent modifiers, internal links, and accurate alt text.
  6. Do not rely on Rank Math scores as proof of rankings. They are a checklist indicator, not a search position guarantee.

Recommended hybrid structure:

Homepage          → conversion-optimised, clean CTA, premium design
Treatment pages   → SEO-optimised, long-form, FAQ schema, keyword-rich

14. UK Aesthetics Compliance Rules

All copy on aesthetics clinic pages must follow these rules before going live.

Wording

Avoid Use instead
Botox Anti-wrinkle treatment, botulinum toxin treatment
Filler (as a casual shorthand) Dermal filler, lip filler
"Guaranteed results" "Expected outcomes", "results vary by individual"
"Permanent" "Long-lasting", "results that build over time"
"Safe" (as a blanket claim) "Clinician-led", "clinically appropriate", "suitable for..."
"No risk" "Minimal downtime", "non-surgical"

Before/After Photos

  • Add a disclaimer near any results imagery: "Results vary. Individual outcomes depend on the treatment area, skin type, and course followed."
  • Do not make before/after the primary hook. Use them as secondary proof, not the headline claim.

Regulated Treatments

  • Botulinum toxin (anti-wrinkle) can only be prescribed by a qualified prescriber in the UK.
  • Do not imply that a practitioner administers prescription-only medicines without confirming their prescribing status.
  • Do not use CQC-regulated language loosely (e.g. "medical clinic", "clinical", "doctor-led") unless the practitioner holds the relevant credentials.

Pricing

  • Showing price ranges is allowed and recommended for transparency.
  • Do not use "from £X" if the lowest price is not genuinely available.
  • Course pricing should be presented as a total investment, not disguised as a per-session number only.

15. Asset Management

Priority order for imagery

  1. Real clinic photos — reception, treatment room, device, practitioner.
  2. Real before/after results (with consent and disclaimer).
  3. Brand-aligned stock — only if nothing real is available and the stock image is not recognisably generic.

Requesting assets from the client

Send this request early, ideally before starting the build:

To get the page looking its best, I need:
- 2-3 photos of the clinic interior or reception
- 1-2 photos of the treatment device or setup
- A practitioner photo if they are happy to be featured
- Any before/after results with patient consent
- Logo in SVG or high-res PNG if available

While waiting for real assets

Use placeholder structure images from the assets folder if they exist, or a neutral dark/cream colour block as a background. Never use obviously generic stock (white-coat models, staged smiling patients, stock devices).

Mark placeholder images with an HTML comment so they are easy to find and replace:

<!-- TODO: Replace with real Exion device photo once received from client -->
<img src="assets/placeholder-device.jpg" alt="Treatment device at [Clinic Name]">

Image format and compression

  • Use WebP where possible for production (smaller file size, supported by all modern browsers).
  • JPEG is acceptable for photos. PNG only for logos or images requiring transparency.
  • Compress all images before uploading. Target: under 200KB per image for hero/above-fold, under 100KB for below-fold.
  • Use loading="lazy" on all below-fold images.
  • Use fetchpriority="high" on the above-fold hero image.

Quick macOS compression with sips (no extra tools needed):

# Resize to max 900px wide (maintains aspect ratio, overwrites in place)
sips -Z 900 assets/kelsey-headshot.jpg

# Batch compress all JPEGs in assets folder
for f in assets/*.jpg; do sips -Z 900 "$f"; done

For the Nurse Kelsey v2 build, compressing a 4MB headshot to 900px reduced it to 113KB with no visible quality loss at display size. Always compress before deploying. A 4MB hero image is a hard fail on mobile QS.


16. Final Rule

Build for the patient/customer first, then wire the backend.

The page should never expose the machinery behind the campaign. GHL, tracking, qualification logic, and manual sync are backend decisions. The visitor should only experience:

This is relevant to me.
This clinic feels trustworthy.
I understand what happens next.
I know whether this is likely within my budget.
I can request the right consultation without confusion.

Aurea Growth — AI-Powered SEO Guide for Aesthetics Clinics

Scope: Practical, low-overhead SEO playbook for UK aesthetics/dental clinics.
Who executes: John (strategy + Claude Code) or Camille (content implementation).
Leverage: Claude Code and Codex handle the heavy lifting — writing, schema, GBP copy, keyword clustering. You implement.


0. Platform Snapshot (What Each Site Already Has)

From Wappalyzer audit (May 2026):

Nova Beauty (Wix) Ayadi (WordPress)
Platform Wix WordPress + Elementor
SEO Plugin None detected Yoast SEO ✓
Analytics Facebook Pixel only Site Kit (GA + Search Console) ✓
CDN Google Cloud CDN SiteGround CDN
Schema Wix auto (partial) Yoast auto (full)
Hosting Wix managed SiteGround
Tech SEO readiness Moderate Strong

What this means:
- Ayadi has a stronger baseline. Yoast + Site Kit = ready to optimize immediately.
- Nova has no Google Analytics. First action: connect GA4 + Search Console via Wix dashboard.
- Both sites are missing treatment-specific pages. That is the #1 gap for both.


1. Priority Stack — What To Do First

Do not try to do everything at once. This order matters.

Month 1 — Foundation (no content yet, just setup)

Priority Action Who Time
1 Connect Google Analytics 4 to Nova Beauty (Wix) John 20 min
2 Connect Google Search Console to Nova Beauty (Wix) John 10 min
3 Claim and fully optimize Google Business Profile for both clinics John / Camille 1.5 hrs each
4 Audit and fix NAP consistency (name, address, phone) across all directories Camille 2 hrs
5 Set up automated review request flow (GHL post-visit SMS → Google review link) John 1 hr
6 Build keyword list for each clinic using Claude Code + SEMrush/free tools John 1 hr

Month 2 — Content Build

Priority Action Who Time
7 Create 8-12 treatment pages per clinic (Claude Code generates, Camille implements) Both 2-3 hrs/clinic
8 Add JSON-LD schema markup to every treatment page (Claude Code generates, paste in) Camille 30 min total
9 Optimize all meta titles and descriptions (Claude Code generates batch) Camille 1 hr
10 Write and implement GBP service descriptions for every treatment Camille 1 hr

Month 3 — GEO + Growth

Priority Action Who Time
11 Restructure existing content for GEO (AI search) using Claude Code reformat prompt Camille 2 hrs
12 Publish 2-4 blog posts targeting informational keywords (how much does X cost, etc.) Camille 2 hrs
13 Submit site to relevant directories (Doctify, Fresha, Treatwell profile if kept, Phorest) Camille 1 hr
14 First review audit — are reviews coming in? Adjust automation if not John 30 min

2. Google Business Profile — Full Optimization

This is free, high-leverage, and takes one afternoon. Do this before anything else.

Step-by-step setup

  1. Go to google.com/business and sign in with the clinic's Google account
  2. Claim the profile if not yet claimed
  3. Fill in every field. Do not leave anything blank.

Exact fields to complete

Business name: Exactly as it appears on signage — no keyword stuffing.

Primary category:
- Aesthetics clinic: "Medical Spa" or "Skin Care Clinic"
- Dental + aesthetics (Nova): "Dental Clinic" as primary, add "Medical Spa" as secondary

Secondary categories: Add all that apply:
- Laser Hair Removal Service
- Skin Care Clinic
- Medical Spa
- Beauty Salon (only if applicable)

Business description (250-300 words):
Use Claude Code to generate this (see prompt in Section 5). Key rules:
- Paragraph 1: Who you are, what you specialise in, why patients trust you (credentials, years of experience)
- Paragraph 2: Key treatments, differentiators (prescriber-led, non-invasive, medically supervised)
- Include location (East Finchley, North London) naturally — not forced
- Do NOT mention prices or offers here

Services tab: Add every treatment individually.
For each service include:
- Name of treatment
- Description (50-80 words, written by Claude — see prompt in Section 5)
- Price range or "Contact for pricing"

Photos: Minimum 20 photos.
- Clinic exterior (from street)
- Reception and waiting area
- Treatment rooms
- Before/after results (with patient consent)
- Team photos (practitioners with credentials visible)
- Equipment photos (device brand logos are trust signals)
- At least 1 new photo per month going forward

Hours: Exact, including special holiday hours. Keep these updated.

Q&A section: Pre-populate this yourself. Ask the 5 most common questions patients have and answer them. Claude Code can draft these (see prompt in Section 5).


3. Technical SEO — Platform-Specific Setup

3a. Wix (Nova Beauty)

Wix is not the ideal SEO platform but it is workable for a single-location clinic. Follow these steps.

Step 1: Connect Analytics (Critical — do this first)
- Wix dashboard → Marketing & SEO → Marketing Integrations
- Connect Google Analytics 4 (enter GA4 Measurement ID)
- Connect Google Search Console: Settings → Domains & Emails → Add Google Search Console

Step 2: Set up Wix SEO Basics
- Go to: Marketing & SEO → SEO → SEO Setup Checklist
- Complete every item in the checklist (takes 30 min)
- Enable the XML sitemap (it auto-generates — just make sure it's on)
- Set your primary domain (with or without www — pick one, stick to it)

Step 3: URL structure
- Keep URLs short and keyword-focused
- Format: /treatments/botox-north-london or /botox-north-london
- Avoid: /page-12345 or /copy-of-treatments
- In Wix: Edit page → Settings → SEO → Page URL

Step 4: Meta tags on every page
For each treatment page, fill in:
- SEO Title: [Treatment] in [Location] | [Clinic Name] (max 60 characters)
- Meta Description: 150-160 characters — state the treatment, location, and one differentiator

Use Claude Code to generate these in bulk (see Section 5).

Step 5: Add JSON-LD schema
Wix supports custom JSON-LD. For each treatment page:
- Edit page → Settings → Advanced → Additional meta tags
- Paste in the schema code generated by Claude (see Section 5)

Step 6: Image optimization
- Before uploading any image to Wix, rename the file descriptively
- Bad: IMG_3845.jpg
- Good: botox-north-london-nova-beauty.jpg
- After upload, add alt text to every image via the Wix image editor
- Use Claude to generate alt text in bulk from a list of image names

Key Wix SEO limitations to know:
- You cannot install SEO plugins like Yoast — all optimization is manual
- Wix's built-in performance is slower than WordPress on complex pages (acceptable for a clinic)
- URL customization is limited but sufficient for this use case
- Structured data is manual JSON-LD paste — Claude generates it for you

3b. WordPress + Yoast (Ayadi)

Ayadi already has the best possible setup. Yoast is installed, Site Kit is connected. Optimize the configuration.

Step 1: Yoast configuration
- Go to: Yoast → General → Features
- Make sure all are ON: SEO analysis, Readability analysis, Schema, XML Sitemaps

Step 2: Set your site identity in Yoast
- Yoast → General → Site Representation
- Choose "Organisation" (not individual)
- Add clinic name, logo, business type ("MedicalBusiness")

Step 3: Schema type for pages
- For treatment pages: Yoast → Schema → Page type = "Web Page", Article type = "None"
- Schema for services is added via custom JSON-LD (Claude generates it — see Section 5)

Step 4: Yoast for every treatment page
In the Yoast panel at the bottom of each page editor:
- Focus keyword: [treatment] [location] (e.g. "HIFU East Finchley")
- SEO title: [Treatment] in [Location] | Ayadi Beauty
- Meta description: 150-160 characters
- The Yoast traffic light will tell Camille if the page passes — aim for green on both SEO and Readability

Step 5: Internal linking
- Every treatment page links to at least 2 related treatment pages
- Every treatment page links to the contact/booking page
- The homepage links to all main treatment category pages

Step 6: Cornerstone content
In Yoast, mark your most important pages as "Cornerstone Content" — these get prioritised in Yoast's internal link suggestions.
Cornerstone pages: Botox, Fillers, HIFU, Laser Hair Removal, whatever the top 3 treatments by revenue are.


4. Treatment Page Structure — The Core SEO Asset

Every treatment needs its own dedicated page. This is the single highest-impact SEO action.

How many pages to build

Nova Beauty targets:
- Anti-wrinkle (Botox) — North London
- Dermal Fillers — North London
- HIFU — East Finchley
- Profhilo / Polynucleotides — North London
- Laser / Photofractional — East Finchley
- Dental Implants — East Finchley
- Invisalign — North London
- IV NAD+ Therapy — London (broader — less local competition)
- Exosome Therapy — London
- Microneedling — North London

That is 10 pages. Build all 10 before anything else.

Ayadi targets:
- EMSculpt Neo — North London (or BTL Exion if that replaced it)
- Geneo X Facial — East Finchley
- Body Contouring — North London
- Laser Hair Removal — East Finchley / North London
- Clinical Massage — East Finchley
- Facials — East Finchley

That is 6-8 pages. Each one targets a different keyword combination.

Exact page template

Use this structure for every treatment page. Claude Code generates the content — Camille pastes it in.

URL: /treatments/[treatment-name]-[location]
Example: /treatments/hifu-east-finchley

H1: [Treatment] in [Location] | [Clinic Name]
Example: HIFU Treatment in East Finchley | Nova Beauty Clinic

Opening paragraph (60-80 words):
State what the treatment is, who it is for, what results patients can expect,
and how long results last. Include the target keyword in the first sentence.

H2: What is [Treatment]?
Plain-language explanation. No medical jargon without explanation.
3-4 short paragraphs or a bullet list.

H2: How [Treatment] Works
Step-by-step process: before, during, and after.
Keep it factual and reassuring.

H2: Key Benefits
Bullet list of 5-8 patient-outcome benefits.
Focus on: results, downtime, how it compares to alternatives.
Example: "No surgery, no downtime — results in one session"

H2: Ideal Candidate
Who is a good fit for this treatment.
Who to consult on first (skin type, contraindications — brief and reassuring, not frightening).

H2: Results & Timeline
When they see initial results.
When results peak.
How long results last.
What affects longevity.

H2: Before & After
[Image gallery placeholder or actual images with alt text]

H2: Frequently Asked Questions
Minimum 6 questions. Format as question/answer pairs.
Must include: How much does [treatment] cost? Does [treatment] hurt?
How long does [treatment] last? How many sessions do I need?
What should I do before/after? Is [treatment] safe?

H2: Book Your [Treatment] Consultation
Short closing paragraph (2-3 sentences).
One clear CTA: a button or embedded form linking to the booking system.
Include phone number and WhatsApp if applicable.

Local keyword targeting

Format: [Modifier] + [Treatment] + [Location]

Use these location modifiers for East Finchley clinics:
- East Finchley
- North London
- N2 (postcode — some patients search by postcode)
- Finchley (without "East")
- Highgate (5 min away — searches spill over)
- Muswell Hill (adjacent)

Do not try to rank for "Botox London" — too competitive. Start with East Finchley and North London.


5. Claude Code Workflow — Prompts and Processes

This section is the core of the AI leverage. John or Camille uses these prompts. Claude Code does the work.

How the 6 workflows fit together

There are 6 workflows. They are not all equal. Read this before diving in.

F is always first. Run it once per clinic at the start. It gives you the keyword list that drives everything else — which treatments to target, which location modifiers to use, which blog topics to write. Do not run A, B, C, D, or E before you have run F.

A and E are always paired. Every time you generate a treatment page with A, you immediately run E on the output. A writes the page. E restructures it for AI search. One does not get published without the other. Think of them as one step in two parts.

B, C, D are one-time batch runs per clinic. Once all treatment pages are written (after all your A+E runs), run B to generate all meta titles and descriptions in a single prompt. Run C to generate all schema markup in one go. Run D once for GBP content (business description, service descriptions, Q&A). These do not repeat unless the clinic adds new treatments.

Sequence per clinic:

Step 1 — Run F once         → get keyword list
Step 2 — Run A+E per treatment → build all treatment pages
Step 3 — Run B once         → all meta titles + descriptions
Step 4 — Run C once         → all schema markup
Step 5 — Run D once         → all GBP content

How many times to run each workflow for Nova and Ayadi:

Workflow Nova Beauty Ayadi Notes
F (keyword research) 1x 1x Once per clinic at start
A+E (treatment pages) 10x 6-8x Once per treatment, always paired
B (meta batch) 1x 1x After all pages are written
C (schema batch) 1x 1x After all pages are written
D (GBP content) 1x 1x Once per clinic

Total runs for both clinics combined: roughly 20-22 A+E pairs, 4 batch runs (B, C, D, F x2).

Who runs what:

  • John runs F (keyword research requires judgment on which keywords to include)
  • John or Camille runs A+E (mechanical once you have the keyword list)
  • Camille runs B, C, D (batch prompts, pure execution)
  • Camille implements everything into Wix (Nova) or WordPress (Ayadi)

Workflow A — Generate a treatment page

Use this prompt in Claude Code (or in the Claude chat):

You are a medical aesthetics SEO copywriter for a UK clinic.

Write a complete, SEO-optimised treatment page for the following:
- Treatment: [INSERT TREATMENT NAME]
- Clinic: [INSERT CLINIC NAME]
- Location: [INSERT AREA, e.g. East Finchley, North London]
- Primary keyword: [e.g. "HIFU East Finchley"]
- Secondary keywords to include naturally: [e.g. "HIFU North London, face lifting East Finchley, non-surgical facelift N2"]
- Clinic differentiators: [e.g. "medically supervised, 25 years experience, Obagi-certified"]
- Target word count: 900-1,100 words

Page structure required:
H1 → Opening paragraph → H2: What is it → H2: How it works → H2: Benefits (bullet list) → H2: Ideal candidate → H2: Results & timeline → H2: FAQs (6 questions minimum) → H2: Book a consultation

Tone: Authoritative and warm. Clinical but not cold. No jargon without explanation. Short paragraphs (2-3 sentences max). Definitive statements, not vague claims.

Do NOT include: Prices (unless instructed), competitor names, unsubstantiated superlatives ("best in London").

Output the full page content ready to paste into [Wix/WordPress].

Workflow B — Generate meta titles and descriptions in batch

When you have a list of treatment pages ready, run this once:

Write SEO meta titles and meta descriptions for the following treatment pages at [CLINIC NAME] in [LOCATION].

Rules:
- Title: max 60 characters, format = [Treatment] in [Location] | [Clinic Name]
- Description: 145-155 characters, include treatment + location + one differentiator + implicit CTA
- No clickbait, no ALL CAPS, no em dashes

Pages:
1. Botox / Anti-wrinkle injections
2. Dermal fillers
3. HIFU face lifting
4. Profhilo
5. Microneedling
[Continue for all pages]

Output as a table: | Page | Title | Description |

Workflow C — Generate JSON-LD schema for a treatment page

Run this per treatment page (or batch it):

Generate valid JSON-LD structured data markup for the following:

Page type: Service page for a UK medical aesthetics clinic
Clinic name: [CLINIC NAME]
Clinic address: [FULL ADDRESS WITH POSTCODE]
Clinic phone: [PHONE]
Clinic website: [URL]
Treatment: [TREATMENT NAME]
Treatment description: [1-2 sentences]
Price range: [e.g. "£180–£400" or omit if not sharing prices]
Provider type: MedicalBusiness

Include: LocalBusiness + MedicalBusiness schema, Service schema for the treatment, AggregateRating if provided.

Output: Production-ready JSON-LD code block only. No explanation.

Paste the output into:
- Wix: Edit page → Settings → Advanced → Additional meta tags
- WordPress: Use a plugin like "Schema Pro" or paste in the page's Custom HTML block

Workflow D — Generate GBP content

Business description:

Write a Google Business Profile description for [CLINIC NAME], a [type of clinic, e.g. "doctor-led medical aesthetics clinic"] in [LOCATION].

Details:
- Key treatments: [list top 5]
- Credentials: [e.g. "prescriber-led, CQC registered, 25 years experience"]
- Differentiators: [e.g. "no Groupon deals, no discounts, outcome-focused"]
- Target patient: [e.g. "discerning patients in N2 and surrounding areas"]

Rules:
- 200-300 words
- Include location (area name and postcode) naturally
- Do not mention prices
- Do not use em dashes
- No generic phrases like "state of the art" or "cutting edge"
- End with a soft CTA: "Book a consultation" or "Get in touch"

Service descriptions (GBP):

Write Google Business Profile service descriptions for the following treatments at [CLINIC NAME]:
[List of 8-10 treatments]

Rules per description:
- 60-80 words each
- What the treatment does + who it is for + one key benefit
- Include location if it fits naturally
- No prices
- No em dashes

Output as a list: Service name → Description

GBP Q&A pre-population:

Write 8 Google Business Profile Q&A entries for [CLINIC NAME], an aesthetics clinic in [LOCATION].

Questions should cover:
- Opening hours and booking process
- Whether the clinic accepts walk-ins
- What to expect at a first consultation
- Whether treatments are medically supervised
- How to find the clinic (parking, public transport)
- Whether they cater to nervous or first-time patients
- What makes them different from beauty salons
- Cancellation policy

Output format: Q: [question] A: [answer, 40-60 words]

Workflow E — GEO restructure (AI search optimization)

Once a treatment page is drafted, run this to optimize it for ChatGPT, Perplexity, and Google AI Overviews:

Rewrite the following treatment page content to be optimized for AI search engines (ChatGPT, Perplexity, Google AI Overviews).

Rules for AI-search optimization:
- Maximum 3 sentences per paragraph
- Lead every section with the direct answer first
- Convert any long paragraphs into bullet lists where possible
- Add specific numbers and timelines (e.g. "results appear within 3-7 days" not "results appear quickly")
- Replace vague claims with definitive statements
- Add a short "Quick Summary" section at the top (4-6 bullet points: what it is, who it is for, how long results last, downtime, price range if available)

Do not change the page structure or headings. Only restructure the prose within each section.

[PASTE TREATMENT PAGE CONTENT HERE]

Workflow F — Keyword research with Claude

If you do not have SEMrush, run this to get a starting list:

I am doing local SEO for [CLINIC NAME], a [type of clinic] in [SPECIFIC AREA, e.g. East Finchley, North London, N2].

Generate a keyword list targeting local patients. Focus on:
1. Treatment-specific keywords with location modifier (e.g. "botox East Finchley")
2. Intent-based keywords (patients ready to book, not just browsing)
3. Question-based keywords for blog content (e.g. "how much does HIFU cost in London")
4. Adjacent keywords that capture patients earlier in the decision process

For each keyword, estimate:
- Search intent (commercial, informational, navigational)
- Approximate competition level (low/medium/high based on your knowledge)
- Which page on the site should target this keyword

Treatments to target: [list all clinic treatments]
Avoid: Brand keywords for other clinics, keywords targeting practitioners (not patients), national keywords too broad to compete on.

Output as a table: Keyword | Intent | Competition | Target Page

6. Review Automation — The Off-Page SEO Lever

Reviews are the fastest local SEO signal after treatment pages. Every new Google review improves local pack ranking.

Setup (GHL-based, already in John's stack)

  1. Create a GHL workflow: trigger = appointment completed (status change in GHL or Vagaro webhook)
  2. Wait: 60-90 minutes after appointment end time
  3. Send SMS: "Hi [name], thank you for visiting us today. If you have a moment, we'd love to hear how it went: [Google review link]"
  4. If no response after 48 hours: send one follow-up email with the same link
  5. Cap at 2 touchpoints — no more

Google review link format:
https://search.google.com/local/writereview?placeid=[PLACE_ID]
Get the Place ID from Google Business Profile → Manage profile → Get more reviews → Copy link

Review velocity targets

Both Nova and Ayadi have very few reviews relative to their quality. Targets:
- Month 1-3: 1 new review per week minimum
- Month 4+: 2 per week as patient volume grows

At 2 reviews/week, you reach 50 reviews in ~6 months. That alone moves a clinic from invisible to visible in local pack results for most treatment keywords in East Finchley.

What to do with reviews for SEO

  • Respond to every review within 48 hours. Include the treatment name and location naturally.
    Example: "Thank you so much for sharing your experience with our HIFU treatment in East Finchley..."
    This is free keyword placement in a Google-indexed response.
  • Flag and report any fake negative reviews promptly.

7. GEO — Optimising for AI Search (ChatGPT, Perplexity, Google AI Overviews)

AI-referred web traffic grew 527% year-over-year in early 2025. By the time your clients rank on traditional search, GEO is already the next frontier. Build it in from the start.

What GEO means in practice

When a patient asks ChatGPT or Perplexity "what is the best HIFU clinic in North London?", the AI generates an answer by pulling from web content. GEO is the practice of structuring your content so AI models are more likely to cite you.

It does not replace traditional SEO. You need to rank traditionally first — AI models pull from indexed, well-ranking content. GEO changes how the content is written and structured to increase citation likelihood.

GEO content rules (apply to every treatment page)

Paragraph length: 2-3 sentences maximum. AI models do not parse long blocks.

Lead with the answer: Every section should open with the direct answer, not a build-up.
- Bad: "There are many factors to consider when thinking about the duration of HIFU results..."
- Good: "HIFU results typically last 12-18 months for most patients."

Definitive statements: Use specific numbers and timelines.
- Bad: "Results can last a long time"
- Good: "Results typically last 12-18 months, depending on skin laxity and age at treatment"

Bullet lists: Convert prose descriptions into structured lists where possible. AI models extract bullets directly.

Quick Summary block: Add a "Key Facts" or "Quick Summary" section near the top of each treatment page with 4-6 bullet points:

Key Facts:
- Treatment time: 60-90 minutes
- Downtime: None
- Results visible: 2-3 months (collagen builds gradually)
- Results last: 12-18 months
- Sessions needed: 1 (top-up after 12 months)
- Price range: From £400

This is the block AI models are most likely to quote directly.

E-E-A-T signals: AI models prioritise content from demonstrably expert sources. Add practitioner credentials to every page (name, qualifications, years of experience). This is also an NHS/CQC trust signal for medical content.

GEO content to create separately (beyond treatment pages)

FAQ pages: A dedicated FAQ page per treatment is high-value for AI citation. Patients often ask AI tools questions like "how many sessions of laser hair removal do I need in the UK?" A well-structured FAQ page has a high chance of being cited.

Comparison content: "HIFU vs. surgical facelift: key differences" — AI models love comparison content. One blog post per competitive comparison, written using the GEO restructure prompt in Section 5.

Local authority content: "What to look for in an aesthetics clinic in North London" — written from the clinic's perspective. This positions the clinic as the authoritative voice and increases citation probability for local queries.


8. Blog Strategy — Only What You Can Sustain

Do not publish 100 blog posts. Publish 6-10 high-quality, GEO-optimized posts and keep them updated. Thin content published at scale is worse than no content.

Blog topics to prioritise (informational + high local intent)

Cost/pricing posts (high conversion intent):
- "How much does Botox cost in North London in 2026?"
- "HIFU price guide: what to expect in a London clinic"
- "Dental implants vs veneers: cost comparison in East Finchley"

Process/expectation posts (pre-decision research):
- "What to expect at your first Botox consultation in the UK"
- "How many sessions of laser hair removal do you need?"
- "What is the recovery like after HIFU?"

Adjacent content (top of funnel):
- "Signs you might benefit from Profhilo (and who it is not right for)"
- "HIFU vs. thread lifts: which is better for skin laxity?"

Use Workflow A (modified for blog format) in Section 5 to generate drafts. Then run Workflow E to GEO-optimise them.

Blog cadence

  • 1-2 posts per month is sufficient for a local clinic
  • Prioritise updating existing posts over creating new ones after 6 months
  • Always check: does this post target a keyword with real local search volume before writing it?

9. Directory & Citation Building

NAP consistency (Name, Address, Phone) across the web is a local ranking factor. Camille can do this in 2-3 hours once, then it only needs checking quarterly.

Directory list for UK aesthetics clinics

High priority (submit first):
- Google Business Profile (already covered)
- Bing Places for Business (free, often forgotten)
- Doctify (UK healthcare review platform — important for medical credibility)
- Fresha (if the clinic uses or plans to use it for booking)
- Treatwell (even if they are moving away from it as a booking channel, the listing still drives discovery)
- Yell.com
- Thomson Local
- Trustpilot

Secondary priority:
- Facebook Business Page (check address is correct)
- Instagram Business profile (check phone/website)
- Whatclinic.com
- Booksy (if relevant to treatment mix)

NAP format to use consistently everywhere:

Nova Beauty Clinic
260 East End Road
East Finchley
London
N2 8AU
020 5925 1232

Any variation in capitalisation, comma placement, or abbreviation (e.g. "Rd" vs "Road") counts as an inconsistency. Pick one format and replicate it exactly.


10. Ongoing Cadence — What To Do Each Week/Month

Weekly (Camille, 30-45 min)

  • Check if any new Google reviews came in — respond to all within 48 hours (use Workflow from Section 5 if needed: "Write a Google review response for a clinic that includes [treatment name] and [location] naturally, 40-60 words, warm and professional tone")
  • Check GBP for new Q&A submissions from patients — answer any within 24 hours
  • Flag any technical errors in Search Console (Ayadi) or Wix SEO dashboard (Nova)

Monthly (John or Camille, 1-2 hrs)

  • Review Search Console data: which queries are generating impressions? Which pages are ranking but not getting clicks? Prioritise those for meta description optimisation.
  • Publish or update 1-2 blog posts
  • Add 5-10 new photos to each clinic's GBP
  • Check review count and velocity — is the automation working?
  • Run one new treatment page through Claude if there are remaining treatments not yet covered

Quarterly (John, 1 hr)

  • Full keyword position check: are the treatment pages moving up in rankings?
  • Review NAP consistency across directories — anything changed?
  • Update any treatment pages with new clinical information or updated pricing notes
  • Update "2026" references in content to stay current (important for GEO — AI models favour recent content)

11. What Not To Do

Do not keyword stuff. Writing "botox east finchley botox north london botox n2" in a paragraph kills the page. Use each variant once, naturally.

Do not create thin location pages. The "zipper" method (service + city at scale) works for large national operators. For a single-location clinic, creating 40 near-identical pages triggers a duplicate content penalty. Build 10 high-quality unique pages, not 40 thin ones.

Do not ignore page speed. Especially on Wix — compress every image before uploading. Use TinyPNG or Squoosh. Large images are the most common cause of Wix page speed issues.

Do not build backlinks through link farms. This is still a penalty risk. The only link building to pursue: getting listed on Doctify, local press mentions (if Ehsan's PhD work generates press), and genuine patient review platforms.

Do not use AI-generated content without review. Claude Code generates the draft. A human reviews for accuracy (medical claims, correct treatment names, pricing) before publishing.


12. Measuring Success

After 60-90 days, check these metrics to know if SEO is working:

Metric Tool What to look for
Organic impressions Google Search Console Increasing month-over-month
Treatment page clicks Google Search Console Treatment pages appearing with clicks
GBP calls and directions Google Business Profile Insights Increasing post-optimisation
Google review count GBP Adding 1+ per week
Local pack appearance Manual search (incognito, location set to East Finchley) Do you appear in the 3-pack for any treatment?
Organic sessions GA4 Month-over-month growth

Do not obsess over keyword position before 90 days. SEO results take time. What you can measure early: review velocity, GBP views, and Search Console impression growth. Those indicate the foundation is working.


Appendix: Quick Reference — Claude Code Prompts at a Glance

Execution order (always follow this sequence)

1. F  — Keyword research         → run once per clinic, before anything else
2. A  — Treatment page           → run once per treatment (10x Nova, 6-8x Ayadi)
3. E  — GEO restructure          → run immediately after every A, never skip
4. B  — Meta titles + descs      → run once after all A+E pages are done
5. C  — Schema markup            → run once after all A+E pages are done
6. D  — GBP content              → run once per clinic (3 sub-prompts: description, services, Q&A)

Who does what

Task John Camille
Run F (keyword judgment) Yes No
Run A+E (content generation) Either Either
Run B, C, D (batch outputs) Either Yes
Implement into Wix (Nova) No Yes
Implement into WordPress (Ayadi) No Yes
Connect GA4 + Search Console (Nova) Yes No
Set up GHL review automation Yes No
Optimise GBP (upload photos, fill fields) Either Yes

Prompt location reference

Workflow Section Runs per clinic Paired with
F — Keyword research 5 — Workflow F 1 Nothing — run first
A — Treatment page 5 — Workflow A 1 per treatment Always run E immediately after
E — GEO restructure 5 — Workflow E 1 per treatment Always follows A
B — Meta batch 5 — Workflow B 1 After all A+E done
C — Schema batch 5 — Workflow C 1 After all A+E done
D — GBP content 5 — Workflow D 1 After all A+E done
Review response Section 9 Weekly ongoing

Clinic Omnichannel AI Stack — Decision Playbook

Last updated: 2026-05-06
Source client: Elhama Beauty Boutique (setup April 2026)

This document captures every decision, constraint, failure mode, and architecture pattern from building a full omnichannel AI system for a UK aesthetics clinic. Use it at the start of every new client engagement to avoid re-learning the same lessons.


What This Stack Does

A single system that:
- Handles all inbound messages (WhatsApp, Instagram DM, Facebook DM, SMS) via AI triage
- Handles all inbound phone calls via an AI voice agent using the clinic's own cloned voice
- Stores every interaction in a CRM, logs calls, extracts contact details automatically
- Lets the clinic owner see everything in one inbox without touching a phone


Stack Components

Tool Role Required?
GHL (GoHighLevel / LeadConnector) Unified inbox, CRM, workflows, WhatsApp Business API, Instagram/Facebook DMs, contact management Always
Appointwise AI triage agent for chat channels (WhatsApp, Instagram, Facebook, SMS). Sits on top of GHL. Yes for chat AI
Vapi AI voice agent for inbound phone calls. Handles call, collects details, generates transcript + summary Yes for voice AI
Twilio Phone number infrastructure. Buy numbers here. Vapi connects via Twilio. Yes (via Vapi)
ElevenLabs Voice cloning. Clone the owner's voice and assign it to the Vapi agent Yes if voice clone needed
n8n Automation bridge for complex logic between systems Optional — not needed for v1
Meta Business Manager Required to connect Instagram DMs and Facebook DMs to GHL Yes if using Meta channels

Channel Coverage — What Is and Is Not Possible

Channel AI Triage Notes
WhatsApp messages Yes Via GHL WhatsApp Business API + Appointwise
Instagram DMs Yes Via Meta Business Portfolio connected to GHL
Facebook DMs Yes Via Meta Business Portfolio connected to GHL
SMS (inbound) Yes Via GHL LC Phone number
Inbound phone calls Yes Via Vapi + carrier forwarding
WhatsApp voice calls No Hard technical limit — see below
Outbound SMS follow-up post-call Yes, with conditions Requires LC Phone number with SMS capability

WhatsApp Voice Calls — Hard Limit

When a number is connected to WhatsApp Business API, it cannot receive WhatsApp in-app voice or video calls. The call shows as failed or dropped on the caller's screen. GHL sees nothing — no notification, no contact record created. This cannot be fixed technically.

Mitigation:
1. Add a note to the business WhatsApp bio: "For calls, please use the regular phone number. For messages, WhatsApp us here."
2. Send a broadcast to existing contacts before go-live explaining the change
3. First-message GHL automation: when a new contact messages, the opening reply includes "If you tried to call via WhatsApp and it didn't connect, please call [number] directly."

Accept this as a trade-off. The client must understand it before the number goes live on the API.


The "Same Number" Problem

Every clinic wants one number that does everything. Here is the reality:

What they want What is actually possible
07501 914514 handles WhatsApp messages Yes — connect to GHL WhatsApp Business API
07501 914514 receives carrier calls and Vapi picks up Yes — set carrier forwarding to Vapi number
07501 914514 receives WhatsApp voice calls No — hard limit, see above
SMS sent back to caller from same Vapi number Depends on number capability — see below
One number for everything Achievable for 3 of the 4 above

Number architecture in practice:

The client keeps their existing mobile number (e.g. 07501 914514). It gets two roles:
1. Connected to GHL WhatsApp Business API — handles all messages
2. Carrier-level call forwarding to a separate Twilio/Vapi number — handles all calls

The Vapi number is a separate UK number (e.g. +44 1923 311259) bought in Twilio. Callers dial the clinic number, get forwarded, and Vapi answers.

Client dials 07501 914514
       |
       ├── Carrier-level forwarding → +44 1923 311259 (Vapi)
       |         Vapi answers, collects details, logs to GHL
       |
       └── WhatsApp message → GHL inbox → Appointwise AI triage

Call Forwarding — Unconditional vs Conditional

Two options. Decide with the client.

Type Behaviour Use case
Conditional (busy / no answer) Vapi picks up only if the owner doesn't Owner wants to answer sometimes
Unconditional (always forward) Vapi always answers, owner never picks up Owner is always working and cannot answer

For clinics like Elhama: unconditional is almost always the right call. Owner-operators doing hands-on treatments physically cannot break to take calls.

Unconditional forwarding dial codes (UK carriers):
- All carriers: **21*+441923311259# (replace with actual Vapi number)
- To cancel: ##21#

Confirm the client's carrier before go-live — codes vary slightly.


Voice AI — GHL Native vs Vapi

Option Status Notes
GHL Native Voice AI Unreliable in production (tested April 2026) Call forwarding failed in production on tested GHL numbers. Not ready for live client use. Review again after 30+ days.
Vapi Reliable, production-tested Preferred for all live client voice work. Connects via Twilio. Full transcript, recording, structured data extraction.

Decision: always use Vapi for voice in v1. Revisit GHL native at 30 days.


Voice Cloning with ElevenLabs

When to do it: at the first in-person meeting, once the client has seen the system and trusts it enough to want their voice on it.

Process:
1. Client reads a 2-minute script (natural, conversational — not a formal read)
2. Record audio (phone voice memo is fine)
3. Upload to ElevenLabs → Instant Voice Clone → name it clearly
4. Copy the voice ID
5. PATCH the Vapi assistant: {"voice": {"provider": "11labs", "voiceId": "VOICE_ID"}}

What to expect: the clone will not be perfect on the first attempt. The client may describe it as "too fast" or "too English" — this is a prompt issue as much as a voice issue. Soften the system prompt alongside the voice change.

System prompt tone for clinic voice agents:
- Warm, unhurried, conversational UK English
- One question at a time — never stack questions
- Use connectors: "Of course", "No worries at all", "That sounds lovely", "Absolutely"
- Frame photo review / qualification as a caring personal touch, not a barrier
- Never invent pricing, availability, or clinical advice


Vapi → GHL Integration

Vapi does not natively sync with GHL. The bridge is a GHL Inbound Webhook workflow.

Architecture

Vapi call ends
    ↓
Vapi fires end-of-call-report webhook → GHL Inbound Webhook trigger
    ↓
GHL workflow runs:
  1. Create/Update Contact (phone number)
  2. Log External Call (direction, status, recording URL)
  3. (optional) Send SMS / WhatsApp follow-up

Structured Data Extraction

Vapi can extract structured fields from every call automatically. Configure via analysisPlan.structuredDataSchema on the assistant:

{
  "analysisPlan": {
    "structuredDataSchema": {
      "type": "object",
      "properties": {
        "callerName": {"type": "string"},
        "callerEmail": {"type": "string"},
        "callerPhone": {"type": "string"},
        "treatmentInterest": {"type": "string"}
      }
    },
    "structuredDataPrompt": "Extract caller name, email, phone number stated during call, and treatment interest. Leave blank if not mentioned."
  }
}

These fields appear in the webhook payload at message.analysis.structuredData.* and can be mapped directly in GHL workflow actions.

GHL Workflow — Critical: Loop Lock Prevention

Problem: Vapi fires multiple webhook events per call (status-update, transcript, assistant-request, end-of-call-report — typically 6–10 events per call). If all hit the GHL workflow, it runs 10+ times per call. GHL detects this as a loop (threshold: 50 workflow starts per contact in 30 minutes) and auto-locks the workflow to Draft. Even a NO-branch exit counts as a start, so an If/Else filter inside GHL alone is not enough once test volume builds.

Fix — two steps, in this order:

Step 1 (do first — eliminates the problem at source): Vapi dashboard → your assistant → Advanced → Server Messages → set to ["end-of-call-report"] only. Vapi stops sending all intermediate events. GHL never sees them. This must go in before the workflow is republished.

Note: serverMessages filter only works on Vapi Assistants — not Vapi Workflows.

Step 2 (secondary safeguard): Add an If/Else branch as the first action after the Inbound Webhook trigger:
- Condition: {{message.type}} equals end-of-call-report
- Yes branch: all actions (Create Contact, Log Call, field mappings) run here
- No branch: workflow ends immediately

Never publish the GHL webhook workflow without both fixes in place.

If the workflow gets loop-locked: contact GHL support via live chat and say "Our inbound webhook workflow was auto-locked to Draft due to loop detection. We have identified the root cause and have a fix ready. Please unlock the workflow so we can re-publish it." GHL will unlock it manually — their senior support team can do this. Do not attempt to republish before the Vapi serverMessages filter is set.

GHL Workflow — Field Mapping Reference

Confirmed working paths. Do NOT use common wrong guesses — they silently map nothing.

GHL Field Vapi Payload Path Notes
Contact Phone {{message.call.customer.number}}
Contact First Name {{message.analysis.structuredData.callerName}} Requires structuredDataSchema on assistant
Contact Email {{message.analysis.structuredData.callerEmail}}
Custom — treatment interest {{message.analysis.structuredData.treatmentInterest}}
Custom — call summary {{message.summary}}
Custom — transcript {{message.artifact.transcript}} NOT message.transcript — that path does not exist
Custom — recording URL {{message.artifact.recording.recordingUrl}} NOT message.recordingUrl or message.artifact.recordingUrl
Custom — call outcome manual dropdown no direct Vapi field — set after human review
Log Call — Direction inbound (hardcoded)
Log Call — From Vapi UK number (hardcoded, e.g. +441923311259)
Log Call — To {{message.call.customer.number}}
Log Call — Status completed (or map from {{message.endedReason}})
Log Call — Attachment (recording) {{message.artifact.recording.recordingUrl}} same corrected path as above

Do not map message.durationSeconds — this field does not exist in the Vapi end-of-call-report payload. Derive duration from call.startedAt / call.endedAt if needed later.

Re-entry Setting

Set the workflow re-entry to Allow every time. Every call must run the workflow, not just the first one per contact.


SMS Follow-up After Call

Constraint: UK Twilio local numbers often do not support SMS. Always check the number's capabilities in Twilio before assuming SMS is available. In the Configure tab, if you see "Messaging configuration is unavailable for this phone number" — SMS is not possible from that number regardless of GHL settings.

Options:

Option When to use
GHL LC Phone number (separate number) Fastest to set up. SMS comes from a different number but message identifies the clinic.
Twilio Connect (connect existing Twilio account to GHL LC Phone) Preferred for future clients. Lets GHL use numbers from your Twilio account for SMS. Requires GHL to confirm whether it conflicts with Vapi using the same number simultaneously.
WhatsApp template message post-call Cleanest option. Once the client's number is on WhatsApp Business API, send a WhatsApp follow-up from their own number. Requires a pre-approved message template. Available after go-live.
Full number migration to GHL Avoid. Moves the number out of your Twilio account, breaking Vapi.

Recommended for v1: skip SMS entirely. Add WhatsApp follow-up as a post-go-live workflow action using the client's main number.


Twilio Number Buying — What to Check

Before buying a Twilio number for any client:

  1. Capabilities — confirm Voice AND SMS are listed under Capabilities in Twilio console. UK local numbers (01xxx / 02xxx) often do not support SMS. UK mobile-style numbers (07xxx) usually do.
  2. Regulatory bundle — UK numbers require a Regulatory Bundle with an approved address. Create this before buying if it doesn't already exist.
  3. Messaging capability — after buying, check the Configure tab. If "Messaging configuration is unavailable" appears, you cannot send SMS from that number.

Meta / Instagram / Facebook Setup

What is needed

  • Client grants access to their Meta Business Portfolio (Business Manager)
  • Facebook Page must be connected to the portfolio
  • Instagram account must be switched to Professional and linked to a Facebook Page
  • GHL must be approved as a messaging integration in Meta Business Suite
  • All accounts must show "Connected" in GHL → Settings → Integrations

Critical visibility rule for GHL

For Meta-connected channels, Business Portfolio ownership alone is not enough.

The final gate that controls which Pages / Instagram accounts are actually visible to GHL is usually the Facebook login that authenticated LeadConnector:

Facebook -> Settings & privacy -> Business integrations -> LeadConnector -> View and edit

This is where the connected Facebook user can expose or hide the assets available to GHL.

If an account looks correctly connected in Meta but still does not appear in GHL:
1. Log into the exact Facebook user that connected LeadConnector
2. Open Settings & privacy -> Business integrations
3. Open LeadConnector -> View and edit
4. Confirm the correct Pages / Instagram accounts are enabled
5. Reconnect or refresh the Facebook/Instagram connection inside GHL if needed

Treat this as a first-line debug step on every clinic setup.

Common blockers

Blocker Fix
Instagram shows "Login needed" in Meta Client must log into Instagram and reconnect the account
2FA sent to an old/unavailable phone Find which phone holds the 2FA. That person must be physically present with the device to complete the connection.
Business partner's account linked to wrong number Same — the account holder must be present with the registered device
Instagram not linked to a Facebook Page Switch to Professional account first; Instagram prompts to create or link a Page during that process

Multi-account setup (e.g. business partner with separate Instagram)

If a client has a business partner with a separate Instagram driving traffic:
1. Add the partner's Instagram to the client's Meta Business Portfolio (Business Settings → Instagram Accounts → Request Access)
2. Connect as a second Instagram channel in GHL
3. The same Appointwise agent handles both — the partner does not need to do anything operationally

Requires the partner's agreement and Meta page access.

Important: even after the Page + Instagram are correctly linked in Meta, the second account may still stay invisible in GHL until the Facebook user who connected LeadConnector enables that asset in Business integrations -> LeadConnector -> View and edit.


WhatsApp Business API Registration

Pre-registration requirements

  • Meta Business Portfolio must be verified. Go to business.facebook.com → Settings → Security Center → Business Verification. Status must show Verified before GHL can register any number. Verification takes 2–5 business days after submitting documents.
  • Correct legal name in Meta. The legal business name entered in Meta Business Manager must match the Companies House certificate exactly — including "Limited." Any mismatch causes rejection.
  • Full address required. The address field in Meta Business Manager must contain the full registered address, not just "United Kingdom."
  • Primary document for UK businesses: Certificate of Incorporation from Companies House. Free to download. Covers both legal name and registered address. A utility bill alone is not accepted for the legal name — it can only supplement for address/phone verification.
  • Identity verification: Meta also requires a personal ID from the company director to complete verification. Passport is preferred. This must be done in person — the director must be physically present to upload their ID.
  • The phone number must be in an active SIM on the day of registration (Meta sends an OTP via SMS or voice call).

The number must be released from the WhatsApp Business App first

A phone number cannot be registered on the WhatsApp Business API while it is active on the WhatsApp Business App. This is the most common cause of the "account locked" error in GHL.

Fix: the client must delete their WhatsApp Business App account from within the app before the API registration can proceed. Uninstalling the app alone is not sufficient — the account deletion must happen inside the app.

Steps:
1. Open WhatsApp Business App on the client's phone
2. Settings → Account → Delete my account → confirm
3. Wait 5 minutes for Meta's servers to deregister the number
4. Then proceed with GHL registration

Important: warn the client that this creates a downtime window of approximately 20–30 minutes. Plan it for a quiet period — not mid-clinic.

GHL registration sequence (embedded signup via 360dialog)

  1. GHL Settings → WhatsApp → Connect a new number
  2. Log in with the Facebook account that has Business Manager Admin access to the Meta Business Portfolio
  3. Select the correct business portfolio
  4. Enter the phone number in international format: +447XXXXXXXXX — never with a leading 0
  5. Choose Voice Call for OTP delivery (more reliable than SMS for UK O2 numbers)
  6. Client receives call on their phone, reads out the 6-digit code
  7. Enter the code in GHL
  8. Set display name (trading name the client wants customers to see)
  9. Test immediately — send a message to the number from a separate phone and confirm it appears in the GHL inbox

What the client loses when the number goes on the API

  • WhatsApp in-app voice and video calls (hard technical limit — see above)
  • The number is no longer usable on the WhatsApp Business App

What the client gains

  • Unified inbox in GHL — all WhatsApp messages centralised
  • AI triage via Appointwise
  • Broadcast campaigns to opted-in contacts
  • Automated follow-up workflows

Common errors and fixes

Error Cause Fix
"Account locked" Meta Business Portfolio not verified, OR number still active on WhatsApp App Check verification status in Security Center first; if verified, ensure client has deleted WhatsApp App account
"Number already registered" Account deletion not fully processed Wait 10 more minutes, retry
OTP not arriving SMS blocked by carrier Switch to Voice Call option
Documents rejected Legal name mismatch between Meta and Companies House Check exact name — must include "Limited" if that is the registered name

One-time WhatsApp history import: during setup, GHL supports importing up to 6 months of WhatsApp Business history. This is a one-time process — do it at setup, not after.


Contact Data Migration

Before connecting any live number:
1. Export contacts from all devices (iPhone via iCloud → .vcf, Android via Google Contacts)
2. Merge and deduplicate
3. Import into GHL subaccount

Do not migrate the live number until the contact import is done. Some CRMs treat number connection as a trigger for history import — doing it in the wrong order risks losing data.


Go-Live Order

Always follow this sequence. Do not shortcut it.

  1. Export and import contacts into GHL subaccount
  2. Build and test the AI triage agent (Appointwise) on a separate test WhatsApp number — not the live number
  3. Run 3-7 days internal stress testing on the test line
  4. Refine AI tone, photo timing, pricing handling, and handoff rules from test chats
  5. Set up and test Vapi voice agent independently — call the Vapi number directly
  6. Validate Vapi → GHL webhook end to end (contact created, call logged, structured data populated)
  7. Connect Instagram and Facebook DMs — confirm Meta permissions first
  8. Register live WhatsApp number on GHL only when test line is stable
  9. Set carrier call forwarding to Vapi number only after WhatsApp is live and tested
  10. Run full end-to-end test across all channels
  11. Go live — AI qualifies, human books manually for v1
  12. Set up Stripe recurring billing — only charge when full system is live

Appointwise Setup Notes

  • One Appointwise agent can handle multiple channels in the same GHL subaccount (WhatsApp, Instagram, Facebook, SMS)
  • Trigger pattern for v1: Customer Replied → apply AW-Active-[ClientName] tag
  • Stop logic at photo / qualification stage: handle in CTA and prompt instructions, not a separate trigger
  • Fixed prices, from prices, and consultation-led pricing all handled in the prompt and knowledge base
  • Multilingual: instruct the AI to mirror the lead's language

Billing Setup

  • Never start billing before the full system is live and tested
  • Agreed billing trigger: all channels live, tested, and client has seen the system working
  • Standard: Stripe recurring, £250/month, auto-debit
  • Keep a clear written record of the agreed go-live date and billing start date

Common Failure Modes

Failure Root Cause Fix
GHL workflow loop-locked Vapi fires 6–10 events per call; GHL counts each workflow start toward the 50/30min lock threshold Step 1: set Vapi serverMessages to ["end-of-call-report"] only (eliminates events at source). Step 2: add {{message.type}} = end-of-call-report If/Else filter as first GHL action. Both required.
Workflow re-locks after republish Vapi serverMessages not set before republishing — GHL still gets hit with all intermediate events Set Vapi serverMessages filter FIRST, then republish GHL workflow. Order matters.
Transcript or recording URL not mapping Using wrong payload paths (message.transcript, message.recordingUrl) Use {{message.artifact.transcript}} and {{message.artifact.recording.recordingUrl}} — the outer artifact wrapper is required
Contact created but name/email missing Structured data not configured on Vapi assistant Add analysisPlan.structuredDataSchema to Vapi assistant, map fields in GHL Create Contact action
SMS action fails in GHL No LC Phone number in subaccount, or Twilio number doesn't support messaging Check Twilio capabilities first; buy LC Phone number or use WhatsApp template instead
Vapi not answering calls Server URL not set on Vapi assistant PATCH {"serverUrl": "GHL_WEBHOOK_URL"} on the Vapi assistant record
Instagram DMs not routing Instagram account not connected or shows "Login needed" Client must reconnect Instagram login in Meta Business Manager
2FA blocks Meta connection 2FA registered to an unavailable phone (old number, old device) Find who holds the registered number; that person must be present with the device
Voice agent sounds rushed / too formal Default voice + generic prompt Clone client's voice via ElevenLabs; rewrite prompt with warm UK connectors, one question at a time
WhatsApp calls dropping silently Number on WhatsApp Business API — cannot receive WA calls Explain to client, add bio note, send pre-go-live broadcast
Carrier forwarding not working Wrong dial code for carrier, or number not on compatible carrier Confirm carrier, get exact code, test with a second phone before go-live

Vapi + GoHighLevel Best Practices

Plain-English reference for what we learned while testing Vapi with GoHighLevel / LeadConnector.

Last updated: 2026-04-07


Production Findings — Elhama Beauty (2026-05-04)

This section documents what was discovered and fixed during the live Elhama Beauty Vapi → GHL setup. These are production-validated findings, not theory. Apply them to every future client using this stack.


Critical: Vapi fires multiple webhook events per call

By default, Vapi sends every event type to the server URL — not just the final end-of-call-report. Events fired per call include:

  • assistant-request
  • status-update
  • transcript
  • function-call
  • hang
  • end-of-call-report

All fire to the same URL within the same second. GHL runs the workflow once per event. On a single call, the same contact gets enrolled 6–10+ times in 2 seconds. GHL's loop detection locks the workflow at 50 starts per contact within 30 minutes.

This will happen on every new Vapi → GHL client if not fixed before the first test call.


Fix 1 — Set serverMessages on the Vapi assistant (do this first)

The serverMessages field on the Vapi Assistant object controls which event types fire to the server URL. Set it to only end-of-call-report before pointing any GHL workflow at the assistant.

"serverMessages": ["end-of-call-report"]

Via API:

curl -X PATCH "https://api.vapi.ai/assistant/{assistantId}" \
  -H "Authorization: Bearer {VAPI_PRIVATE_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"serverMessages": ["end-of-call-report"]}'

Via dashboard: Assistant → Advanced → Server Messages → select end-of-call-report only.

Important: serverMessages only works on Vapi Assistants. It does not work on Vapi Workflows. If using a Vapi Workflow, filter at a middleware layer (n8n, Make, or a small relay script) before the payload reaches GHL.


Fix 2 — GHL loop lock mechanics

GHL locks a workflow when it reaches 50 starts for the same contact within 30 minutes. "Starts" counts every workflow entry — including ones that exit immediately via a NO branch on an If/Else filter. The filter inside GHL reduces processing but does not prevent the lock. The only clean fix is suppressing events at the Vapi level via serverMessages.

The If/Else filter on message.type is still worth adding as a secondary safeguard, but it is not a substitute for serverMessages.


Fix 3 — GHL mapping reference must be set before building actions

When creating a new Inbound Webhook workflow in GHL, GHL requires a mapping reference payload before any field variables are available in the workflow builder. Without it, all mapped fields return empty and Create Contact errors with "no value was found for any of the mapped fields."

Correct order:
1. Create the workflow and copy the webhook URL
2. Send a sample end-of-call-report payload to that URL (via curl or a test call)
3. Go back to the trigger settings → Mapping Reference → Check for new requests → select the payload
4. Only then build the action steps

If you build actions before setting the mapping reference, the variable picker will either be empty or suggest incorrect paths. Re-select the mapping reference and re-map each field.

Sample payload to send for mapping reference setup:

curl -X POST "{GHL_WEBHOOK_URL}" \
  -H "Content-Type: application/json" \
  -d '{
    "message": {
      "type": "end-of-call-report",
      "endedReason": "customer-ended-call",
      "summary": "Test call summary.",
      "artifact": {
        "transcript": "AI: Hello.\nUser: Hi.",
        "recording": {
          "recordingUrl": "https://storage.vapi.ai/sample.mp3",
          "stereoRecordingUrl": "https://storage.vapi.ai/sample-stereo.mp3"
        }
      },
      "analysis": {
        "structuredData": {
          "callerName": "Test User",
          "callerEmail": "test@example.com",
          "callerPhone": "+447911000000",
          "treatmentInterest": "Brow Treatment",
          "callOutcome": "Booking Inquiry"
        }
      },
      "call": {
        "id": "sample-001",
        "customer": { "number": "+447911000000" },
        "startedAt": "2026-05-04T14:00:00.000Z",
        "endedAt": "2026-05-04T14:03:30.000Z"
      }
    }
  }'

Fix 4 — Corrected Vapi end-of-call-report field paths

The following paths are production-confirmed from the Vapi end-of-call-report payload. GHL prefixes all webhook fields with inboundWebhookRequest. in the variable picker.

Field Correct GHL variable Common mistake
Caller phone {{inboundWebhookRequest.message.call.customer.number}} message.phoneNumber.number does not exist
First name {{inboundWebhookRequest.message.analysis.structuredData.callerName}}
Email {{inboundWebhookRequest.message.analysis.structuredData.callerEmail}}
Treatment interest {{inboundWebhookRequest.message.analysis.structuredData.treatmentInterest}}
Call summary {{inboundWebhookRequest.message.summary}}
Transcript {{inboundWebhookRequest.message.artifact.transcript}} message.transcript does not exist at top level
Recording URL {{inboundWebhookRequest.message.artifact.recording.recordingUrl}} message.recordingUrl does not exist at top level
Ended reason {{inboundWebhookRequest.message.endedReason}}
Event type {{inboundWebhookRequest.message.type}}
Duration Does not exist as a standalone field Derive from call.startedAt / call.endedAt

structuredData fields only populate if analysisPlan.structuredDataPrompt and a JSON schema are configured on the Vapi assistant. Verify with a test call payload before relying on them.


Fix 5 — Vapi native GHL OAuth connector is broken (as of 2026-05)

The official Vapi MCP OAuth connector for GHL has been reported broken for months. Users report silent credential errors and 500/502 responses even when Vapi logs show tools triggering correctly. Do not attempt to use the native connector. Use the custom Inbound Webhook workflow approach documented above.


Standard setup checklist for new Vapi → GHL inbound client

  1. Create GHL Inbound Webhook workflow → copy webhook URL
  2. Patch Vapi assistant serverMessages to ["end-of-call-report"] via API
  3. Set Vapi assistant server URL to the GHL webhook URL
  4. Send sample payload to the GHL webhook URL
  5. In GHL workflow trigger → Mapping Reference → select the sample payload
  6. Build action steps using inboundWebhookRequest. prefixed variables from the table above
  7. Publish the workflow
  8. Make a real test call → confirm exactly 1 enrollment in Enrollment History
  9. Confirm all fields populated on the contact record

End-of-Day Update (2026-04-07)

Transcript Receiver — deployed to Cloudflare

The ghl-transcript-receiver Cloudflare Worker is now live:

  • Worker: https://ghl-transcript-receiver.johntossou.workers.dev
  • Webhook endpoint: POST /webhook/transcript (no auth — GHL doesn't support request headers)
  • Query endpoint: GET /transcripts?token=AUTH_TOKEN&date=YYYY-MM-DD
  • KV namespace ID: 01c5ffa00a554d96ab95ce889a97df6a
  • Wrangler config: tools/cloudflare-workers/ghl-transcript-receiver.wrangler.jsonc
  • Auth token: stored in .env as AUTH_TOKEN

Still required in GHL: go to Automation → "Transcript Generated" workflow → update the webhook action URL to the worker endpoint above → publish. Without this step, GHL won't push any transcripts to the worker.

GHL API — confirmed limitation

The GHL public REST API (/conversations/messages/{id}) does not return recording URLs or transcript text. These are stored GHL-internally. The only programmatic path is the "Transcript Generated" webhook workflow.

Twilio also does not hold the recordings from GHL native dialer calls — GHL uses its own pipeline.

GHL call recording — Camille's number

Call recording is not enabled on +44 7414 269881. Camille's human calls connect and last up to 5 minutes but leave no transcript. Enable recording in GHL → Phone Numbers → +44 7414 269881 → Call Recording ON.

2026-04-07 AI calling session findings

20 VAPI calls across 2 batches (~$0.73). Key issues observed:

  1. Premature hang-up — AI said "Goodbye" while a receptionist was mid-sentence giving an email address (London City Skin Clinic). Bhavisha prompt needs a longer patience window before ending when a human is actively speaking.
  2. Garbled primary_issue — Halcyon Medispa had a broken observation ("positive reviews costing inquiries"). Field content must be validated before calls go out.
  3. Best near-miss — Gielly Green: AI disclosed itself, Christie fetched the owner, call timed out on hold. Silence timeout threshold may need extending when AI has been explicitly told to hold.

End-of-Day Update (2026-04-04)

By the end of the 4 April 2026 session, the main shift was this:

  • Vapi prompting and GHL enrichment are no longer the only concern
  • the next automation layer is now a post-call renderer for email follow-up

New operating principle

Do not let GoHighLevel send post-call follow-up emails by stitching together raw dropdown values.

Use this pattern instead:

  1. GHL workflow is triggered by a custom disposition
  2. GHL waits ~10 minutes for transcript / summary availability
  3. GHL calls an external renderer endpoint
  4. the renderer combines:
  5. transcript nuance
  6. canonical GHL fields
  7. disposition
  8. the renderer writes final copy back into GHL
  9. GHL only sends if render_status = ready

Why this matters

The live outbound schema uses machine-friendly fields like:
- primary_issue
- booking_system
- team_structure

Those are excellent for:
- filtering
- routing
- choosing the angle

They are not finished copy.

That means:
- primary_issue should not be pasted raw into an email
- GHL template logic alone is too brittle for high-quality personalised follow-up

Local build status

Prepared locally:

  • docs/aurea-post-call-renderer-design.md
  • docs/ghl-post-call-renderer-workflow-blueprint.md
  • tools/cloudflare-workers/aurea-followup-renderer.js
  • tools/cloudflare-workers/aurea-followup-renderer.wrangler.jsonc
  • tools/cloudflare-workers/README-aurea-followup-renderer.md
  • tools/setup_ghl_render_fields.py

Not yet live

Two blockers remain:

  1. Cloudflare deploy
  2. wrangler is not authenticated on this machine yet

  3. GHL render-field creation

  4. the current GHL token can read and update contacts
  5. but it does not have the scope to manage custom field definitions
  6. so the renderer fields still need manual creation or a higher-scope token

Required renderer fields

  • rendered_email_subject
  • rendered_email_body
  • render_status
  • render_type
  • render_confidence
  • render_error

Best-practice conclusion

For Aurea’s post-call follow-up layer, the safest stack is now:

  • Vapi handles the call
  • GHL owns workflows and sending
  • Cloudflare renders bounded copy
  • tasks happen only on exception branches

Update suite au feedback de Claude

One important refinement was added after architecture review:

  • the renderer should not become a fragile all-or-nothing dependency

So the recommended v1 is now:
- fallback-first
- renderer second

In practice that means:
- safe fallback templates should exist before renderer rollout
- transcript quality should be checked before transcript-based rendering
- if rendering fails, times out, or lacks enough context, the system should degrade gracefully rather than silently skip follow-up
- the renderer remains a quality layer, not the only path to a follow-up email


Update Note (2026-04-04)

This document began as a Vapi + GHL integration reference, but it now also needs to reflect the live demo-clinic voice setup.

Important live clarifications:

  • +44 1355 209592 is the Demo Voice number, not the default Bhavisha prospecting number
  • the inbound demo assistant on that number is Riley
  • Riley assistant ID: cca2a23d-abcf-45ed-9aca-f54902ae3aaf
  • Riley is a demo clinic front-desk / after-hours reception assistant only
  • Riley should remain separate from Bhavisha outbound and callback roles

Voice configuration clarification:

  • the exact ElevenLabs voice ID for Bhavisha 2 is BIS6xSao7QaZrZ0MgsWG
  • if Riley uses the Bhavisha 2 voice for demo consistency, document that explicitly rather than inferring it from another assistant

Prompting clarification:

  • for demo inbound agents, pronunciation and pacing should not rely only on prompt wording
  • use Vapi voice-layer formatting / replacements and Deepgram keyterms where possible
  • keep the prompt structured into sections and keep it to one question at a time

Outbound schema clarification:

  • the live JCT Advisory outbound schema is now locked to:
  • decision_maker
  • booking_system
  • primary_issue
  • meta_ads
  • google_ads
  • team_structure
  • worth_mentioning
  • these are now the source-of-truth fields for Bhavisha personalization
  • the older vapi_observation / vapi_leak / vapi_angle model is now historical, not canonical

Executive Summary

We now have two useful patterns for connecting Vapi and GoHighLevel:

  1. Transcript push workflow
    This sends transcript-related data out of GoHighLevel to our own webhook endpoint.

  2. External call logging workflow
    This accepts a webhook into GoHighLevel and creates a contact + logs an external call directly into the contact's native activity / call history.

The important result from testing on JCT Advisory is:

  • the Inbound Webhook -> Create Contact -> Log External Call workflow executes successfully
  • it creates the contact
  • it adds an outbound call activity in GoHighLevel
  • this means we likely do not need the full Marketplace / Conversation Provider route for this use case

That is a meaningful simplification.


What We Confirmed

1. The native GHL workflow path works

We tested this workflow:

Inbound Webhook -> Create Contact -> Log External Call

Using a test payload, GoHighLevel:

  • accepted the webhook
  • created the contact John Test
  • logged an Outbound Call
  • showed the result on the contact record

This is the most important practical finding.

2. The workflow must use webhook payload variables, not contact variables

When using Inbound Webhook, the early workflow steps do not yet have a GHL contact object.

So for Create Contact, you must map fields from the incoming webhook payload, for example:

  • phone
  • firstName
  • lastName

Do not map fields like:

  • {{contact.first_name}}
  • {{contact.last_name}}
  • {{contact.phone}}

Those are empty before the contact exists, and the step fails.

3. Log External Call is strict about values

For the external call step:

  • direction should be lower-case: inbound or outbound
  • status should use allowed values such as:
  • completed
  • answered
  • busy
  • no-answer
  • failed
  • canceled
  • voicemail
  • pending

4. You can leave the date blank

If the Date field is left blank in Log External Call, GoHighLevel will apply the current date and time automatically.

That is useful for fast testing.

5. Use the real outbound number in From

For accurate logging, the From field should be the real caller number used by the Vapi outbound setup.

For the Bhavisha / JCT Advisory setup, the Vapi outbound number must match the live prospecting architecture.

Important clarification:

  • +44 1355 209592 is now the demo inbound number used by Riley
  • do not treat +44 1355 209592 as the default Bhavisha outbound number unless the live routing has explicitly been changed again
  • always cross-check the current source of truth in docs/phone-number-architecture.md

At the time of the original test, the number we resolved was:

+441355209592

6. The public workflow creation API is not available in the way we hoped

We tested the public GHL API and found:

  • GET /workflows/ returned an authorization / scope issue
  • POST /workflows/ returned 404 Not Found

So, at least with the current token and public API surface, we should assume:

  • workflows need to be created in the GHL UI
  • then tested via webhook

We should not assume we can fully automate workflow creation programmatically.


Workflow 1: Transcript Workflow

What it does

This workflow sends transcript-related call data out of GoHighLevel to our own webhook.

In the current setup:

  • trigger: Transcript Generated
  • action: Webhook
  • destination: our ngrok / server endpoint

What data it sends

Based on the current workflow, it sends values such as:

  • contactId
  • contactName
  • phone
  • companyName
  • transcript
  • summary
  • callDuration
  • direction

What this workflow is for

This workflow is useful when:

  • GoHighLevel already has the call / transcript event
  • we want to push the transcript elsewhere
  • we want custom downstream processing
  • we want to enrich, summarize, store, or route the transcript outside GHL

In simple terms

GoHighLevel says:

"A transcript has been generated. I will now send that data to your server."

So this workflow is:

GHL -> our webhook

Best use case

Use this when the transcript already exists inside GHL and you want custom handling outside GHL.


Workflow 2: Vapi External Call Logging Workflow

What it does

This workflow lets an external system send call details into GoHighLevel.

In the tested setup:

  • trigger: Inbound Webhook
  • action 1: Create Contact
  • action 2: Log External Call

What this workflow is for

This workflow is useful when:

  • Vapi handles the call
  • we want GHL to reflect that call natively
  • we want a call activity in the contact record
  • we want to avoid a more complex Marketplace / Conversation Provider setup

In simple terms

Vapi (or our script) says:

"Here is a call that happened. Please create or update the contact, then log the call in GHL."

So this workflow is:

Vapi / script -> GHL webhook

What we tested successfully

We sent this style of payload:

{
  "phone": "+447000000001",
  "firstName": "John",
  "lastName": "Test",
  "direction": "outbound",
  "status": "completed",
  "duration": 125,
  "recordingUrl": "https://example.com/test-recording.mp3"
}

And GHL successfully:

  • created the contact
  • logged the call
  • showed the call activity on the contact

Best use case

Use this when Vapi is the voice runtime and GHL should reflect the call outcome inside the CRM.


Best default approach for Vapi outbound / external calls

For real Vapi call logging into GHL, the cleanest current direction is:

  1. Vapi finishes the call
  2. Vapi or our bridge sends a webhook to GHL
  3. GHL workflow creates / updates the contact
  4. GHL logs the external call natively

This gives us:

  • simpler architecture
  • less Marketplace complexity
  • reduced developer billing risk
  • cleaner CRM visibility

What to keep as fallback

We should still keep the Python bridge available as fallback:

tools/vapi_to_ghl_bridge.py

Why:

  • the new workflow path is now validated for basic call logging
  • but the bridge is still useful for:
  • richer transcript storage
  • custom notes
  • custom business logic
  • backup if GHL workflow behavior changes

So the bridge should not be deleted yet.


Next Step: Structured Clinic Context For Bhavisha

The bigger goal is not just call logging.

The real system goal is:

  • store structured observations about each clinic inside GoHighLevel
  • pass those fields into the Vapi agent
  • make each Bhavisha call feel like a short spoken diagnostic audit
  • test whether better context increases conversion rates for cold outreach

This is now the recommended next build step.

Design rules

These are the rules we want to keep:

  • max 8 fields
  • each field has exactly one job
  • each field must be speakable in under 10 words
  • if a field is empty, Bhavisha ignores it completely
  • every field must be fillable by a human researcher or an AI scraper in under 5 minutes per clinic

These rules are important because they force the context to stay usable in real calls.

Proposed field schema

This section is now historical.

Use it only to understand the older Vapi-first model.

As of 2026-04-04, the recommended canonical schema for JCT Advisory outbound prep is the newer GHL contact schema documented in:
- vapi-outbound-routing-system.md
- jct-ghl-vapi-alignment-audit-2026-04-04.md

Deprecated as source-of-truth fields:
- vapi_observation
- vapi_leak
- vapi_angle

The current recommended schema is:

GHL Field Name Vapi Variable Purpose Example
decision_maker {{decisionMaker}} Best person to ask for by name Sarah Johnson
booking_system {{bookingSystem}} What booking system they use Phorest
primary_issue {{primaryIssue}} The single sharpest diagnostic angle no_online_booking
meta_ads {{metaAds}} Observable Meta ads state Past ads, pixel on site
google_ads {{googleAds}} Observable Google Ads state Active
team_structure {{structure}} Solo vs team context Team
worth_mentioning {{notes}} Nuance or second-order detail Google reviews mention slow callbacks

Important note:

  • if clinic_type already exists in GHL, treat it as an existing reusable field, not a brand-new one
  • if firstName or contact name already covers the decision-maker use case well enough, decision_maker can be reconsidered later

Live field lock for the current JCT Advisory build:
- decision_maker
- booking_system
- primary_issue
- meta_ads
- google_ads
- team_structure
- worth_mentioning

Do not rename these without updating:
- the GHL enrichment and push scripts
- the Vapi personalization mappers
- the Bhavisha prompt docs

How Bhavisha should use the fields

The purpose is not to dump all the data into the script.

The purpose is to make the call feel informed, precise, and calm.

Current recommended usage logic:

  • If {{decisionMaker}} is present, Bhavisha can ask for the person by name.
  • If {{bookingSystem}} is present, Bhavisha can anchor the observation in that system or flow.
  • If {{primaryIssue}} is present, Bhavisha should frame the conversation around that one issue rather than stacking multiple claims.
  • If {{metaAds}} or {{googleAds}} are present and relevant, Bhavisha may use them as supporting context, not as the main opener.
  • If {{notes}} is present, Bhavisha should use it sparingly and only if it helps credibility.
  • If a field is empty, Bhavisha should ignore it completely.
  • Bhavisha should never say she "researched" the clinic. The specificity should do the work.

Prompt behavior rules

These are the operating rules the prompt should follow:

  • use at most one strong detail early
  • do not stack observation and proofPoint in the opening
  • if observation exists, prefer it as the first specific detail
  • if the call is going well, keep the detail light and diagnostic
  • if the person pushes back, proofPoint can be used once as grounding
  • if toneHint says busy owner-operated, be shorter and softer
  • if toneHint says premium, slow-burn, avoid sounding transactional
  • if angle exists, keep the conversation focused around that single problem frame

How the fields get populated

There are two valid paths.

Path A: Manual now

Camille or another researcher fills 3-4 fields per clinic before calling:

  • vapi_observation
  • vapi_leak
  • vapi_angle
  • vapi_tone_hint

This should take about 3 minutes per clinic.

This is the right immediate operational path.

Path B: Automated later

A clinic intelligence scraper fills the full schema from:

  • website
  • Google Business Profile / reviews
  • social links
  • booking path

That would reduce the manual prep work later.

What we should build next

The immediate next implementation should be:

  1. create the custom fields in GHL
  2. update vapi_place_personalized_call.py to pull those fields from GHL
  3. map them into Bhavisha's Vapi variable values
  4. update the prompt so it uses the variables conditionally and lightly
  5. test on a small batch of clinics

What success looks like

A good version of this system should produce calls that:

  • sound specific without sounding creepy
  • feel like a short spoken audit, not a generic sales pitch
  • stay consistent across many clinics
  • can be improved by refining field quality rather than rewriting the whole agent

Practical implementation order

Recommended build order:

  1. Keep call logging working first
  2. Add the field schema in GHL
  3. Add field extraction to vapi_place_personalized_call.py
  4. Update Bhavisha prompt rules
  5. Run 10-20 test calls with structured clinic context
  6. Compare results against generic calling

This is the right way to test the real hypothesis:

does precise diagnostic context, delivered consistently at scale, improve cold-call conversion with clinics?


Practical Best Practices

1. Keep the webhook payload simple

Start with the smallest useful payload:

  • phone
  • firstName
  • lastName
  • direction
  • status
  • duration
  • recordingUrl

Do not overcomplicate the first version.

2. Test with fake data first

Before using real Vapi traffic:

  • send a simple webhook manually
  • confirm contact creation
  • confirm call logging
  • confirm the activity looks correct in GHL

This saves a lot of time.

3. Publish before testing

Draft workflows may look configured but still not behave as expected.

Always:

  • save
  • publish
  • then test

4. Check Execution Logs before checking the contact

The fastest debugging order is:

  1. Did the webhook trigger?
  2. Did Create Contact execute?
  3. Did Log External Call execute?
  4. Then inspect the contact record

5. Do not assume a field is mapped correctly just because it “looks filled”

In GoHighLevel, a field can look populated but still point to the wrong data source.

Always ask:

  • is this a value from the webhook payload?
  • or is it a value from the contact object?

That distinction matters a lot.

6. Use the real outbound number in From

If the goal is to reflect the real call source, use the real Vapi outbound number in From.

Important architecture warning:

  • +44 1355 209592 is currently the demo voice number for Riley
  • do not blindly reuse older documentation references for Bhavisha call logging
  • confirm the live prospecting number before treating any From value as authoritative

For Bhavisha / JCT Advisory, the older documented number was:

+441355209592

7. Use lower-case call values

Safer defaults:

  • direction: inbound / outbound
  • status: completed, answered, etc.

Avoid guessing capitalization.

8. Treat workflow UI setup as part of the real system

Even if the API is limited, the workflow UI can still be a robust production tool.

Do not over-prioritize API purity if the UI gives a simpler and more reliable path.


When To Use Which Workflow

Use the Transcript Workflow when:

  • GHL already has the transcript event
  • you want to send transcript data out to your own server
  • you want custom processing outside GHL

Use the External Call Logging Workflow when:

  • Vapi is handling the call
  • you want the call reflected directly in GHL
  • you want native CRM call visibility
  • you want to avoid Marketplace / Conversation Provider complexity

For Vapi + GoHighLevel call logging, the current best practical recommendation is:

  • use GHL Inbound Webhook + Create Contact + Log External Call for native CRM call logging
  • keep the Python bridge as a fallback / enrichment layer
  • do not assume Marketplace / Conversation Provider is necessary unless a later requirement forces it

Demo Inbound Assistant Best Practices (Aurea Demo Clinic)

This section is specifically for the demo clinic inbound experience, not the outbound Bhavisha system.

Live role

  • assistant name: Riley
  • assistant ID: cca2a23d-abcf-45ed-9aca-f54902ae3aaf
  • number: +44 1355 209592
  • purpose: after-hours clinic reception demo

Design rules

  • keep this assistant separate from all outbound prospecting roles
  • it should sound like a premium clinic front desk, not a sales bot
  • it should handle inbound enquiries, basic screening, booking next steps, and message-taking
  • it should not give medical advice
  • it should not directly manage existing appointments beyond taking a callback message

Prompt rules that proved useful

  • separate the Vapi firstMessage from the main system prompt
  • structure the prompt into sections: identity, scope, flow, routing, hard rules
  • ask one question at a time
  • keep replies short enough to sound spoken rather than read
  • write for natural British phone speech, not for chatbot-style completeness
  • avoid rigid exact-line scripting unless legally necessary

Pronunciation and speech handling

For demo assistants, pronunciation should be handled in layers:

  1. prompt clarity
  2. Vapi voice-layer replacements / formatting
  3. transcriber keyterms for brand and treatment names

Current examples worth preserving:

  • Aurea pronunciation support
  • Profhilo pronunciation support
  • polynucleotides pronunciation support
  • Deepgram keyterms for clinic-specific and treatment-specific phrases

Voice configuration

The exact ElevenLabs voice ID for Bhavisha 2 is:

BIS6xSao7QaZrZ0MgsWG

If this voice is reused on demo agents like Riley, document it explicitly in the assistant notes and architecture docs so the live setup stays traceable.

Stress-test scenarios the demo assistant must handle cleanly

  • new treatment enquiry
  • existing appointment / reschedule request
  • price question
  • urgent concern / reaction concern
  • request to speak to the practitioner directly
  • caller says they never received the booking link
  • silent caller
  • wrong number / out-of-scope caller

Next Build Concept: AI Reveal System (2026-03-31)

The idea

Two call cases that turn the AI nature of the system into the product demo itself.

Case 1 — Outbound cold call

Bhavisha opens with one sharp, personalised observation about the clinic before any pitch. The observation is pulled from GHL fields (vapi_observation, vapi_leak). Once the person engages, Bhavisha reveals she is an AI. The reveal is not a confession — it is proof of concept delivered live. The call becomes a demo, not a sales conversation.

Suggested opening:

"Hi, is that [NAME]? I noticed your booking link goes to a contact form rather than a calendar — I'd guess that loses you a few enquiries a week. I'm actually an AI calling on behalf of Aurea Growth — which is the point. Worth a quick chat to see if something like this makes sense for your clinic?"

Case 2 — Missed call callback (inbound)

When a clinic owner calls back the outbound number after a missed call, the AI answers:

"Hi, is that [NAME]? Thanks for calling back. I'm essentially calling to solve the exact problem you're facing right now — catching up with leads you missed while you were busy."

The irony does the selling. They just lived the problem in the last 60 seconds.

Why Case 2 is particularly strong

The clinic owner calls back an unknown number, an AI picks up, and the opening line names the exact problem they just experienced. The product sells itself before any pitch is made. There is no equivalent moment in standard cold outreach.

Design rules for the reveal

  • The reveal must happen after the observation lands and the person engages, not on a timer and not immediately.
  • Trigger: they respond positively or ask a follow-up question — then Bhavisha says it.
  • Too early: they hang up. Too late: it feels like a trick.
  • The reveal line should be short and confident, not apologetic.

What needs to be built

What Change needed
Bhavisha prompt (Case 1) Add reveal logic: after engagement signal, reveal AI identity as capability proof
Case 2 agent Separate Vapi assistant — inbound handler, different opening, different script
GHL workflow Route missed-call-callbacks to the Case 2 agent's phone number
vapi_leak field Already exists — move it into the opening line, not buried later

What already exists

  • GHL custom fields (vapi_observation, vapi_leak, vapi_angle, vapi_tone_hint) are designed for exactly this — feeding the personalised opening
  • vapi_place_personalized_call.py and the batch caller already inject these into Vapi variable values
  • The GHL call logging workflow already handles outcomes

The reveal logic and Case 2 agent are the only new pieces needed.


Build Status: AI Reveal System (updated 2026-04-04)

What has been written

File What it is Status
tools/vapi_cold_prompt_v2.txt Case 1 — outbound cold call with AI reveal Live in Vapi, updated 2026-04-04
tools/vapi_callback_prompt.txt Case 2 — inbound callback handler Live in Vapi, updated 2026-04-04
tools/vapi_batch_caller.py Batch caller CLI Live
tools/vapi_caller_ui.py Browser UI for batch calling Live
tools/vapi_place_personalized_call.py Single call with GHL context Live
tools/push_cold_prospects_to_ghl.py Scrape → score → push to GHL Live

What still needs to be built

Step 1 — Create GHL custom fields
- Create these custom contact fields in GHL (Settings → Custom Fields):
- vapi_observation
- vapi_leak
- vapi_angle
- vapi_tone_hint
- These are what gets filled per clinic before calling

Step 2 — Update vapi_place_personalized_call.py to pull custom fields
- Currently only pulls core contact fields (firstName, companyName, tags)
- Needs to also read the 4 custom fields from GHL and pass them into Vapi variable values

Step 3 — Maintain live Vapi assistants
- Case 1 prompt is already live in Vapi
- Case 2 assistant is already created and live in Vapi
- keep assistant IDs documented when changing prompts or settings
- keep the shared voice ID documented: BIS6xSao7QaZrZ0MgsWG

Step 4 — GHL workflow for inbound callbacks / forwarding
- Create a GHL workflow: Inbound Call → (if contact tagged outbound-call-completed) → route to Case 2 Vapi assistant
- GHL needs to forward the inbound call to the Case 2 Vapi number
- Alternatively: configure the Vapi phone number directly as the inbound handler
- Current status: still blocked / unresolved with GoHighLevel as of 2026-04-04

Step 5 — Test the full loop
- Push 5 cold prospects via push_cold_prospects_to_ghl.py
- Fill vapi_observation and vapi_leak on each manually
- Run batch caller via the UI
- Confirm GHL logs the outcomes and applies tags
- Call back the outbound number from a test phone
- Confirm Case 2 agent answers with the callback opening
- Confirm both assistants can check live availability and create a real appointment on calendar K5k7NPpz9lKadHgt7V5x

Full loop (when all steps are complete)

push_cold_prospects_to_ghl.py
    → 30 ICP-matched clinics pushed to GHL with tags + angle notes
    ↓
Enrich each contact in GHL (vapi_observation, vapi_leak, vapi_angle, vapi_tone_hint)
    ↓
Aurea Caller UI → Start Calling
    → Bhavisha calls each clinic (Case 1 prompt)
    → Opens with sharp observation, reveals AI after engagement
    → GHL logs outcome, applies tags (voicemail / no-answer / completed / callback-requested)
    ↓
Clinic owner calls back the outbound number
    → Case 2 agent picks up
    → "I'm solving the exact problem you're experiencing right now"
    → Books discovery call

Live state now

  • Bhavisha Case 1 assistant is live
  • Bhavisha Case 2 assistant is live
  • both assistants now use the Bhavisha 2 ElevenLabs voice
  • both assistants were updated on 2026-04-04 with improved settings and prompt behavior
  • both assistants now have GoHighLevel tools attached in Vapi for contact lookup / creation, calendar availability, and calendar event creation
  • both assistants are now instructed to book directly into Aurea Growth | Intro Call (K5k7NPpz9lKadHgt7V5x) when the prospect agrees to a meeting
  • the forwarding problem in GoHighLevel is still unresolved and remains under discussion with GHL

Known Open Questions

These are still worth verifying later:

  • whether all real Vapi call outcomes map cleanly into GHL status values
  • how best to handle inbound vs outbound in production payloads
  • whether recording URLs always display the way we want in GHL
  • whether transcript content should still be stored separately as notes or pushed elsewhere
  • whether long-term production load introduces any workflow edge cases

Short Version

If someone asks, “What did we learn?” the short answer is:

We proved that Vapi call data can be pushed into GoHighLevel using a simple inbound webhook workflow. GHL can create the contact and log the call natively, which means we likely do not need the Marketplace / Conversation Provider route for this use case. The old transcript push and Python bridge still matter, but the simpler native workflow is now the preferred first option for call logging.

Autogenerated navigation links to improve Obsidian connectivity.

  • [[docs/vapi-outbound-routing-system|Aurea AI Outbound Routing System]]
  • [[docs/jct-ghl-vapi-alignment-audit-2026-04-04|JCT Advisory GHL ↔ Vapi Alignment Audit]]
  • [[docs/ghl-post-call-renderer-workflow-blueprint|GHL Post-Call Renderer Workflow Blueprint]]
  • [[docs/aurea-post-call-renderer-design|Aurea Post-Call Renderer Design]]
  • [[docs/aurea-gtm|Aurea Growth — Go-to-Market Strategy]]
  • [[aurea-growth-encyclopedia/wiki/index|Aurea Growth Encyclopedia]]

Client Landing Pages & CRO

/client-landing-page-builder local

End-to-end Aurea Growth landing page builder for clinic/client campaigns.

When: Default orchestration skill for any client/prospect landing page, campaign page, GHL form test, or Cloudflare-hosted page.

/page-cro local

Conversion audit and optimisation for marketing pages.

When: After the page offer and structure exist. Tightens above-the-fold clarity, proof, objections, CTA logic, and section order.

/form-cro local

Optimise lead-capture and qualification forms that are not signup flows.

When: Use on GHL forms, embedded forms, suitability checks, consultation request forms, and multi-step lead capture.

/analytics-tracking local

Set up or audit analytics, events, conversions, and measurement plans.

When: Before shipping paid-traffic pages. Define CTA clicks, form-load events, submissions, thank-you conversions, and GA4/GTM contracts.

/seo-audit local

Review metadata, headings, crawlability, local SEO, and search-intent fit.

When: Use before publishing treatment pages, client-domain pages, or any page expected to rank or affect Quality Score.

/schema-markup local

Add, fix, or optimise structured data for services, FAQs, local businesses, and offers.

When: Use on clinic/service pages after copy is stable. Validate JSON-LD before deploy.

/ab-test-setup local

Plan, design, and implement A/B tests and experiment tracking.

When: Use when traffic volume justifies variants or when creating a deliberate test plan for headline, CTA, proof, or form friction.

/optimize system

Diagnose and fix UI performance across loading speed, rendering, images, and animation cost.

When: Use before deploying paid-traffic pages, especially when third-party forms, large images, or animation are involved.

Premium Design & UI

/landing-page local

Design high-converting single-offer landing pages.

When: When building or rewriting a campaign page, treatment page, or any single-CTA page.

/ui-ux-pro-max local

Reference catalogue of colour palettes, font pairings, UX guidelines, and section patterns.

When: Phase 1 of any build. Load early to choose palette, fonts, and UX patterns before writing code.

/impeccable system

Production-grade frontend design guardrails.

When: Run alongside ui-ux-pro-max in Phase 1. Sets quality rules and catches generic LLM design habits.

/high-end-visual-design system

High-end agency visual direction: typography, spacing, art direction, and premium restraint.

When: Use when a page needs to feel luxury, clinical-premium, boutique, editorial, or expensive.

/design-taste-frontend system

Senior UI/UX taste layer for avoiding default-looking interfaces.

When: Use on client-facing frontend work when the page must feel intentional, polished, and less template-like.

/redesign-existing-projects system

Upgrade existing websites/apps by auditing weak design and applying a premium redesign pass.

When: Use when improving an existing page rather than starting from a blank file.

/typeset system

Fix font choices, hierarchy, sizing, line-height, and readability.

When: After the first draft exists. Run if typography looks flat, inconsistent, too large, or not premium enough.

/layout system

Improve spacing, visual rhythm, section composition, and scan paths.

When: Phase 3 refinement when sections feel samey, cards dominate the page, or hierarchy is weak.

/colorize system

Add strategic colour to designs that feel monochromatic, flat, or cold.

When: Use carefully on premium clinic pages: add warmth and distinction without making the palette loud.

/distill system

Strip unnecessary complexity from noisy or overbuilt designs.

When: Use when a page has too many cards, claims, badges, effects, CTAs, or repeated sections.

/quieter system

Tone down visually aggressive designs while preserving quality and intent.

When: Use for luxury, medical, or clinic pages that feel too salesy, loud, shiny, or synthetic.

/animate system

Add purposeful scroll entrances, hover states, and micro-interactions.

When: Use after structure and copy are locked. Keep motion subtle on clinical/luxury pages.

/delight system

Add memorable moments of personality and interaction.

When: Use sparingly on client pages when the brand can support a tasteful signature moment. Avoid gimmicks.

/adapt system

Adapt layouts across screen sizes, devices, and contexts.

When: Required QA pass before client review. Check mobile sticky CTAs, form embeds, visual crop, and tap targets.

/critique system

UX audit for hierarchy, cognitive load, trust, and anti-patterns.

When: Use after implementation and before polish. Best for honest critique of whether the page feels premium and converts.

/audit system

Technical quality checks across accessibility, performance, theming, responsive design, and code quality.

When: Use before deployment or client handoff, especially after third-party embeds or responsive changes.

/polish system

Final quality pass fixing alignment, spacing, consistency, and micro-detail issues.

When: Last pass before sending to client or deploying. Catches the little things that make work feel finished.

Marketing, Copy & Strategy

/copywriting local

Write or rewrite marketing copy for pages, offers, ads, emails, and sales assets.

When: Required for every client-facing page. Tightens offer clarity, headlines, proof, objections, CTAs, and voice.

/copy-editing local

Improve existing marketing copy without rebuilding the entire message.

When: Use when the page already has decent copy but needs clarity, tone, concision, or premium restraint.

/clarify system

Improve unclear UX copy, labels, microcopy, and instructions.

When: Use for forms, CTAs, reassurance copy, FAQ labels, error states, and any copy causing decision friction.

/marketing-psychology local

Apply psychology and behavioural science: anchoring, framing, social proof, scarcity, and objections.

When: Use alongside copywriting on conversion pages, especially high-ticket consultations and suitability gates.

/pricing-strategy local

Improve pricing, packaging, anchoring, and monetisation strategy.

When: Use when showing course values, deposits, consultation fees, packages, or pay-5-get-6 style offers.

/customer-research local

Conduct, analyse, or synthesise customer research.

When: Use before major page rewrites when reviews, calls, surveys, or client notes can reveal actual patient language.

/product-marketing-context local

Create or update product/service marketing context documents.

When: Use for repeat clients or multi-page builds so positioning, offers, objections, and proof stay consistent.

/ai-seo local

Optimise content for AI search engines and LLM citations.

When: Use when building authoritative treatment pages, clinic service pages, or content designed to be cited by AI search.

/cold-email local

Write B2B cold emails and follow-up sequences that get replies.

When: Outbound prospecting and client acquisition sequences.

/email-sequence local

Create or optimise warm/lifecycle email sequences and drip campaigns.

When: Use for GHL nurture, missed-call, reactivation, appointment reminder, and post-lead follow-up sequences.

/ad-creative local

Generate, iterate, and scale ad creative for Meta and Google campaigns.

When: Use when turning landing-page positioning into ad hooks, headlines, descriptions, or creative briefs.

/paid-ads local

Strategy and execution for paid advertising campaigns on Google and Meta.

When: Campaign planning, targeting, Quality Score diagnosis, budget allocation, or ad performance troubleshooting.

/sales-enablement local

Create sales collateral, objection handling, one-pagers, and pitch assets.

When: Use when the landing page needs a matching sales aid, client pitch, objection sheet, or consultation script.

Automation, AI & GHL

/vapi-prompt-builder local

Build production-quality VAPI AI assistant prompts from scratch.

When: Setting up a new VAPI voice agent: inbound, outbound, qualification, booking, or follow-up.

/n8n-workflow-patterns system

Proven workflow architectural patterns from real n8n workflows.

When: When designing a new n8n automation before building nodes.

/n8n-node-configuration system

Operation-aware n8n node configuration guidance.

When: When configuring node operations, credentials, resources, and common property traps.

/n8n-code-javascript system

Write JavaScript code in n8n Code nodes.

When: When writing or debugging JS inside an n8n Code node.

/n8n-code-python system

Write Python code in n8n Code nodes.

When: When writing or debugging Python inside an n8n Code node.

/n8n-expression-syntax system

Validate n8n expression syntax and fix common errors.

When: When an n8n expression throws an error or behaves unexpectedly.

/n8n-validation-expert system

Interpret n8n validation errors and guide fixes.

When: When the n8n editor or MCP validation reports node, credential, expression, or workflow errors.

/n8n-mcp-tools-expert system

Expert guide for using n8n MCP tools effectively.

When: When building, validating, or inspecting n8n workflows via MCP tools.

/aurea-clinic-outbound-enrichment local

Prepare and enrich JCT Advisory cold clinic prospects for outbound calling.

When: Before a batch of clinic outbound calls or when enriching prospects for Bhavisha/JCT workflows.

Operations & Shipping

/cloudflare:wrangler plugin

Cloudflare Wrangler deployment and management guidance.

When: Use when deploying Cloudflare Pages/Workers, checking projects, or debugging Pages deploys.

/cloudflare:web-perf plugin

Measure and improve Core Web Vitals and runtime performance.

When: Use after deployment when the live page needs performance inspection or Lighthouse/Core Web Vitals work.

/security-review local

Audit files or scripts for security vulnerabilities.

When: Before deploying scripts that handle user data, API keys, external inputs, redirects, forms, or webhooks.

/session-handoff system

Generate a session handoff summary for context continuity.

When: At the end of a long build/deploy session so the next session can pick up cleanly.

/browser-use:browser plugin

Use the in-app browser to inspect, navigate, test, and screenshot local or live pages.

When: Use after frontend changes to inspect the current tab, click CTAs, test forms, and verify responsive behaviour.

/review system

Review code changes for correctness, style, regressions, and risk.

When: Use for PR-style checks or when reviewing another agent/contractor's edits before merge.

All Source Documents

These are the source markdown files in docs/. Edit them directly, then run python build.py and redeploy to update the site.

TitleFileUpdatedRead timeDescription
AUREA COMMERCIAL GUIDE — v6Aurea Growth V6.md2026-04-229 min readGrowth-Led Positioning, Offer Logic, Qualification, and GTM Doctrine
Agentic Architecture Upgrade Plan — 2026-03-28agentic-architecture-upgrade-plan-2026-03-28.md2026-04-2011 min read> Added by Codex on 2026-03-28 for durable handoff to Claude Code / Cloud Code. Purpose: assess how to integrate external repos and patterns into the current JC
Aurea Ad Creative Contextaurea-ad-creative-context.md2026-04-084 min readSystem context for use with /ad-creative and /paid-ads skills. Feed this file when generating Meta ad briefs, Google Ads copy, or any paid creative for clinic c
Aurea Copywriting Contextaurea-copywriting-context.md2026-04-083 min readSystem context for use with the /copywriting skill. Feed this file when reviewing or writing any Aurea-facing copy — Mailman letters, GHL email templates, SMS f
Aurea Growth — Go-to-Market Strategyaurea-gtm.md2026-04-2026 min read> Last updated: 2026-04-14
Aurea Growth — Meta Ad Copy (All 10 Videos)aurea-meta-ad-copy.md2026-03-3014 min readCampaign: Click-to-WhatsApp | Messaging objective | UK clinic owners
Aurea Growth — Meta Campaign Strategyaurea-meta-campaign-strategy.md2026-04-2055 min read> Status: Phase 1 Click-to-WhatsApp paused on 2026-03-29. Instant Form relaunch published on 2026-04-06, paused on 2026-04-09 for form correction, relaunched on
Aurea Post-Call Renderer Designaurea-post-call-renderer-design.md2026-04-206 min readLast updated: 2026-04-04
Aurea Recruitment Sourcing Referenceaurea-recruitment-sourcing-reference-2026-04-16.md2026-05-0212 min readLast updated: 2026-05-02
Aurea Growth — AI-Powered SEO Guide for Aesthetics Clinicsaurea-seo-guide-aesthetics-clinics.md2026-05-0627 min readScope: Practical, low-overhead SEO playbook for UK aesthetics/dental clinics.
Aurea Growth — UGC Script Packaurea-ugc-3-core-angles-instant-form.md2026-04-2010 min read> Status: working draft
Aurea Voice Agent Architecture: Clinic Prospectingaurea_voice_agent_architecture.md2026-04-208 min readLast updated: 2026-04-02
Clinic Omnichannel AI Stack — Decision Playbookclinic-omnichannel-ai-stack-playbook.md2026-05-1119 min readLast updated: 2026-05-06
David / Smile Wanted — Company-Only Target Listdavid-smilewanted-consolidated-list-2026-04-16.md2026-04-164 min readDate: 2026-04-16
Field Growth Rep Interview Deckfield-growth-rep-interview-deck-v1.md2026-04-206 min readThis deck is for interviewing and onboarding Field Growth Rep candidates for Aurea Growth.
Gemini Batch API Referencegemini-batch-api-reference.md2026-04-203 min readSource: user-provided reference saved on 2026-03-29.
GHL Post-Call Renderer Workflow Blueprintghl-post-call-renderer-workflow-blueprint.md2026-04-209 min readLast updated: 2026-04-07
GHL Voice Agent Knowledge Bases — JCT Advisoryghl-voice-agent-knowledge-bases-2026-04-14.md2026-04-143 min readLast updated: 2026-04-14
GHL Voice Agent Prompts + Knowledge Base — JCT Advisoryghl-voice-agent-prompts-2026-04-14.md2026-04-2021 min readLast updated: 2026-04-14 (post live number tests)
GHL Voice Agents Mapping — JCT Advisoryghl-voice-agents-mapping-2026-04-14.md2026-04-149 min readLast updated: 2026-04-14 (post live number tests)
GoHighLevel Workflow Auditghl-workflow-audit-2026-04-09.md2026-04-208 min readDate: 2026-04-09
JCT Advisory GHL Voice Audit — 2026-04-14jct-advisory-ghl-voice-audit-2026-04-14.md2026-04-2013 min readLast updated: 2026-04-14 (after disposition confirmation and live number retest)
John Tossou — Independent Buyer-Side Advisoryjct-advisory-one-pager.md2026-04-184 min readFor CMOs, founders, and commercial leaders making decisions they only get to make once.
JCT Advisory GHL ↔ Vapi Alignment Auditjct-ghl-vapi-alignment-audit-2026-04-04.md2026-04-208 min readDate: 2026-04-04
John Tossou - Master Experience Documentjohn-tossou-master-experience.md2026-05-0127 min readPurpose: central source of truth for John's professional experience, metrics, clients, positioning angles, and CV-safe notes. This document is meant to consolid
johntossou-brand-strategyjohntossou-brand-strategy.md2026-05-0616 min readname: johntossou.com — personal brand consulting vehicle
John Tossou — LinkedIn Profile Copyjohntossou-linkedin.md2026-05-022 min readLast updated: 2026-05-02
Jordan Platten — Reference Strategyjordan-unfair-ai-cold-outreach-reference.md2026-04-2013 min readDocument de reference dedie a la strategie exposee dans la video sur le systeme d'outreach AI capable de generer des rendez-vous de vente de maniere largement a
Mailman Visual Upgrade Log — 2026-03-28mailman-visual-upgrade-log-2026-03-28.md2026-03-282 min read- User asked to exploit the UI UX Pro Max repo ideas to improve the visual quality of Mailman letters before batching the week's outputs.
Meta UGC Hook Best Practicesmeta-ugc-hook-best-practices.md2026-04-2014 min readDate: 2026-04-03
Norman Daliva - Candidate Filenorman-daliva-candidate-file.md2026-05-088 min readLast updated: 2026-05-08 (post-onboarding call)
Independent Contractor Agreementnorman-daliva-cold-caller-contractor-agreement.md2026-05-0611 min readJCT Advisory Ltd trading as Aurea Growth - Cold Caller / Appointment Setter
Phone Number Architecturephone-number-architecture.md2026-04-206 min readLast updated: 2026-04-14
Pre-Ship Checklistpre-ship-checklist.md2026-04-081 min readRun this before pushing any script change to production — Cloudflare Worker, Flask app, webhook receivers, or any tool that touches external APIs.
Private Intake Demo — Project Hubprivate-intake-demo.md2026-04-2021 min readStatus: V1 complete (Aisha). Generator pipeline rebuilt with Claude Vision. Reliability hardening completed on 2026-04-02. Gemini visual-analysis experiments ad
Proposal Template Best Practicesproposal-template-best-practices.md2026-04-207 min readThis document captures the best practices learned from building proposals for:
Stop Vibe Coding. Start Getting Customers. — Reference Strategystop-vibe-coding-start-getting-customers-reference.md2026-04-0912 min readDocument de reference dedie a la these exposee dans cet episode sur la facon de transformer des projets AI "vibe coded" en business capables d'obtenir de vrais
Field Growth Rep — Candidate Briefstreet-team-candidate-brief.md2026-04-2010 min readYou walk into target clinics, speak to the owner or clinic manager, show them something specific about their own booking flow, and book a short call with John.
Independent Contractor Agreementstreet-team-independent-contractor-template.md2026-04-206 min readAurea Growth - Street Team Operator
UGC AI Generation Best Practicesugc-ai-generation-best-practices-2026-03-29.md2026-04-209 min readDate: 2026-03-29
Universal Directory Setupuniversal-directory-setup.md2026-04-201 min readThis dashboard creates a single interactive HTML index for the full Aurea Growth root.
Upwork Brief — UGC Actors for Aurea Meta Campaignupwork-ugc-meta-actors-brief.md2026-04-204 min readI am looking for UGC-style actors/creators to record short face-to-camera Meta ad creatives for an aesthetics-clinic campaign.
Vapi + GoHighLevel Best Practicesvapi-ghl-best-practices-2026-03-31.md2026-05-0429 min readPlain-English reference for what we learned while testing Vapi with GoHighLevel / LeadConnector.
Aurea AI Outbound Routing Systemvapi-outbound-routing-system.md2026-04-2045 min readLast updated: 2026-04-14
Aurea Growth SOP — Building Websites & Landing Pages with Codex/Claude Code, GHL Logic, and External Hostingwebsite-landing-page-ghl-external-hosting-sop.md2026-05-1334 min readLast updated: 2026-05-13 (revised 2026-05-13: added GHL iframe performance/reliability pattern, Cloudflare cache headers, robots.txt note, GHL custom code produ