⚡ Speed-to-Lead · Third-Party Dashboard + GHL Workflow Engine

Assignment Of Lead → New Lead

Your Claude Code dashboard owns every form, opt-in, landing page, and the sales-call screens. GoHighLevel owns only the workflows and the CRM data. This guide shows the GHL workflows to build node-for-node, and the exact integration contract that wires your dashboard to them.

🖥️ All forms = your dashboard ⚙️ Workflows = GHL 🔌 API + Webhooks bridge 📞 Auto-dial on claim

AThe architecture at a glance

One rule decides where everything lives: if a human looks at it, it's in your dashboard; if it's automation or CRM data, it's in GHL. The two never overlap. Your dashboard writes data into GHL and pulls data out of GHL through the API, and starts workflows by firing webhooks. GHL runs the automation and (optionally) webhooks results back.

🖥️ Your Claude Code Dashboard

Everything the human sees and touches
  • Opt-in / capture forms on public landing pages
  • Landing pages (branded, fast, fully yours)
  • Lead claim screen (rep picks themselves)
  • Sales-call forms (client search, call page, notes, create-new-client)
  • Rep dashboard (objection handlers, pricing, follow-up)
API + Webhooks WRITE / START ▶ ◀ READ / SYNC

⚙️ GoHighLevel

The invisible engine + system of record
  • The two workflows in this guide (+ Sales Call workflows)
  • Contacts + custom fields (the data you write)
  • Pipelines + opportunities
  • Tags, calendars, users (reps)
  • SMS / email / call delivery (A2P, numbers)
🧠

Why hybrid, not 100% external: GHL already does the hard, regulated parts (A2P texting, call bridging, deliverability, scheduling). Rebuilding those is a waste. You build the part that should be branded and flexible (the front end) and let GHL be the engine. Your dashboard is the head; GHL is the body.

BThe integration contract — every place your dashboard talks to GHL

This is the big component. Because all forms are third-party, your dashboard is now responsible for every write that a native GHL form used to do for free. Build to this contract and the workflows behave exactly as drawn. All calls are GHL API 2.0 (V1 is end-of-support).

Use caseWhat your dashboard doesGHL API / mechanicWhen
① Opt-in / capture (public landing pages)
Create the leadUpsert contact: first, last, email, phone, services, city, source, consentPOST /contacts/upsert (dedupe by email/phone)On opt-in submit
Record sourceWrite Source / Lead Source fieldSame upsert payload (custom field)On opt-in submit
Capture consentStore TCPA/A2P opt-in + timestamp on a fieldCustom field on the contactOn opt-in submit
Start the dispatcherKick off Assignment Of LeadInbound Webhook trigger, or POST add-to-workflowLast, after the writes
② Lead claim (rep dashboard)
Find the right contactTypeahead search so the claim ties to the real contactGET /contacts/searchAs rep types
Assign the userSet the contact's assigned user to the claiming repPUT /contacts/{id}  assignedTo: userIdOn claim
Assign the leadAlso set the Assigned Sales Rep custom field + add the rep-name tagSame PUT (custom field) + POST tagOn claim
Map rep → GHL userTranslate "John" to the GHL user IDGET /users/?locationId (cache it)Dashboard load
Start New LeadBegin nurture/dial (this also cancels the round-robin)Inbound Webhook / add-to-workflowOn claim
③ Sales-call forms (rep dashboard)
Load the clientSearch + pull the contact into the call screenGET /contacts/search + GET /contacts/{id}Call start
Save call dataWrite City/Location + the 3 levers (Send Contract & Invoice, Sales Call Email, Disqualified)PUT /contacts/{id} (custom fields)On submit
Save notesSales-call notes onto the contactPOST /contacts/{id}/notesOn submit
Move the pipelineCreate/update opportunity, stage, value, packed namePOST /opportunitiesOn submit
Book follow-upPut the follow-up date on a GHL calendarPOST /calendars/events/appointmentsOn submit
Start Sales Call workflowFire Sales Call [Part I] — submit LASTInbound WebhookAfter all writes
④ Platform / cross-cutting (always)
Render dropdownsReps, sources, pipeline stages, calendars, servicesGET users / pipelines / calendars / custom fieldsDashboard load
Field-ID parityWrite to the exact field IDs the workflows readFrom the discovery-script outputBuild time
AuthHold a Private Integration token server-side (per location)Bearer token, never in the browserAlways
ReliabilityWrite data first, fire workflow last; retry/queue webhooksIdempotency keys; log failuresEvery submit
Sync back (optional)Keep the dashboard live when GHL changes somethingGHL outbound Webhook action → your appOn GHL events

