Go High Level (GHL) Integration
There are three ways to connect Volume Reach and GHL. If you want your new leads called, you want the first one.
| Send new GHL leads to Volume Reach (first section) | Send contacts to GHL | Trigger a one-off call from GHL (advanced) | |
|---|---|---|---|
| What it does | Every new GHL contact — including Facebook and Meta lead-form leads — becomes a Volume Reach contact, and your AI agent calls them within seconds | Pushes a Volume Reach contact into GHL, by button or from an automation | A GHL workflow starts one qualification call and gets the result back. Creates no contact in Volume Reach |
| Setup | One free GHL action, two pastes. ~5 minutes | Paste one token. ~2 minutes | Two GHL workflows + API key. ~20 minutes |
| Requires | Any GHL plan with Workflows | Any GHL plan with Private Integrations | A plan with Workflows + Inbound Webhooks |
Send new GHL leads to Volume Reach
This is the one to use for Facebook and Meta lead forms. They already land in GoHighLevel; this passes each new lead on to Volume Reach. No Zapier.
1. Create the connection in Volume Reach
Go to Automations → Lead Sources → GoHighLevel. Choose where these leads should land and whether your AI agent should call them automatically, then create the connection. You get two things to copy: a URL and an API key. The key is shown once.
Before a lead can be called you need a voice agent and a phone number in Volume Reach. The same screen checks both and links you to whatever is missing.
2. Add one action in GoHighLevel
- Automation → Workflows → Create Workflow. Pick the trigger that matches your leads: Facebook Lead Form Submitted for Meta lead ads, or Contact Created for every new contact. Add the filter Phone is not empty.
- Add the action Webhook — the free one, not "Custom Webhook". Method: POST. Paste the URL into URL.
- Under Custom Data, click + Add item. Name it anything (for example
volume_reach_key) and paste your API key as the value. - Save and Publish.
That is the whole setup. GoHighLevel sends the contact's name, phone, email, tags and address on its own, so there is no request body to write.
Why Custom Data? GoHighLevel's free Webhook action has no place to put a header, which is where an API key normally goes. We find your key by its value, remove it the moment it arrives, and never save it on the contact.
3. Check it is working
Keep the GoHighLevel screen in Volume Reach open — it says Listening for GoHighLevel. In GHL, add yourself as a contact using your own cell number and run the workflow on that contact. Within about ten seconds the screen shows your name, and your agent calls you (one real call, billed like any call). If you chose "just save them", it shows the lead saved instead.
If nothing arrives after a couple of minutes, open the workflow's Execution Logs in GoHighLevel:
| GHL shows | It means | Fix |
|---|---|---|
| No execution at all | The workflow did not run | Check it is published and the trigger matches the contact |
401 | The key did not reach us | Re-copy the key into the Custom Data value |
400 naming phone | That contact has no phone number | Test with a contact that has one |
Good to know
- The same person twice: a lead we already received in the last 24 hours is recognised as a duplicate and is not called again.
- Using "Send contacts to GHL" as well? A contact that Volume Reach has just created in GHL is ignored if GHL immediately sends it back, so the two never loop.
- Prefer the paid Custom Webhook action, or Zapier? Both work: POST to the same URL with an
Authorization: Bearer <your-API-key>header and the body described in the Inbound Leads Webhook reference. GoHighLevel bills Custom Webhook at about 1 cent per lead. - Want more than one call? In the Lead Sources screen choose Build the follow-up with AI — for example "call them, text if they don't answer, then email me".
Send contacts to GHL (the easy way)
Connect once, and every Push to CRM button click — plus any automation with a "Push to CRM" step — writes the contact straight into your GHL sub-account.
Connect it
- In Volume Reach, go to Settings → CRM Connection.
- In GHL, open the sub-account you want contacts to land in → Settings → Private Integrations → Create New Integration.
⚠️ It must be created inside the sub-account, not in Agency Settings. An agency-level token cannot add contacts and will be rejected.
- Enable these two permissions: View/Edit Contacts and View Locations.
- Copy the token (GHL shows it only once) and paste it into Volume Reach. Click Connect.
That's it. Volume Reach confirms which GHL account it connected to.
What gets sent
Each push creates or updates a GHL contact with the owner's name, phone, and property address, applies your Volume Reach tags (adding to any tags already there — nothing is overwritten), and attaches a note with the call outcome, pipeline stage, co-owners, property details, and a link back to the contact in Volume Reach.
Good to know
- Keep duplicate prevention ON in your GHL location (it's GHL's default, under Settings → Business Profile). Volume Reach matches on phone number, so with duplicates allowed, each push creates another new contact instead of updating the existing one.
- If the same person owns several properties, GHL keeps one contact for them, showing the most recently pushed property's address. Each push adds its own note, so the full history stays intact.
- If your token is rotated or revoked (GHL suggests rotating every 90 days), Volume Reach shows a "Needs reconnection" warning on the CRM Connection page — paste a fresh token there to fix it.
- Already using the webhook setup below? Keep it. Both run — connecting a token doesn't turn anything off.
Trigger a one-off call from GHL (advanced)
Most people do not need this section. It starts a single call and creates no contact in Volume Reach — if you want your new leads saved and called, use Send new GHL leads to Volume Reach above.
Trigger a Volume Reach qualification call from any GHL workflow when a new lead arrives, and pipe the outcome (interested? appointment time? transcript?) back into the GHL contact record — moving deals forward automatically.
Time to build: ~20 minutes. GHL plan requirement: any plan with Workflows + Inbound Webhooks (Standard, Unlimited, Agency Pro).
The flow
GHL: New Form Submission → Workflow: Webhook action → POST /api/v1/public/calls
│
▼
Volume Reach dials + qualifies
│
▼
call.completed webhook → GHL Inbound Webhook
│
▼
Update contact, move pipeline stage,
create appointment
Two GHL workflows total: one outbound (fires the call) and one inbound (receives the result and updates the contact). They're decoupled because Volume Reach runs asynchronously — the dial, conversation, transcription, and extraction together take 1–3 minutes.
Part 1: One-time setup in Volume Reach (5 min)
Step 1 — Create an API key
- Settings → API Keys → Create.
- Name it something descriptive:
GHL Production. - Check these scopes:
calls:place,calls:read,webhooks. - Click Create. Copy the
vr_live_...key immediately — it's shown only once. Paste it into your password manager or GHL's secret store.
Step 2 — Configure your Voice Agent
Make sure the agent you'll use is set up with a system prompt that asks the right qualifying questions (budget, timeline, interest level, appointment availability).
- Settings → Voice Agents → Edit.
- In the Prompt tab, confirm your
systemPromptreferences any{{variableName}}you plan to pass in from GHL. For example:Hi {{leadName}}, I'm calling from Acme about the quote request for {{propertyAddress}}. - In the Extraction tab (if available), pick a template (e.g.
Appointment Booking) or define custom fields. The schema determines what GHL receives back. See Structured Extraction. - Copy the agent's
id(e.g.va_abc123...) from the URL bar.
Step 3 — Create the webhook endpoint
- Automation → Webhook Endpoints → Create.
- Name:
GHL Inbound. - URL: the Inbound Webhook URL from GHL (see Part 3 below — if you haven't set up GHL yet, just use
https://webhook.site/<your-test-id>temporarily and come back to update). - Method: POST.
- Click Create. Save the returned
signingSecret— you won't see it again.
Step 4 — Subscribe to call.completed
- Automation → Triggers → Create.
- Name:
GHL — All Qualified Calls. - Event type:
call.completed. - Webhook endpoint: the one you created in step 3.
- Conditions (optional): restrict to
outcome: ["INTERESTED", "APPOINTMENT"]if you only want to fire GHL updates for hot leads. - Save.
Part 2: GHL outbound workflow (fires the call)
Sub-account → Automation → Workflows → New Workflow → start from scratch.
Trigger
Pick whichever GHL event should trigger a Volume Reach qualification call:
- Form Submitted (most common — a lead fills out a landing page form).
- Contact Created (any new contact in your sub-account).
- Opportunity Stage Changed (e.g. moving to "Needs Qualification").
- Tag Added (e.g. when you tag a contact
needs-qualification).
Filters (recommended)
Add filters so the workflow only fires when you actually want a call:
Contact > Phoneis not empty.Contact > Phone Type= "mobile" (optional — avoid calling landlines that can't receive SMS follow-ups).- Wait step: set a short delay (e.g. 30 seconds) to give GHL time to finish contact creation before you trigger the call. This avoids race conditions.
Action: Webhook
Add a Webhook action. Configure as follows:
URL:
https://api.volumereach.com/api/v1/public/calls
Method: POST
Headers (use GHL's header editor):
| Key | Value |
|---|---|
Authorization | Bearer vr_live_... (paste your key) |
Content-Type | application/json |
Body (JSON — use GHL's custom-value dropdowns for each {{...}}):
{
"agentId": "va_abc123",
"phoneNumber": "{{contact.phone}}",
"variables": {
"leadName": "{{contact.full_name}}",
"leadEmail": "{{contact.email}}",
"leadSource": "ghl-{{workflow.name}}",
"propertyAddress": "{{contact.address1}}, {{contact.city}} {{contact.state}}"
},
"metadata": {
"ghlContactId": "{{contact.id}}",
"ghlLocationId": "{{location.id}}",
"ghlWorkflowId": "{{workflow.id}}"
},
"idempotencyKey": "ghl-{{contact.id}}-{{workflow.id}}",
"knownConsent": true,
"consentRecordId": "ghl-form-{{contact.id}}"
}
Idempotency is not optional for GHL. GHL retries webhooks on transient errors. Without
idempotencyKey, a network blip = 2 calls to the same lead. The key pattern above (ghl-<contactId>-<workflowId>) makes the request idempotent across retries.
Save + publish
- Name the workflow clearly:
Volume Reach — Qualify New Leads. - Toggle it Active.
Part 3: GHL inbound workflow (receives call.completed)
GHL's Inbound Webhook trigger gives you a URL that fires a workflow whenever anything POSTs to it. Volume Reach will POST here when the call finishes.
Step 1 — Create the custom fields
Before the workflow can write results back to the contact, create the custom fields Contacts → Custom Fields:
| Field name | Data type | Purpose |
|---|---|---|
VR Last Call ID | Single line | Link from contact to Volume Reach call record |
VR Last Outcome | Dropdown | INTERESTED, NOT_INTERESTED, CALLBACK, NO_ANSWER, BUSY, VOICEMAIL, WRONG_NUMBER, DNC |
VR Last Summary | Multi-line text | One-sentence summary from the AI agent |
VR Last Transcript | Multi-line text | Full call transcript (capped at 10 000 chars) |
VR Appointment (ISO) | Single line or Date | Confirmed appointment time |
VR Budget Range | Dropdown | under_5k, 5k_to_15k, 15k_to_50k, 50k_plus, not_mentioned |
VR Interested | Boolean | From extraction schema |
Your extraction schema should produce fields that match. See Structured Extraction for template schemas.
Step 2 — Create the inbound workflow
Automation → Workflows → New Workflow.
Trigger: Inbound Webhook.
GHL generates a URL that looks like:
https://services.leadconnectorhq.com/hooks/<location_id>/webhook-trigger/<unique>
Copy that URL and paste it into Part 1 → Step 3 (Volume Reach webhook endpoint) if you haven't already.
Step 3 — Test the trigger
In Volume Reach, place a test call via:
- Automation → Public API Dashboard → Test Call (once that page ships), or
- A curl invocation of
POST /api/v1/public/callsdialing your own phone.
Complete the test call. GHL will receive the call.completed webhook and display a sample payload. Click "Use this sample" so GHL knows the field shape.
Step 4 — Build the workflow actions
Add these actions in order:
A. If / Else — "Is this call from the Public API?"
- Condition:
{{inboundWebhookRequest.event}}equalscall.completed. - If false, end the workflow.
B. Find Contact by ID
- Search field: Contact ID.
- Value:
{{inboundWebhookRequest.data.metadata.ghlContactId}}. - This locates the contact the outbound workflow dialed.
C. Update Contact Custom Fields
| Field | Value |
|---|---|
VR Last Call ID | {{inboundWebhookRequest.data.call_id}} |
VR Last Outcome | {{inboundWebhookRequest.data.outcome}} |
VR Last Summary | {{inboundWebhookRequest.data.extraction.summary}} |
VR Last Transcript | {{inboundWebhookRequest.data.transcript}} |
VR Appointment (ISO) | {{inboundWebhookRequest.data.extraction.appointmentDateTimeISO}} |
VR Budget Range | {{inboundWebhookRequest.data.extraction.budgetRange}} |
VR Interested | {{inboundWebhookRequest.data.extraction.interested}} |
D. If / Else — "Was the lead interested?"
- Condition:
{{inboundWebhookRequest.data.extraction.interested}}=true. - True branch: continue to actions E, F, G.
- False branch: add tag
needs-nurture, end workflow.
E. Add Tag: qualified-lead.
F. Move Contact in Pipeline: to your "Sales Qualified" stage.
G. Create Appointment (if appointmentDateTimeISO is set):
- Condition first:
{{inboundWebhookRequest.data.extraction.appointmentDateTimeISO}}is not empty. - Calendar: your sales calendar.
- Start time:
{{inboundWebhookRequest.data.extraction.appointmentDateTimeISO}}. - Duration: 60 min (or
{{inboundWebhookRequest.data.extraction.appointmentDurationMinutes}}if your schema exposes it). - Description: paste the summary + a link back to the call ID for transcript lookup.
H. Send Internal Notification (Slack / email to sales team):
🔥 New qualified lead from Volume Reach
Name: {{contact.full_name}}
Phone: {{contact.phone}}
Outcome: {{inboundWebhookRequest.data.outcome}}
Summary: {{inboundWebhookRequest.data.extraction.summary}}
Appointment: {{inboundWebhookRequest.data.extraction.appointmentDateTimeISO}}
Step 5 — Publish + activate
Name the workflow: Volume Reach — Process Call Result. Toggle Active.
End-to-end test
- Create a test form submission in GHL with your own cell phone number and a test email.
- Within ~30 seconds (the delay step), your phone should ring.
- Have a short conversation with the AI agent — something like "Yes I'm interested, budget around $10k, Tuesday 2pm works for an appointment."
- Hang up.
- Within 60 seconds, check the GHL contact record. The custom fields should be populated. The pipeline stage should have moved. A calendar appointment should exist for Tuesday 2pm.
If any step didn't fire:
- Volume Reach → Automation → Deliveries (once dashboard ships) shows the delivery attempts with status codes.
- GHL → Workflow → Execution Logs shows whether the inbound webhook triggered the workflow and which actions ran.
Payload reference (what GHL receives)
Complete call.completed payload so you can reference field paths while wiring actions:
{
"event": "call.completed",
"timestamp": "2026-04-23T14:05:47.123Z",
"data": {
"call_id": "call_xyz",
"outcome": "INTERESTED",
"duration_seconds": 127,
"started_at": "2026-04-23T14:03:41.000Z",
"recording_url": null,
"transcript": "Agent: Hi Jane...\nJane: Yes, I'm interested...\n(full transcript, capped at 10000 chars)",
"contact": {
"name": "Jane Smith",
"phone": "+15551234567",
"address": "123 Oak St",
"city": "Springfield",
"state": "IL",
"zip": "62704"
},
"variables": {
"leadName": "Jane Smith",
"leadEmail": "jane@example.com",
"leadSource": "ghl-Volume Reach — Qualify New Leads",
"propertyAddress": "123 Oak St, Springfield IL"
},
"metadata": {
"ghlContactId": "<contact-id>",
"ghlLocationId": "<location-id>",
"ghlWorkflowId": "<workflow-id>"
},
"known_consent": true,
"consent_record_id": "ghl-form-<contact-id>",
"extraction": {
"interested": true,
"wantsAppointment": true,
"appointmentDateTimeISO": "2026-04-28T18:00:00Z",
"appointmentDurationMinutes": 60,
"budgetRange": "5k_to_15k",
"summary": "Homeowner interested in a roof estimate, $5-15k budget, wants Tuesday 2pm site visit."
},
"extraction_error": null
}
}
Field-by-field docs: Webhooks. Extraction authoring: Structured Extraction.
Common pitfalls
Phone numbers in non-E.164 format. GHL sometimes stores phones as (555) 123-4567. Volume Reach normalizes automatically via normalizeE164, but if your tenant has international numbers, pre-format with GHL's phone-parser custom field or Volume Reach will reject the request with invalid_phone_number.
Workflow infinite loops. If your inbound workflow adds a tag that matches the outbound workflow's trigger filter, you'll re-trigger the call forever. Guard with a contact-level check: "Only fire if VR Last Call ID is empty" on the outbound workflow, or use a one-time trigger condition.
Missing consent. GHL lead forms must include a TCPA-compliant opt-in checkbox ("I agree to be contacted by phone about my quote request"). Without a documented opt-in, do not set knownConsent: true. Volume Reach logs your attestation but you remain legally responsible.
Over-dialing a bad lead. If someone opts out mid-call (outcome DNC), Volume Reach marks the call but doesn't update GHL automatically. Your inbound workflow should explicitly check {{inboundWebhookRequest.data.outcome}} for DNC, and if present, add the contact to a suppression list, remove them from active pipelines, and do NOT trigger any follow-up workflows.
Rate limits. Volume Reach throttles at 60 POSTs/min per API key for write routes. If you're bulk-importing 500 leads and all trigger the workflow at once, you'll see 429 rate_limited. Add a delay step to spread out the triggers, or split the import.
Webhook delivery retries. If your GHL inbound workflow is temporarily broken, Volume Reach retries with exponential backoff (1m / 5m / 30m / 2h / 6h / 24h — 6 attempts). Failed deliveries show up in Automation → Deliveries where you can replay them after fixing the GHL workflow.
What's next
- Extraction schema authoring — tune the exact fields your GHL workflow receives.
- Webhooks — signature verification, retry behavior, DLQ replay.
- FAQ — troubleshooting specific errors and support process.
- API Reference — full endpoint list + error codes.