Volume Reach

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 GHLTrigger a one-off call from GHL (advanced)
What it doesEvery new GHL contact — including Facebook and Meta lead-form leads — becomes a Volume Reach contact, and your AI agent calls them within secondsPushes a Volume Reach contact into GHL, by button or from an automationA GHL workflow starts one qualification call and gets the result back. Creates no contact in Volume Reach
SetupOne free GHL action, two pastes. ~5 minutesPaste one token. ~2 minutesTwo GHL workflows + API key. ~20 minutes
RequiresAny GHL plan with WorkflowsAny GHL plan with Private IntegrationsA 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

  1. 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.
  2. Add the action Webhook — the free one, not "Custom Webhook". Method: POST. Paste the URL into URL.
  3. Under Custom Data, click + Add item. Name it anything (for example volume_reach_key) and paste your API key as the value.
  4. 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 showsIt meansFix
No execution at allThe workflow did not runCheck it is published and the trigger matches the contact
401The key did not reach usRe-copy the key into the Custom Data value
400 naming phoneThat contact has no phone numberTest 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

  1. In Volume Reach, go to Settings → CRM Connection.
  2. 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.

  3. Enable these two permissions: View/Edit Contacts and View Locations.
  4. 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

  1. Settings → API Keys → Create.
  2. Name it something descriptive: GHL Production.
  3. Check these scopes: calls:place, calls:read, webhooks.
  4. 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 systemPrompt references 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

  1. Automation → Webhook Endpoints → Create.
  2. Name: GHL Inbound.
  3. 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).
  4. Method: POST.
  5. Click Create. Save the returned signingSecret — you won't see it again.

Step 4 — Subscribe to call.completed

  1. Automation → Triggers → Create.
  2. Name: GHL — All Qualified Calls.
  3. Event type: call.completed.
  4. Webhook endpoint: the one you created in step 3.
  5. Conditions (optional): restrict to outcome: ["INTERESTED", "APPOINTMENT"] if you only want to fire GHL updates for hot leads.
  6. 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).

Add filters so the workflow only fires when you actually want a call:

  • Contact > Phone is 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):

KeyValue
AuthorizationBearer vr_live_... (paste your key)
Content-Typeapplication/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 nameData typePurpose
VR Last Call IDSingle lineLink from contact to Volume Reach call record
VR Last OutcomeDropdownINTERESTED, NOT_INTERESTED, CALLBACK, NO_ANSWER, BUSY, VOICEMAIL, WRONG_NUMBER, DNC
VR Last SummaryMulti-line textOne-sentence summary from the AI agent
VR Last TranscriptMulti-line textFull call transcript (capped at 10 000 chars)
VR Appointment (ISO)Single line or DateConfirmed appointment time
VR Budget RangeDropdownunder_5k, 5k_to_15k, 15k_to_50k, 50k_plus, not_mentioned
VR InterestedBooleanFrom 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/calls dialing 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}} equals call.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

FieldValue
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

  1. Create a test form submission in GHL with your own cell phone number and a test email.
  2. Within ~30 seconds (the delay step), your phone should ring.
  3. Have a short conversation with the AI agent — something like "Yes I'm interested, budget around $10k, Tuesday 2pm works for an appointment."
  4. Hang up.
  5. 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.