16-Step Client Setup Guide
Complete source text: registration, GHL configuration, ZIP gate, verification, and launch checks. Extracted from the October 18, 2026 Crestline Growth Complete System Blueprint. Page breaks are retained for source reference.
Team-only material. This site is not access-controlled yet. Do not enter client information, passwords, payment data, or API tokens here. Configure Cloudflare Access before using these guides operationally.
Blueprint page 11
5. Complete Client Setup Guide — Source Text
Full text preserved from supplied export, including exact field names, instructions, code, and ad copy.
CCrestline — New Client Setup service-area gate · funnel wiring · GHL workflow
← Crestline HQ
← Billing portal
What this page does
This page sets up the backend for a brand-new roofing client: it registers their service area on Crestline's server, verifies their
GoHighLevel connection, and hands you every piece of code you need to paste — with instructions for each paste. You do not need
Cloudflare access or any coding — everything required lives on this page. If every step below shows done and the four checks are
green, the client's lead flow works.
What you need before you start: ① admin access to the client's GHL sub-account, ② the client's zip code list, ③ the client's funnel
already cloned into that sub-account, ④ about 20 minutes. Existing clients (Priority, Tampa, Diamond) are managed elsewhere —
never re-create them here.
Auto-provisioned clients: when a client submits the onboarding form, their sub-account is created automatically from the master
snapshot and a row appears in the table below by itself — with the zips, Location ID, phone and timezone already filled, and their GHL
login under the row's Login button. For those clients, skip steps 1, 2 and 4: start at step 3 (create the token inside their new
sub-account), add the funnel domain in Edit once their domain is connected, then continue from step 5.
The guide — do the steps in order
1
Get the zip list from the client
Ask the client: "Send me every zip code you service."
Save whatever they send. Any format is fine — commas, new lines, a spreadsheet column, even a sentence. The form in step 4 cleans
it automatically.
Why: the list is the client's statement of where they work. Do not shrink it or "improve" it. If a zip looks weird, ask the client — don't
delete it.
2
Find the sub-account's Location ID
Open the client's sub-account in GoHighLevel. Check the account name at the top-left — it must be the client's, not the agency's.
Click Settings at the bottom of the left sidebar.
Click Business Profile.
Near the top there is a field labeled Location ID — a code of about 20 letters and numbers. Click its copy icon.
Paste it somewhere safe for a minute (a note, a scratch doc). You'll need it in step 4.
3
Create the Private Integration token
Still inside Settings, click Private Integrations in the settings menu on the left. (Can't see it? Use the search box at the top of the
settings menu.)
Click Create new integration.
Name: type Crestline Worker.
Scopes / permissions: select all of them. If there is a select-all option, use it. Otherwise tick every box.
Click Create.
A token starting with pit- appears one time only. Copy it now.
⚠ If you lose the token, you cannot view it again. Delete the integration and create a new one — then update the client here with Edit
→ Replace token.
4
Register the client here
Fill every field, then click Create client. The page cleans the zip paste and tells you how many zips it found. This step only registers
the client on Crestline's backend — nothing goes live from this alone.
Company name
e.g. Smith Roofing
Blueprint page 12
Funnel domain (no https://, no slash)
getsmithroofing.com
Location ID (step 2)
AbC123…
Private Integration token (step 3)
pit-…
Timezone (the client's local time)
Eastern — America/New_York
Client phone (the number they're reachable on — shows in SMS templates; exactly as it should appear)
(313) 555-1234
Location callout (region in headlines: "…experts in ___". County/region, NOT a city — our call, e.g. Pierce County, Tampa Bay)
Tampa Bay
Meta pixel / dataset ID (the long number from Events Manager — powers the LP pixel + CAPI; add later via Edit if the dataset isn't
created yet)
1234567890123456
Zip list — paste exactly what the client sent
33510, 33511, 33523 …
Create client
5
Create the three tags in GHL
In the client's sub-account: Settings → Tags.
Click the button to add a new tag (+ New Tag or Add Tag).
Create these three tags, spelled exactly like this, all lowercase:
in-service-area
out-of-service-area
roofing_lead_crestline
Copy all three
Why now: if a tag gets created later by a typo, you end up with two almost-identical tags and broken automations that are miserable
to debug.
6
Create a test contact
In the client's sub-account: Contacts → add a new contact.
Fill ALL of these fields — skip none:
First name Test · Last name Contact · Email: your own · Phone: any valid-looking number · Street address 123 Main St · City + State:
anything · Postal code: one of the client's real zips.
Save, then open the contact.
Copy the Contact ID: look at the browser address bar — the code after /contacts/detail/ is the Contact ID (about 20 letters and
numbers).
Why every field: when a field is empty, GHL's test feature sends it as an empty string, the server correctly rejects it, and GHL shows a
useless "Internal Server Error". This exact trap has burned us twice.
7
Run Verify — get four green checks
In the Clients table at the bottom of this page, find your client's row.
Click Verify. Paste the test contact's Contact ID when asked.
Wait a few seconds. You want four green checks. Every red check explains exactly what is wrong and which step to redo.
Then open the test contact in GHL: it should now carry both tags in-service-area and out-of-service-area (the test applies both on
purpose). Seeing them in GHL is your proof. Remove both tags from the test contact afterwards.
Four green checks = the token, the sub-account, the zip list and the tagging all work in production, because Verify runs the same
code a real lead runs. Verify also writes the brand values into the sub-account's custom values: the company name into
Blueprint page 13
communicationcompanyname, the client phone into clientcustomnumber, and the location callout into locationcallout (the region
inthe landing-page headline "…experts in ___"). SMS/email templates and page copy brand themselves from these — an extra
checkappears for each value you filled in. If you ever change any of them here, just run Verify again to push the new values.
8
Paste the zip gate into the funnel
How the funnel works: the opt-in form redirects to a small transition page, which reads the visitor's zip and instantly forwards them to
the booking page (in area) or the "out of service area" page (not in area). You are pasting the code that does that forwarding, and
making sure the form passes the zip along.
On your client's row, click Pastes, then copy A — LP gate code.
In GHL: Sites → Funnels → open the client's funnel → open the transition page (the page the form redirects to — NOT the opt-in page,
NOT book-estimate) → Edit.
Open the page's settings (gear icon) and paste the code into the custom-code box for the page — the field is called Tracking Code →
Footer or Custom CSS/JS depending on GHL version. Save and publish.
Look at the two URLs inside the pasted code: they end with /book-estimate and /out-of-service-area. If this funnel's pages use different
paths, edit those two URLs to match — otherwise leave them.
Now the form: open the opt-in page → click the form → form settings → On Submit → choose Redirect to URL — and paste B — Form
redirect URL from the Pastes panel. Replace TRANSITION-PAGE-PATH with the transition page's actual path. Save and publish.
⚠ The redirect URL MUST contain postal_code={{postal_code}}. If it's missing, the gate reads an empty zip and bounces every single
lead to "out of service area".
9
Build the service-area workflow in GHL
In the client's sub-account: Automation (left sidebar) → + Create Workflow → Start from Scratch.
Name it Service area check.
Add the trigger: Form Submitted → filter it to the opt-in form.
Add the first action: Custom Webhook (type "webhook" in the action search).
Method: POST
URL: copy C — Webhook URL from the Pastes panel.
Authorization: choose Bearer Token → add a new credential → name it Crestline Worker Token → paste the secret from D — Worker
secret in the Pastes panel. Paste the raw value only — do not type the word "Bearer", GHL adds it. This credential lives per
sub-account, so you create it fresh for every new client.
Body: copy E — Webhook body from the Pastes panel and paste it as the JSON body / custom data.
Add action: Wait → 5 seconds. (Lets the tag land before the next step reads it.)
Add action: If/Else → condition: Contact Tags → includes → in-service-area.
In the YES branch, add action: Add Contact Tag → roofing_lead_crestline. Leave the NO branch empty.
Before publishing, open the webhook step and click Send Test Request with your test contact selected. It must come back green /
200. If it says "Internal Server Error", the test contact is missing its postal code — redo step 6.
Publish the workflow (toggle at the top right, then save).
10
Billing — card on file + per-inspection fee
Billing is handled on the Billing portal. If the client came through the sales onboarding automation, they already exist there with their
fee and card-save link — just check their row.
If they're not there, add them on that page with the same company name, set the fee, and send them the card-save link.
The two billing webhooks for the client's sub-account (bill on booked inspection, refund alert) are behind the GHL button on their row
on the Billing portal — same Custom Webhook recipe as step 9, same Bearer credential you already created.
11
Final live test — prove the whole chain
Open the client's live funnel URL in a private / incognito window.
Fill the opt-in form with your own details and an in-area zip. Submit.
You must land on the booking page.
In GHL Contacts: your submission exists and carries the tags in-service-area and roofing_lead_crestline.
Repeat with zip 99999: you must land on the out of service area page. (The contact still gets created — that's normal in this funnel
pattern — but it's tagged out-of-service-area and gets no lead tag.)
Blueprint page 14
Delete your test submissions from Contacts when done.
Both submissions behaved correctly? The client's backend is fully live. Ads targeting comes next — copy F — Meta zip list from the
Pastes panel when the campaign gets built.
12
Header banner — logo, badges, reviews
The landing pages build their own header banner from what you enter here — no design work. The page asks the server "which
banner belongs to my domain?" and renders it, exactly like the zip gate. Fill it once; edits go live within ~5 minutes.
On the client's row, click Banner.
Logo file: download the logo the client uploaded on their onboarding form (their contact in the sales CRM → form submission) and
choose it here. PNG, JPG, SVG or WebP. If it's .ai/.eps/.pdf, convert at cloudconvert.com to SVG first. If it's small and blurry, run it
through vectorizer.ai first and use the SVG.
Badges: tick what the client selected on the certifications question — nothing they don't hold. Rules the form enforces for you: only
the highest tier per manufacturer (Platinum outranks Preferred; Master Elite outranks Certified), and at most 1 Owens Corning + 2 of
GAF / CertainTeed / BBB — more would overcrowd the banner. If a client holds more, drop the weakest (rank: GAF Master Elite > OC
Platinum > CertainTeed > GAF Certified > OC Preferred).
Rating + review count: open the client's Google Business Profile link (also on their form submission) and copy the live rating and
count. Type the raw count — it becomes "(N+)" automatically. A rating below 4.5 is refused on purpose: flag it, don't ship it.
Accolade: only if their "other accolades" answer has something strong (a ranking, a named award) — up to 3 lines, small / BIG / small.
Otherwise leave empty.
Click Save banner and check the Live preview: logo not stretched, badges right, numbers right.
The funnel side needs nothing per client — the snapshot's pages already carry the loader. (One-time, in the master funnel only: the
header image element is replaced with the loader snippet — that's already documented in the snapshot pack.)
13
Funnel tracking codes — verify they arrived, paste if missing
These three code blocks live in the master funnel and normally arrive automatically with the snapshot — they contain nothing
client-specific (the Meta pixel ID is fetched live from this portal's record). So this step is a 60-second check, and only becomes a paste
job if a block is missing.
In the client's sub-account: Sites → Funnels → open the funnel → Settings tab → look at Head Tracking Code. It should contain the "
Meta Pixel Loader" block below. Missing? Paste it there and Save.
Open the booking page → the page's Settings (gear) → Tracking Code → Header. It should contain the "Lead event" one-liner. Missing?
Paste it. Same check on the thank-you page with the "Schedule event" one-liner. Save + publish anything you changed.
That's all — the pixel itself activates when the Meta pixel / dataset ID is filled on the client's row here (Edit → Meta pixel / dataset ID →
Save, then it's live within ~5 minutes). No ID set = nothing fires = no wrong-pixel risk.
Meta Pixel Loader — funnel Settings → Head Tracking Code Copy
<!-- Meta Pixel Loader (client-neutral — ID served by domain from the setup portal) -->
<script>
!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)
f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;t.src=v;s=b.getElementsByTagName(e)[0];s.p
arentNode.insertBefore(t,s)}(window,document,'script','https://connect.facebook.net/en_US/fbevents.js');
(function(){
window.__metaq = window.__metaq || [];
fetch('https://maya-ghl-worker.ahnafpathan55.workers.dev/client-config?host=' + encodeURIComponent(location.hostname))
.then(function(r){ return r.json(); })
.then(function(c){
if (!(c && c.ok && c.pixelId)) return;
fbq('init', c.pixelId);
fbq('track', 'PageView');
(window.__metaq || []).forEach(function(e){ fbq('track', e); });
window.__metaq = { push: function(e){ fbq('track', e); } };
}).catch(function(){});
})();
</script>
Lead event — booking page → Settings → Tracking Code → Header Copy
<script>window.__metaq = window.__metaq || []; window.__metaq.push('Lead');</script>
Blueprint page 15
Schedule event — thank-you page → Settings → Tracking Code → Header Copy
<script>window.__metaq = window.__metaq || []; window.__metaq.push('Schedule');</script>
Why these look different from the classic Meta snippets: the classic base code hardcodes one client's pixel ID (so it must never be
copied between accounts), and bare fbq('track') lines can fire before the pixel loads. This loader fetches the RIGHT client's ID by
domain, and the events queue safely until it's ready.
14
Calendar — staff, availability, buffer (always manual)
The inspection calendar arrives with the snapshot, but its staff member and working hours are per-client and must be set by hand.
The client already told us their hours on the onboarding form — you're just copying them in.
Get the source data: open the client's contact in the sales CRM → their onboarding form submission → read the answer to "Days &
Hours Available for Inspections". It contains the days, the hours, and the buffer time they want between appointments.
In the client's sub-account: Settings → Calendars → find the inspection calendar that came with the snapshot → three-dot menu → Edit.
⚠ Edit THIS calendar — never create a new one. The booking workflows and billing trigger are wired to this exact calendar; a new
calendar silently breaks bookings and billing.
In the Meeting Details / Team section: remove any staff member that came from the master, and add the client's owner (the admin
user of this sub-account) as the assigned team member.
In the Availability tab: set the weekly available hours exactly as the form answer says — untick days they don't work, set start/end
times per day.
Same tab: find the Buffer setting (called "Post buffer" / "Buffer time between appointments" — if you don't see it under Availability,
check the other tabs; the field is named Buffer). Set it to the buffer from the form answer (e.g. 3 hours means a 10am inspection
blocks the next one until 1pm).
Confirm the appointment duration is 60 minutes (the standard inspection slot), then Save.
Sanity check: open the funnel's booking page on the live domain — the offered slots must match the client's stated days/hours, with
gaps matching the buffer.
Connecting the owner's personal Google/Outlook calendar (so bookings appear on their own phone) happens on the kickoff call — it
needs the client to log in themselves.
15
Blooio — iMessage texting (purchase per client + switch the SMS steps)
Every new client gets Blooio (blooio.com) — a managed iMessage service — so automated texts arrive as blue-bubble iMessages
instead of carrier-filtered SMS. Android contacts automatically fall back to RCS, then SMS. One Blooio number is purchased per client.
The message COPY in every workflow stays exactly the same — only the sending channel changes.
Create/log into the Blooio account at app.blooio.com and claim an iMessage-enabled number for this client (plans from ~$39/month
per number, unlimited messages, free trial available; numbers are ready instantly — no hardware, no 10DLC registration).
In Blooio: Integrations → GoHighLevel → authorize this client's sub-account (the authorization is per sub-account — repeat for every
client).
In the client's sub-account, open every workflow that has a "Send SMS" step (the service-area nurture, booking confirmations,
reminders — go through Automation → Workflows one by one) and switch the step's sending channel to Blooio, per Blooio's own GHL
instructions ("select Blooio as the messaging channel in your GHL workflows"). Do NOT rewrite the message text — copy stays
identical, only the channel changes. Save + publish each workflow you touch.
Test: trigger one workflow with a test contact using an iPhone number — the text should arrive as a blue iMessage. Reply to it and
confirm the reply shows in the GHL conversation.
If anything doesn't match these steps (their UI evolves): Blooio's own docs are at help.blooio.com and their GHL page at
blooio.com/integrations/high-level; support is help@blooio.com (they also take questions via text on their site). It is their job to get
you connected — ask them.
16
Meta ads — dataset, campaign + ad library
The full ads launch has its own page: the Ads Manager Playbook — create the client's dataset (pixel + Conversions API, named
[CLIENT] CRESTLINE PIXEL 1), build the campaign with the exact live-proven settings, and load the 13-video + 2-static ad library with
the copy templates. Every setting on that page was read off the live Priority campaign. Do it after the funnel is live (steps 1–13) and
the client's Business Partner access is confirmed.
Clients set up on this page
Existing hard-wired clients (Priority, Tampa, Diamond) don't appear here and can't be edited here — this table is only for clients
onboarded through this page.
Blueprint page 16
Client Funnel domain Zips Verification Actions
Big Texas Roofing and Solar LLC
big-texas-roofing-and-solar-llc ·
1 Not verified
Verify
Pastes
Banner
Edit
Login
Delete
Covered Exteriors
covered-exteriors · pit-…a8f0
getcoveredexteriors.com 311 Verified ✓
Oct 8 11:30 PM
Verify
Pastes
Banner
Edit
Login
Delete
Token + Location ID work together — Authenticated into the sub-account "Covered Exteriors". If that name is NOT this client, the
Location ID or token came from the wrong sub-account.
"communicationcompanyname" set on the sub-account — Set to "Covered Exteriors". Every SMS/email template that uses {{
custom_values.communicationcompanyname }} now shows this automatically.
"clientcustomnumber" set on the sub-account — Set to "+1 917-921-4646". Every SMS/email template that uses {{
custom_values.clientcustomnumber }} now shows this automatically.
"locationcallout" set on the sub-account — Set to "Middlesex County". Every SMS/email template that uses {{
custom_values.locationcallout }} now shows this automatically.
"funneldomain" set on the sub-account — Set to "getcoveredexteriors.com". Every SMS/email template that uses {{
custom_values.funneldomain }} now shows this automatically.
"metadatasetid" set on the sub-account — Set to "4859409137619630". Every SMS/email template that uses {{
custom_values.metadatasetid }} now shows this automatically.
In-area zip tags correctly — Zip 07001 → tagged "in-service-area" on the test contact. Open the contact in GHL and confirm you can
see the tag.
Out-of-area zip tags correctly — Zip 00000 → tagged "out-of-service-area" on the test contact.
State Solar
state-solar ·
81 Not verified
Verify
Pastes
Banner
Edit
Login
Delete