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:
- Replace all relative
assets/paths with absolute Cloudflare Pages URLs — GHL has no local file context so relative paths silently fail. Catchsrc=,href=,srcset=,imagesrcset=, and comma-separated srcset values —srcsetis 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
- 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
nameattributes. - 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://, notfile://. GHL embed scripts may not behave correctly from a local file. - Cloudflare Pages is a good temporary HTTPS preview environment.
- The GHL
POLITE_SLIDE_INembed 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 withdisplay:noneand waits for the GHL script to reveal it. - For a form that should sit inside the landing page's assessment section, use the
INLINEembed 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
90on 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-visibilitycaused 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-visibilityon 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
srcis 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.
4. Recommended Build Workflow With Codex / Claude Code
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:
- Client domain custom page if access exists.
- Client domain custom page prepared locally if access is expected soon.
- GHL native page fallback if access is blocked.
- 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:
- Hero
- Treatment/service keyword in H1.
- Location in H1 or subhead.
- Clear outcome.
- One CTA.
-
Real image or clinic/device visual.
-
Trust strip
- Years of experience.
- Location.
- Founder-led / clinician-led care.
- Technology/device proof.
-
Review or rating if available.
-
Problem / fit
- Who this is for.
- What concerns it helps with.
-
Avoid insecurity-led copy.
-
Benefits
- 3-5 outcome-driven blocks.
-
Keep claims careful and compliant.
-
Treatment/service explanation
- What it is.
- How it works.
- What happens during consultation.
-
Why a course/plan may be needed.
-
Why this clinic
- Local differentiation.
- Founder/clinician experience.
- Environment.
-
Proof.
-
Form / suitability check
- Short enough to complete.
- Strict enough to protect client time.
- One question per step if multi-step.
-
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.
-
FAQ
- Handle objections before final CTA.
-
Include timeline, price, suitability, pain, downtime, what happens next.
-
Final CTA
- Same primary CTA.
- Clear next step.
- 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.devURL 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.txtfile. Otherwise Cloudflare Pages may return the HTML page for/robots.txt, causing Lighthouse/SEO audits to report a malformed robots file. - Add a
_headersfile 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
_headersconservative. Avoid adding broadLink: <...>; rel=preconnectheaders 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:
- Create a new funnel/page in the client sub-account.
- Rebuild sections with native rows, columns, headings, images, buttons.
- Put global CSS in page custom CSS/header settings.
- Use GHL native form or survey.
- Configure conditional logic:
- Good fit → calendar.
- Poor fit → polite thank-you page.
- Add tags:
- Campaign name.
- Suitable.
- Not suitable.
- Booked.
- No booking.
- Follow-up required.
- Add hidden fields:
- UTM source.
- UTM medium.
- UTM campaign.
- UTM term.
- UTM content.
- GCLID if available.
- Landing page URL.
- Test workflow triggers.
- Test mobile layout.
- 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 forgetsrcset=andimagesrcset=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 1pxbackground-clip: textwith 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:
- Build a premium one-page demo using Claude Code or Codex.
- Deploy to Cloudflare Pages for a shareable preview link.
- Send the link via WhatsApp with a short message: what you built, what can be changed, optional mention of ads if relevant.
- Wait for the prospect's reaction before scoping further.
- Once they respond positively, send pricing.
- Close on setup fee + monthly retainer.
- 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:
- Audit existing URLs — list every published page and its Rank Math score if available.
- Identify which pages Google has indexed — check Google Search Console if accessible, or run
site:domain.com. - Keep or redirect every indexed URL. Do not delete a page that Google has already indexed without a 301 redirect to an equivalent page.
- Preserve the homepage keyword target — if the current homepage ranks for a core term, the new homepage must target the same term.
- Rebuild treatment pages as SEO pages with: H1 containing the keyword, FAQ schema, local intent modifiers, internal links, and accurate alt text.
- 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
- Real clinic photos — reception, treatment room, device, practitioner.
- Real before/after results (with consent and disclaimer).
- 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
- Go to google.com/business and sign in with the clinic's Google account
- Claim the profile if not yet claimed
- 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)
- Create a GHL workflow: trigger = appointment completed (status change in GHL or Vagaro webhook)
- Wait: 60-90 minutes after appointment end time
- 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]"
- If no response after 48 hours: send one follow-up email with the same link
- 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:
serverMessagesfilter 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:
- 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.
- Regulatory bundle — UK numbers require a Regulatory Bundle with an approved address. Create this before buying if it doesn't already exist.
- 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)
- GHL Settings → WhatsApp → Connect a new number
- Log in with the Facebook account that has Business Manager Admin access to the Meta Business Portfolio
- Select the correct business portfolio
- Enter the phone number in international format: +447XXXXXXXXX — never with a leading 0
- Choose Voice Call for OTP delivery (more reliable than SMS for UK O2 numbers)
- Client receives call on their phone, reads out the 6-digit code
- Enter the code in GHL
- Set display name (trading name the client wants customers to see)
- 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.
- Export and import contacts into GHL subaccount
- Build and test the AI triage agent (Appointwise) on a separate test WhatsApp number — not the live number
- Run 3-7 days internal stress testing on the test line
- Refine AI tone, photo timing, pricing handling, and handoff rules from test chats
- Set up and test Vapi voice agent independently — call the Vapi number directly
- Validate Vapi → GHL webhook end to end (contact created, call logged, structured data populated)
- Connect Instagram and Facebook DMs — confirm Meta permissions first
- Register live WhatsApp number on GHL only when test line is stable
- Set carrier call forwarding to Vapi number only after WhatsApp is live and tested
- Run full end-to-end test across all channels
- Go live — AI qualifies, human books manually for v1
- 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,
fromprices, 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-requeststatus-updatetranscriptfunction-callhangend-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}} |
— |
{{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
- Create GHL Inbound Webhook workflow → copy webhook URL
- Patch Vapi assistant
serverMessagesto["end-of-call-report"]via API - Set Vapi assistant server URL to the GHL webhook URL
- Send sample payload to the GHL webhook URL
- In GHL workflow trigger → Mapping Reference → select the sample payload
- Build action steps using
inboundWebhookRequest.prefixed variables from the table above - Publish the workflow
- Make a real test call → confirm exactly 1 enrollment in Enrollment History
- 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
.envasAUTH_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:
- 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.
- Garbled primary_issue — Halcyon Medispa had a broken observation ("positive reviews costing inquiries"). Field content must be validated before calls go out.
- 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:
- GHL workflow is triggered by a custom disposition
- GHL waits ~10 minutes for transcript / summary availability
- GHL calls an external renderer endpoint
- the renderer combines:
- transcript nuance
- canonical GHL fields
- disposition
- the renderer writes final copy back into GHL
- 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.mddocs/ghl-post-call-renderer-workflow-blueprint.mdtools/cloudflare-workers/aurea-followup-renderer.jstools/cloudflare-workers/aurea-followup-renderer.wrangler.jsonctools/cloudflare-workers/README-aurea-followup-renderer.mdtools/setup_ghl_render_fields.py
Not yet live
Two blockers remain:
- Cloudflare deploy
-
wrangleris not authenticated on this machine yet -
GHL render-field creation
- the current GHL token can read and update contacts
- but it does not have the scope to manage custom field definitions
- so the renderer fields still need manual creation or a higher-scope token
Required renderer fields
rendered_email_subjectrendered_email_bodyrender_statusrender_typerender_confidencerender_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 209592is 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_makerbooking_systemprimary_issuemeta_adsgoogle_adsteam_structureworth_mentioning- these are now the source-of-truth fields for Bhavisha personalization
- the older
vapi_observation / vapi_leak / vapi_anglemodel is now historical, not canonical
Executive Summary
We now have two useful patterns for connecting Vapi and GoHighLevel:
-
Transcript push workflow
This sends transcript-related data out of GoHighLevel to our own webhook endpoint. -
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 Callworkflow 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:
phonefirstNamelastName
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:
directionshould be lower-case:inboundoroutboundstatusshould use allowed values such as:completedansweredbusyno-answerfailedcanceledvoicemailpending
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 209592is now the demo inbound number used by Riley- do not treat
+44 1355 209592as 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 issuePOST /workflows/returned404 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:
contactIdcontactNamephonecompanyNametranscriptsummarycallDurationdirection
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.
Recommended Architecture Going Forward
Best default approach for Vapi outbound / external calls
For real Vapi call logging into GHL, the cleanest current direction is:
- Vapi finishes the call
- Vapi or our bridge sends a webhook to GHL
- GHL workflow creates / updates the contact
- 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_typealready exists in GHL, treat it as an existing reusable field, not a brand-new one - if
firstNameor contact name already covers the decision-maker use case well enough,decision_makercan 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
observationandproofPointin the opening - if
observationexists, prefer it as the first specific detail - if the call is going well, keep the detail light and diagnostic
- if the person pushes back,
proofPointcan be used once as grounding - if
toneHintsaysbusy owner-operated, be shorter and softer - if
toneHintsayspremium, slow-burn, avoid sounding transactional - if
angleexists, 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_observationvapi_leakvapi_anglevapi_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:
- create the custom fields in GHL
- update
vapi_place_personalized_call.pyto pull those fields from GHL - map them into Bhavisha's Vapi variable values
- update the prompt so it uses the variables conditionally and lightly
- 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:
- Keep call logging working first
- Add the field schema in GHL
- Add field extraction to
vapi_place_personalized_call.py - Update Bhavisha prompt rules
- Run 10-20 test calls with structured clinic context
- 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:
- Did the webhook trigger?
- Did
Create Contactexecute? - Did
Log External Callexecute? - 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 209592is 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
Fromvalue as authoritative
For Bhavisha / JCT Advisory, the older documented number was:
+441355209592
7. Use lower-case call values
Safer defaults:
direction:inbound/outboundstatus: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
Current Recommended Decision
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
firstMessagefrom 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:
- prompt clarity
- Vapi voice-layer replacements / formatting
- transcriber keyterms for brand and treatment names
Current examples worth preserving:
Aureapronunciation supportProfhilopronunciation supportpolynucleotidespronunciation 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.pyand 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.
Related Notes
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.
| Title | File | Updated | Read time | Description |
|---|---|---|---|---|
| AUREA COMMERCIAL GUIDE — v6 | Aurea Growth V6.md | 2026-04-22 | 9 min read | Growth-Led Positioning, Offer Logic, Qualification, and GTM Doctrine |
| Agentic Architecture Upgrade Plan — 2026-03-28 | agentic-architecture-upgrade-plan-2026-03-28.md | 2026-04-20 | 11 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 Context | aurea-ad-creative-context.md | 2026-04-08 | 4 min read | System 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 Context | aurea-copywriting-context.md | 2026-04-08 | 3 min read | System 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 Strategy | aurea-gtm.md | 2026-04-20 | 26 min read | > Last updated: 2026-04-14 |
| Aurea Growth — Meta Ad Copy (All 10 Videos) | aurea-meta-ad-copy.md | 2026-03-30 | 14 min read | Campaign: Click-to-WhatsApp | Messaging objective | UK clinic owners |
| Aurea Growth — Meta Campaign Strategy | aurea-meta-campaign-strategy.md | 2026-04-20 | 55 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 Design | aurea-post-call-renderer-design.md | 2026-04-20 | 6 min read | Last updated: 2026-04-04 |
| Aurea Recruitment Sourcing Reference | aurea-recruitment-sourcing-reference-2026-04-16.md | 2026-05-02 | 12 min read | Last updated: 2026-05-02 |
| Aurea Growth — AI-Powered SEO Guide for Aesthetics Clinics | aurea-seo-guide-aesthetics-clinics.md | 2026-05-06 | 27 min read | Scope: Practical, low-overhead SEO playbook for UK aesthetics/dental clinics. |
| Aurea Growth — UGC Script Pack | aurea-ugc-3-core-angles-instant-form.md | 2026-04-20 | 10 min read | > Status: working draft |
| Aurea Voice Agent Architecture: Clinic Prospecting | aurea_voice_agent_architecture.md | 2026-04-20 | 8 min read | Last updated: 2026-04-02 |
| Clinic Omnichannel AI Stack — Decision Playbook | clinic-omnichannel-ai-stack-playbook.md | 2026-05-11 | 19 min read | Last updated: 2026-05-06 |
| David / Smile Wanted — Company-Only Target List | david-smilewanted-consolidated-list-2026-04-16.md | 2026-04-16 | 4 min read | Date: 2026-04-16 |
| Field Growth Rep Interview Deck | field-growth-rep-interview-deck-v1.md | 2026-04-20 | 6 min read | This deck is for interviewing and onboarding Field Growth Rep candidates for Aurea Growth. |
| Gemini Batch API Reference | gemini-batch-api-reference.md | 2026-04-20 | 3 min read | Source: user-provided reference saved on 2026-03-29. |
| GHL Post-Call Renderer Workflow Blueprint | ghl-post-call-renderer-workflow-blueprint.md | 2026-04-20 | 9 min read | Last updated: 2026-04-07 |
| GHL Voice Agent Knowledge Bases — JCT Advisory | ghl-voice-agent-knowledge-bases-2026-04-14.md | 2026-04-14 | 3 min read | Last updated: 2026-04-14 |
| GHL Voice Agent Prompts + Knowledge Base — JCT Advisory | ghl-voice-agent-prompts-2026-04-14.md | 2026-04-20 | 21 min read | Last updated: 2026-04-14 (post live number tests) |
| GHL Voice Agents Mapping — JCT Advisory | ghl-voice-agents-mapping-2026-04-14.md | 2026-04-14 | 9 min read | Last updated: 2026-04-14 (post live number tests) |
| GoHighLevel Workflow Audit | ghl-workflow-audit-2026-04-09.md | 2026-04-20 | 8 min read | Date: 2026-04-09 |
| JCT Advisory GHL Voice Audit — 2026-04-14 | jct-advisory-ghl-voice-audit-2026-04-14.md | 2026-04-20 | 13 min read | Last updated: 2026-04-14 (after disposition confirmation and live number retest) |
| John Tossou — Independent Buyer-Side Advisory | jct-advisory-one-pager.md | 2026-04-18 | 4 min read | For CMOs, founders, and commercial leaders making decisions they only get to make once. |
| JCT Advisory GHL ↔ Vapi Alignment Audit | jct-ghl-vapi-alignment-audit-2026-04-04.md | 2026-04-20 | 8 min read | Date: 2026-04-04 |
| John Tossou - Master Experience Document | john-tossou-master-experience.md | 2026-05-01 | 27 min read | Purpose: 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-strategy | johntossou-brand-strategy.md | 2026-05-06 | 16 min read | name: johntossou.com — personal brand consulting vehicle |
| John Tossou — LinkedIn Profile Copy | johntossou-linkedin.md | 2026-05-02 | 2 min read | Last updated: 2026-05-02 |
| Jordan Platten — Reference Strategy | jordan-unfair-ai-cold-outreach-reference.md | 2026-04-20 | 13 min read | Document 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-28 | mailman-visual-upgrade-log-2026-03-28.md | 2026-03-28 | 2 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 Practices | meta-ugc-hook-best-practices.md | 2026-04-20 | 14 min read | Date: 2026-04-03 |
| Norman Daliva - Candidate File | norman-daliva-candidate-file.md | 2026-05-08 | 8 min read | Last updated: 2026-05-08 (post-onboarding call) |
| Independent Contractor Agreement | norman-daliva-cold-caller-contractor-agreement.md | 2026-05-06 | 11 min read | JCT Advisory Ltd trading as Aurea Growth - Cold Caller / Appointment Setter |
| Phone Number Architecture | phone-number-architecture.md | 2026-04-20 | 6 min read | Last updated: 2026-04-14 |
| Pre-Ship Checklist | pre-ship-checklist.md | 2026-04-08 | 1 min read | Run 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 Hub | private-intake-demo.md | 2026-04-20 | 21 min read | Status: V1 complete (Aisha). Generator pipeline rebuilt with Claude Vision. Reliability hardening completed on 2026-04-02. Gemini visual-analysis experiments ad |
| Proposal Template Best Practices | proposal-template-best-practices.md | 2026-04-20 | 7 min read | This document captures the best practices learned from building proposals for: |
| Stop Vibe Coding. Start Getting Customers. — Reference Strategy | stop-vibe-coding-start-getting-customers-reference.md | 2026-04-09 | 12 min read | Document 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 Brief | street-team-candidate-brief.md | 2026-04-20 | 10 min read | You 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 Agreement | street-team-independent-contractor-template.md | 2026-04-20 | 6 min read | Aurea Growth - Street Team Operator |
| UGC AI Generation Best Practices | ugc-ai-generation-best-practices-2026-03-29.md | 2026-04-20 | 9 min read | Date: 2026-03-29 |
| Universal Directory Setup | universal-directory-setup.md | 2026-04-20 | 1 min read | This dashboard creates a single interactive HTML index for the full Aurea Growth root. |
| Upwork Brief — UGC Actors for Aurea Meta Campaign | upwork-ugc-meta-actors-brief.md | 2026-04-20 | 4 min read | I am looking for UGC-style actors/creators to record short face-to-camera Meta ad creatives for an aesthetics-clinic campaign. |
| Vapi + GoHighLevel Best Practices | vapi-ghl-best-practices-2026-03-31.md | 2026-05-04 | 29 min read | Plain-English reference for what we learned while testing Vapi with GoHighLevel / LeadConnector. |
| Aurea AI Outbound Routing System | vapi-outbound-routing-system.md | 2026-04-20 | 45 min read | Last updated: 2026-04-14 |
| Aurea Growth SOP — Building Websites & Landing Pages with Codex/Claude Code, GHL Logic, and External Hosting | website-landing-page-ghl-external-hosting-sop.md | 2026-05-13 | 34 min read | Last updated: 2026-05-13 (revised 2026-05-13: added GHL iframe performance/reliability pattern, Cloudflare cache headers, robots.txt note, GHL custom code produ |