“Assign a user” vs “assign a lead”: assigning a user = setting assignedTo (the GHL owner). Assigning the lead = that PLUS the Assigned Sales Rep custom field and the rep-name tag. The New Lead workflow reads both the native owner and the custom field, so your claim must write both or the reassignment branches won't fire. See the deep-dive in Section 3.

iHow to read the workflow diagrams

The workflows below live inside GHL. Build each node top to bottom. The blue node is the Inbound Webhook your dashboard fires; everything after it is standard GHL actions. The callout beside each card explains what ▸, why ★, customize ✦, and where the API touches 🔌.

Inbound WebhookYour dashboard starts it
If/ElseForks the path
Contact opUpdate / assign
TagAdd / remove
CommunicationSMS / email / call
WaitHolds the contact
OpportunityPipeline move
ExitRemove / Add-to-WF
✂️

Streamlined teaching version. Removed: Referral, Country, Tax Rate, Time Zone. Swapped: Slack → Internal Email. The workflow bodies are identical whether the front end is native or third-party — only the first node (the trigger) changed from a GHL form to your Inbound Webhook.

0Before you build — prerequisites

In GoHighLevel
  • Custom fields: Assigned Sales Rep, Source, Services, City + (later) the sales-call levers.
  • Tags: new lead contract lost contract won automatically assigned + one per rep + one per source.
  • Two workflows created (empty): Assignment Of Lead and New Lead, each starting with an Inbound Webhook trigger (copy its URL into your dashboard).
  • Private Integration token (read+write: contacts, custom fields, opportunities, workflows, calendars, users, tags).
In your Claude Code dashboard
  • Run the ID discovery script first, so you write to the exact field/pipeline/workflow/calendar IDs.
  • Cache the Users list (rep name → GHL user ID) for assignment.
  • Store the token + webhook URLs server-side, never in the browser.
  • Build a retry queue so a failed webhook never loses a lead.

1GHL Workflow — “Assignment Of Lead” (the dispatcher)

Goal: clean the new contact, broadcast a claimable lead, and guarantee assignment within minutes (a rep claims it in your dashboard, or GHL round-robins it). Your dashboard starts this by firing its Inbound Webhook after the opt-in upsert.

Inbound Webhook
Fired by your capture form (after upsert)
🔌 Trigger · Inbound Webhook

Your landing-page form upserts the contact via the API, then calls this webhook URL with the contact ID. That starts the workflow. This replaces the old "Form Submitted: New Lead Form" trigger.

▸ Build it

Add trigger → Inbound Webhook → copy the URL → paste into your dashboard's "create lead" handler as the final step.

+
Set contact DND
Remove “Do Not Disturb”
▸ Action · DND

Clears any old Do-Not-Disturb so a returning lead can be texted/emailed again.

+
Remove Tag
contract lost
▸ Action · Remove Tag

Wipes contract lost — they're a fresh lead again.

+
Add Tag
new lead
▸ Action · Add Tag

Stamps new lead for stage + segmentation.

+
If / Else — “Contract Won”
Already a client?
▸ Action · If/Else

Condition: Tags Includes contract won.

★ Why

Don't drip a "new lead" sequence at an existing client.

Contract Won
Tags includes “contract won”
None
Continue main flow ↓
Internal Email
“Already a client”
Remove from Workflow
Current workflow
Contract-Won path

Email the team, drop them out. Call them instead.

None → continues below
✦ Customize

Use Send Internal Notification → Email (not Slack).

+
If / Else — “Lead Source”
Where did it come from?
▸ Action · If/Else

Each branch updates Contact Source + alerts the team.

✦ Customize — DYNAMIC

Sources differ per business. Default: Facebook Ads + SEO; add Google Ads, referral, etc.

Facebook Ads
Source is “facebook ads”
SEO
Source is “seo”
Google Ads
optional
None
No match
+
Banner Notification + Internal Email
“🔔 New lead — claim it”
▸ Action · Internal Notification

