GoHighLevel
Datafly Signal upserts contacts into your GoHighLevel (HighLevel / LeadConnector) sub-account server-to-server using the v2 Contacts API. When a visitor identifies, signs up, or submits a lead, Signal creates or updates the matching contact in your CRM — no client-side form-tracking snippet required.
Prerequisites
Before configuring GoHighLevel in Signal you need a HighLevel sub-account (Location), a Private Integration token scoped to write contacts, and the Location ID of the sub-account you want contacts written to.
Step 1: Find your Location ID
- Open the HighLevel sub-account you want contacts written to.
- Go to Settings > Business Profile. The Location ID is shown there (it also appears in the browser URL when you are inside the sub-account, e.g.
.../location/ve9EPM428h8vShlRW1KT/...). - Copy the Location ID.
Step 2: Create a Private Integration token
Private Integrations are static access tokens you mint inside a sub-account.
- In the same sub-account, go to Settings > Private Integrations.
- Click Create New Integration.
- Give it a name (e.g. “Datafly Signal”).
- Under scopes, enable View Contacts and Edit Contacts (
contacts.readonlyandcontacts.write). - Click Create and copy the token immediately — it is shown only once.
Create the token in the same sub-account whose Location ID you configured. A token from a different sub-account, or an agency-level token, will not write contacts to your Location. HighLevel recommends rotating Private Integration tokens every 90 days.
Configure in Signal
Configuration Fields
| Field | Required | Description |
|---|---|---|
access_token | Yes | Private Integration token with the contacts.write scope. Sent as a Bearer token. |
location_id | Yes | The sub-account (Location) ID that contacts are written to. Must match the token’s sub-account. |
Management UI Setup
Add the integration
Go to Integrations > Add Integration > GoHighLevel and choose the Default variant.
Enter credentials
Paste your Private Integration Token and Location ID.
Select consent
Select the consent category that governs marketing contactability (typically marketing). Signal uses it to set the contact’s do-not-disturb flag.
Save
Click Save. Signal begins upserting contacts on the next identify / sign-up / lead event.
API Endpoint
POST https://services.leadconnectorhq.com/contacts/upsert
Authorization: Bearer {access_token}
Version: 2021-07-28
Content-Type: application/jsonThe upsert endpoint matches an existing contact by email and/or phone according to the sub-account’s Allow Duplicate Contact priority, then creates or updates the contact. This makes re-sends idempotent — the same visitor does not produce duplicate contacts.
Identity Signals
GoHighLevel is your CRM of record, so contact fields are sent in clear (not hashed) — this is intentional and unlike ad-platform conversion APIs.
| Trait | Contact field | Normalisation |
|---|---|---|
email | email | trimmed, lowercased |
phone | phone | normalised to E.164 (e.g. +447700900123) |
first_name | firstName | — |
last_name | lastName | — |
name | name | — |
company | companyName | — |
address | address1 | — |
city / state / postal_code / country | city / state / postalCode / country | — |
website | website | — |
Provide identity by calling datafly.identify() when a user logs in, registers, or submits a form:
datafly.identify("user-123", {
email: "jane.doe@example.com",
phone: "+44 7700 900123",
first_name: "Jane",
last_name: "Doe",
company: "Acme Ltd",
city: "London",
country: "GB"
});Consent and do-not-disturb
Signal maps your canonical marketing consent decision to the contact’s dnd (do-not-disturb) flag:
- marketing granted ->
dnd: false(contact is reachable) - marketing denied ->
dnd: true(contact is flagged do-not-disturb)
If your team manages do-not-disturb separately inside HighLevel, remove the dnd mapping from the integration’s Field Mappings so Signal leaves the flag untouched.
Event Mapping
The Default preset upserts a contact on identity-bearing events and drops everything else (analytics-only events such as product views never create empty contacts).
| Signal event | Action |
|---|---|
Identified | Upsert contact |
Signed Up | Upsert contact (sets source from sign-up method) |
Lead Generated | Upsert contact (sets source and a lead-source tag) |
| All other events | Dropped |
Example: Lead Generated
Datafly.js call:
datafly.identify("lead-987", {
email: "sam@example.com",
phone: "+1 415 555 0142",
first_name: "Sam",
last_name: "Rivera"
});
datafly.track("Lead Generated", {
source: "Pricing Page Form",
lead_tag: "website-lead"
});Payload sent by Signal to GoHighLevel:
{
"locationId": "ve9EPM428h8vShlRW1KT",
"email": "sam@example.com",
"phone": "+14155550142",
"firstName": "Sam",
"lastName": "Rivera",
"source": "Pricing Page Form",
"tags": ["website-lead"],
"dnd": false
}Custom Fields
HighLevel custom fields are addressed by per-Location field IDs, so they are not included in the Default preset. To populate one:
- In HighLevel, go to Settings > Custom Fields and note the field’s ID.
- In the Signal integration’s Field Mappings, add a mapping with target
customFields[].idset to that ID andcustomFields[].field_valueset to your source property.
Testing
Trigger an identify
On your site, submit a form or call datafly.identify() followed by datafly.track("Lead Generated", ...).
Check the contact in HighLevel
Open Contacts in the sub-account. The contact should appear (or update) within a few seconds, with the email, phone, name, and any tag you sent.
Inspect in Signal
Open the Signal Event Debugger and confirm the event delivered with a 200/201 response from services.leadconnectorhq.com.
Troubleshooting
| Problem | Solution |
|---|---|
401 Unauthorized | The Private Integration token is wrong or expired. Regenerate it in the sub-account and update the integration. |
403 Forbidden | The token is missing the contacts.write scope, or it belongs to a different sub-account than location_id. Recreate the token in the correct sub-account with Edit Contacts enabled. |
422 Unprocessable | The contact has no usable identifier or an invalid email/phone. Ensure datafly.identify() carries at least an email or phone, and that phone normalises to E.164. |
| Duplicate contacts created | Review the sub-account’s Allow Duplicate Contact setting so upsert matches on the identifier you send (email and/or phone). |
| Contacts created but unreachable for marketing | Check the dnd mapping and your marketing consent category — denied marketing consent sets dnd: true. |
| Custom field not populating | Custom fields use per-Location field IDs. Add a customFields[].id mapping with the correct ID (see Custom Fields above). |
Rate Limits
HighLevel enforces burst limits of roughly 100 requests per 10 seconds and a daily cap (around 200,000 requests) per sub-account. Signal applies a conservative sustained rate by default; for high lead volume, contact your Signal operator before raising it.