Connect GoHighLevel

OAuth-based two-way sync with GHL: customers, opportunities, conversations, and outbound SMS write-back.

Updated4 min readintegrations

Two-way sync for customer records, opportunity status, conversations, and outbound SMS write-back. Owner/Admin connects via OAuth; backfill runs in the background.

What you'll learn

  • What syncs in each direction and what doesn't
  • How SunrAI matches a GHL contact to an existing SunrAI customer
  • What the backfill engine actually does on first connect
  • What's not yet shipped (voice, reply-handler)

What this integration does

GHL → SunrAI:

  • New GHL contacts mirror as SunrAI customers (matched first, then created if no match)
  • Inbound conversations + SMS flow into the customer's communication log
  • Opportunity status changes feed the SunrAI project's status
  • Calendar events (when enabled) appear in the team's schedule

SunrAI → GHL (write-back):

  • Outbound SMS sent from SunrAI also writes to the GHL conversation
  • Customer field updates push to the matched GHL contact
  • Opportunity rows are created/updated when SunrAI project state changes

How the customer matcher works

When a GHL contact arrives (via webhook or backfill), SunrAI runs a three-step matching cascade before creating a new customer:

  1. Email match — exact, case-insensitive
  2. Phone match — canonicalized (strip formatting, country-code normalize)
  3. Address match — canonicalized (lowercase, normalize whitespace, drop unit qualifiers)

A successful match writes a row to customer_external_refs linking the GHL contact ID to the SunrAI customer. If none of the three match, a new SunrAI customer is created and linked.

Match results are idempotent — re-running on the same input doesn't duplicate customers or refs.

Steps to connect

  1. Open Integrations → GoHighLevel

    Settings → Integrations → GHL. Requires owner or admin.

  2. Click Connect → sign in with OAuth

    GHL will ask which sub-account (Location) to grant access to. Pick the location that maps to this SunrAI team. The OAuth scope grants read/write across contacts, opportunities, conversations, and calendars.

  3. Confirm the location is correct

    After redirect, the integration card shows the connected Location ID and Company ID. Double-check this is the right sub-account — every webhook and write-back will scope to it.

  4. Start the backfill

    The backfill runs automatically on first connect. You can also kick it manually with Run backfill if you re-connect later. Status is shown in the GHL Backfill Status widget below the connection card.

What the backfill does

The backfill is a cursor-based background job that pulls historical data in dependency order:

  1. Pipelines (single page — these define the stage taxonomy)
  2. Contacts (cursor: startAfterId + dateAdded)
  3. Opportunities (page-based, one pipeline at a time)
  4. Conversations (page-based, scoped to matched contacts)
  5. Calendars (page-based)

The worker processes 100 records per tick and pauses if GHL's daily quota for your account drops below 1000 remaining requests. Job state lives in ghl_backfill_jobs — you can re-resume after a pause without losing position.

Typical backfill duration: 5–60 minutes depending on your GHL history size. You can use SunrAI normally while it runs.

Product toggles

The settings card includes four product toggles: Contacts, Opportunities, Conversations, Calendars. Today these are fetch/display only — you can see which products are enabled by the OAuth scope, but the on/off switches don't yet gate sync. They will in a follow-up phase.

What's not yet shipped

  • Voice / call sync (Tasks 19–20 of the GHL plan) — calls don't flow either direction yet
  • Reply handler (Phase 3) — inbound replies are stored, but auto-routing them to the right human or back-and-forth threading is not yet wired
  • Per-product sync toggles (UI shipped, behavior not — see above)

Verify it worked

  • Connect a test sub-account → integration card shows Connected with Location + Company IDs
  • Send a test SMS from SunrAI to a contact that exists in GHL → message appears in the GHL conversation within seconds
  • Modify a contact's email in GHL → after the next webhook fires, the matched SunrAI customer's email updates

Troubleshooting

"Connection status shows 'Needs re-auth'." — Your OAuth token expired (GHL rotates them periodically). Click Reconnect and run through OAuth again. Backfill state is preserved.

"Backfill is stuck." — Open the GHL Backfill Status widget. If it shows "Quota paused," the worker is waiting for your GHL daily quota to reset. If it shows "Error," the Last error field has the specific GHL response — usually a rate-limit blowout or an unauthorized scope.

"A GHL contact appears as a new SunrAI customer instead of matching to the existing one." — The match cascade failed on all three keys. Most common cause: address formatted differently in the two systems (apartment number qualifier, abbreviation differences). Fix by manually merging the duplicate customer in SunrAI; the matcher will use the merged record going forward via customer_external_refs.