Tells the whole team a lead is up for grabs.

🔌 Claim happens in your dashboard

The claim is no longer a GHL link — the rep claims in your dashboard, which assigns + starts New Lead via the API (see Section 2 + the contract).

+
Create Opportunity
Pipeline → New Lead stage
▸ Action · Create/Update Opportunity

Tracks the lead. Pack the name: {{contact.name}} | {{contact.city}} | {{contact.services}}.

+
Wait — 1 minute
▸ Wait (1 min)

Top-rep head start before the team gets re-pinged.

+
Wait — 3 minutes
▸ Wait (3 min)

Grace window. Still unclaimed → auto-assign next.

+
Assign User (Round Robin)
“Only unassigned” = OFF
▸ Action · Assign User

Nobody claimed → round-robin so the lead never dies.

🔌 Note

This fallback stays a GHL action. Your dashboard doesn't touch it — but a dashboard claim that starts New Lead removes the contact here before the 3-min timer, so it's skipped.

+
Add Tag
automatically assigned
★ Critical tag

automatically assigned = the switch New Lead reads. Auto = SMS only. A dashboard claim writes the rep-name tag instead, not this one.

+
Add to Workflow
→ New Lead
▸ Action · Add to Workflow

Hands off to New Lead (Section 2) for the call + nurture.

● END of Assignment Of Lead
⑂ The one decision that controls everything downstream
What happenedHow it's setTag writtenEffect in New Lead
A rep claims in your dashboardDashboard sets assignedTo + Assigned Sales Rep field, then starts New Lead (which removes from this workflow)[rep name]Manual → auto-dials the rep
Nobody claims (3-min elapsed)GHL round-robin Assign User firesautomatically assignedAuto → SMS only, no call

2GHL Workflow — “New Lead” (assign + dial + nurture)

No GHL trigger — your dashboard starts it on claim (via webhook), or Assignment Of Lead adds it on auto-assign. Goal: lock in the right rep, dial when a human's available, nurture until they reply.

⚙️

Settings first: Allow Re-entry + Stop on Response on; contact time zone; send window 7 AM–9 PM. A 2 a.m. lead waits to 7 a.m.

Remove from Workflow
→ Assignment Of Lead
▸ Action · Remove from Workflow

Cancels the round-robin timer so a claimed lead isn't reassigned.

★ Why

Someone already claimed it — round-robining again would steal it.

+
If / Else — “Sales Call” guard
Returning client mid-sales-call?
▸ Action · If/Else (Yes)

Workflow contact Is NOT active in Sales Call [Part I] AND Sales Call [Part II].

★ Why

Don't text "book a call" right after they had one. None → END.

Yes
Not in Part I & II → continue
None
In a sales call → END
+
If / Else — “Assigned User” #1
Returning client? Keep original rep
▸ Action · If/Else (keep existing)

Per rep — Assigned user Includes [rep] AND Assigned Sales Rep is not empty → keep that rep.

★ Why

A past client goes back to the rep who closed them. This block only checks if a name is already attached.

+
If / Else — “Assigned User” #2
Assign whoever claimed it
▸ Action · If/Else (assign claimer)

e.g. Assigned Sales Rep is “John” AND Assigned user is emptyAssign to user: John.

🔌 Where the dashboard fits

Your claim already set assignedTo + the Assigned Sales Rep field. This block is the safety net that pins it inside GHL if the native owner was left empty. Add one branch per rep (John, Alex, Joe …).

+
If / Else — “Automatically Assigned?”
The call-vs-text switch
▸ Action · If/Else

Condition: Tags Includes automatically assigned. Fire an Internal Email: “[rep] assigned to [name].”

Automatic
Tag present → SMS only
Manual (None)
No tag → call the rep
+
Wait — 1 minute
▸ Wait (1 min)

A beat so it's not robotic, then add the source tag.

+
⬇ Automatic path · no call
SMS
“Not free now — book me: {{calendar}}”
▸ Send SMS

Nobody's available (2 a.m.) → point to the calendar, not "chat now."

⬇ Manual path · auto-dial
Call (whisper + bridge)
Rings the rep, connects the lead
▸ Action · Call

