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
1Get 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.
2Find 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.
3Create 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.
4Register 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.
5Create 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
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.
6Create 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.
7Run 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.
8Paste 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.
9Build 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).
10Billing — 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.
11Final 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.)
- Delete your test submissions from Contacts when done.
12Header 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.)
13Funnel 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 (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.parentNode.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><script>window.__metaq = window.__metaq || []; window.__metaq.push('Lead');</script><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.
14Calendar — 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.
15Blooio — 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.
16Meta 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
| Client | Funnel domain | Zips | Verification | Actions |
|---|---|---|---|---|
| Loading… | ||||