Calls the rep, plays a whisper: “Hey {{user.first_name}}, {{contact.name}} inquired about {{contact.services}} — press any key to connect to {{contact.first_name}}.” Keypress → bridges to the lead. Timeout 60s.

★ The magic

Live lead on the line with zero dialing.

+
SMS (if no answer)
“Free to chat now? Or book: {{calendar}}”
▸ Branch on call result

Answer → Stop on Response removes them. No answer → this SMS. Manual copy says "chat now" (a rep is available).

+
Wait → Email #1 → Wait 1 day → Email #2 …
Escalating cadence
▸ Nurture loop

Wait 1 min → Email #1. Wait 1 day → Email #2. Another day → next, widening the gap.

✦ Customize

Every email's one job: book a call. Add lead magnets; split-test once live.

● Stop on Response removes them the instant they reply

3Deep dive — making “assign” actually stick in GHL

This is the part most third-party builds get wrong. The New Lead workflow reads two different "owner" signals, so a claim has to write both.

On claim, your dashboard writes ALL of:
  • 1
    Native ownerPUT /contacts/{id} with assignedTo: <repUserId>. Read by the "Assigned user" conditions.
  • 2
    Assigned Sales Rep field — same PUT, custom field = the rep. Read by the #2 assign block.
  • 3
    Rep-name tagPOST the rep's tag (so it's NOT the auto-assigned path).
  • 4
    Start New Lead — fire its webhook (which removes from Assignment Of Lead).
Why both owner signals?

Block #1 (keep-existing) checks Assigned user + Assigned Sales Rep is not empty — that's the returning-client case. Block #2 (assign-claimer) checks Assigned Sales Rep is [rep] + Assigned user is empty. If your claim only set the native owner, block #2 never fires and the lead can land unassigned in the field the rest of the system trusts. Write both, every time.

🧭

Rep map: "John" in your UI must resolve to John's GHL user ID for assignedTo. Pull GET /users once, cache name→ID.

Verify the wiring

Troubleshooting
ProblemFix
Claim creates a duplicate contactUse upsert / search-by-email before write. Never blind-create.
Lead lands unassigned in reportsYou set assignedTo but not the Assigned Sales Rep field. Write both.
Workflow didn't startYou fired the webhook before the contact existed. Upsert first, webhook last.
Auto-assigned leads still get calledThe fork reads the wrong tag — confirm automatically assigned.
Round-robin skips repsTurn OFF "Only apply to unassigned" on the Assign User action.
FAQ
If the form is third-party, why keep workflows in GHL?

GHL already handles A2P texting, call bridging, deliverability, and scheduling. Rebuilding those is wasted effort. Build the front end; let GHL be the engine.

Webhook trigger or Add-to-Workflow endpoint?

Either works. Inbound Webhook is simplest to wire and is what the diagrams show. Add-to-Workflow is handy when you already have the contact ID and want an explicit call.

What does the dashboard need to READ from GHL?

Users (reps), custom fields, pipelines/stages, calendars, tags, and contacts — to render dropdowns and write to the right IDs. See the contract, group ④.

Do the sales-call forms work the same way?

Yes — same pattern: search the contact, write fields/notes/opportunity/appointment, then fire the Sales Call workflow last. See the contract, group ③.

Why remove Referral / Country / Tax Rate / Time Zone?

Optional enrichment for multi-region businesses. Removed to keep the teaching build fast; add back if you operate across regions.

📋Appendix — Pull your GHL IDs

Your dashboard talks to GHL using the hidden internal IDs behind your assets, not their names (a field called "Assigned Sales Rep" is really aZ8k2Lp9… under the hood). You have two ways to get them: paste the prompt below into GHL Ask AI (fastest, gets everything at once), or grab each one by hand from GHL using the click-paths below.

Easiest path: run the Ask AI prompt at the bottom of this section. It returns every ID in one shot, including the Custom Field IDs and Stage IDs that GHL hides from the normal interface. Only reach for the manual method if Ask AI isn't enabled on your plan.

What this build needs (and what it doesn't)
AssetNeed it?Why
Location ID✅ Yes ×1Every API call is scoped to the sub-account
Workflow IDs✅ Yes ×5Start workflows from the dashboard + the "not active in" checks
Custom Field IDs (+ key + options)✅ Yes ×7The dashboard writes to these exact fields
Pipeline ID + Stage IDs✅ Yes ×1Create/move the opportunity into the right stage
Calendar ID✅ Yes ×1Book the follow-up appointment via API
User IDs✅ Yes ×repassignedTo needs the user ID, not the name
Tag IDs❌ NoThe API adds/removes tags by name — just keep the exact spelling
Trigger links❌ NoThe claim now happens in your dashboard; the GHL claim link is retired
Private Integration token🔑 AuthNot an ID — the runtime key your dashboard uses. Create once, store server-side
Where to find each ID by hand in GHL

Most IDs are the long string in your browser's address bar when you have that item open. The two GHL hides (field + stage IDs) are why Ask AI is the easy button.

AssetClick-path in GHLShows the ID?
Location IDSettings → Business Profile, or read the address bar after /location/✅ URL
Workflow IDAutomation → Workflows → open the workflow → it's the long string in the address bar✅ URL
Pipeline IDOpportunities → open the pipeline → address bar◑ Pipeline in URL; stages hidden → Ask AI
Calendar IDCalendars → Calendar Settings → open the calendar → address bar / booking link✅ URL
User IDSettings → My Staff (Team) → open the person → address bar✅ URL
Custom Field IDSettings → Custom Fields → open the field. You'll see the field key (e.g. contact.type_of_source) but usually not the raw ID❌ Hidden → Ask AI
🏷️

Tags & trigger links — nothing to look up. The API adds/removes tags by name, so just keep the exact spelling of: new lead contract lost contract won automatically assigned + your source tags + one per rep. Trigger links are retired in this architecture (the claim is a dashboard API call now).

The Ask AI lookup prompt

Replace every [bracketed] name with your real asset name, then paste the whole thing into GHL → Ask AI. It pulls only the assets you name, so it never truncates the way a full-account dump does. Anything it can't find by that exact name comes back as NOT FOUND so you know to fix the spelling.

Return the GoHighLevel internal IDs for ONLY the specific assets I name below. Do NOT list any other assets in this account. For any item you cannot find by the exact name given, write "NOT FOUND" next to it so I know to fix the name. Output each section as its own table. LOCATION - Return this sub-account's Location ID. WORKFLOWS — columns: Workflow Name | Workflow ID | Status. Only these: - [New Lead] - [Assignment Of Lead] - [Sales Call [Part I]] - [Sales Call [Part II]] - [Sales Call Recap Email] CUSTOM FIELDS — columns: Field Name | Field ID | Field Key | Options. Only these: - [Assigned Sales Rep] - [Type Of Source] - [Services] - [City] - [Send Contract & Invoice] - [Sales Call Email] - [Disqualified On Sales Call] PIPELINE — for the pipeline named [PIPELINE NAME], return its Pipeline ID, then a table of Stage Name | Stage ID for every stage. I specifically need the New Lead stage and the Sales Call stage. CALENDAR — columns: Calendar Name | Calendar ID. Only this: - [your follow-up / booking calendar name] USERS — columns: Name | User ID | Email. Only these reps (add/remove rows for your team): - [John LastName] - [Alex LastName] - [Joe LastName]
🔌 After you run it

Paste the results back to whoever's wiring the dashboard. With these IDs every row of the integration contract becomes a literal API call (upsert contact, set assignedTo + Assigned Sales Rep, create the opportunity at the New Lead stage, book the follow-up, start the workflow). Tag names you already have.

Revision history
VersionDateChanges
2.1Jun 29, 2026Added the Appendix — Pull your GHL IDs: what to look up vs. skip, the manual click-paths in GHL for each ID, and the copy-paste Ask AI lookup prompt (templated with fill-in asset names).
2.0Jun 29, 2026Rebuilt for third-party architecture: all forms/pages in the Claude Code dashboard, GHL = workflow engine. Added the architecture split + full integration contract + assign-user deep dive. Trigger → Inbound Webhook; claim → API. Renamed rep example Jono → John.
1.0Jun 29, 2026Initial native-GHL teaching build (simplified from the full snapshot).
Speed-to-Lead Build Guide v2.1 · Third-party dashboard + GHL workflow engine · Appendix: GHL ID lookup · Print to PDF to hand out · Customize the ✦ parts to your business.