FR
GoHighLevelIntegration guide

GoHighLevel

Capture leads from GHL forms, opportunities, and calendar events via a workflow webhook — step-by-step with screenshots.

Setup

Create a GHL workflow with a trigger (Form Submitted, Contact Created, Calendar Booked) and the native Webhook action.

Create a GHL workflow

In GoHighLevel, go to Automation > Workflows and click Create Workflow. Pick the trigger (Form Submitted, Contact Created, Calendar Booked — whichever should push the lead to Capturia).

Creating a GHL workflow

Add the Webhook action

As action, pick Webhook. It's the native action that calls an external URL with a custom payload. Don't confuse with Make HTTP Request (more restricted action).

Webhook action in GHL workflow

Configure URL and method

URL: https://app.capturia.io/api/v1/leads/capture

Method: POST

Encoding: JSON (option at the top of the modal)

Custom Headers: add a row Authorization with value Bearer cap_live_xxxxxxxx.

URL method encoding configuration

Build the payload

In Custom Data, add key/value pairs (GHL serializes to JSON automatically when Encoding=JSON). Use GHL Custom Values ({{contact.phone}}, {{contact.first_name}}, etc.) for values.

Example:

  • phone{{contact.phone}}
  • first_name{{contact.first_name}}
  • last_name{{contact.last_name}}
  • email{{contact.email}}
  • sms_consenttrue (fixed value — see step 5)
  • source_labelghl_workflow
Custom Data in the GHL webhook

Capture SMS consent

Recommended: create a GHL Custom Field of type "Checkbox" named SMS Consent, add it to the capture form, then add an If/Else condition in the workflow ("SMS Consent is checked"). In the checked branch only, place the Webhook action with sms_consenttrue (fixed value).

If the client already has a different Custom Field (e.g. Marketing Opt-in), use it in the condition. Hardcoding sms_consent: true without a condition is only acceptable if the upstream form has documented explicit attestation.

SMS Consent Custom Field in GHL

Test and publish

First select a test contact carrying your own cell number and a test email, then click Test Webhook in the action modal: GHL sends the payload to Capturia with that contact's values. A 200 response = a real lead created in Capturia — there is no trial mode: the record shows up under Leads and the SMS agent texts the submitted number if consent is in order. Archive the test record once verified, then save the workflow and toggle it to Publish.

Test and publish GHL workflow

Field mapping

The mapping below uses GHL Custom Values for dynamic values. Adapt according to the Custom Fields available on the client side.

Capturia fieldRequiredSource fieldNotes
phoneYes{{contact.phone}}E.164 (+15145551234), NANP 10 or 11 digits, or formatted (514) 555-1234 / 514-555-1234. 10-digit numbers auto-prefixed with +1.
sms_consentYestrue (fixed value, behind the step-5 condition)Boolean true OR string 'true' / 'yes' / 'oui' / 'vrai' / '1' / 'on' / 'checked' (case-insensitive). Accepted at the body root or inside the `customData` object (GoHighLevel webhooks).
first_nameNo{{contact.first_name}}Lead's first name
last_nameNo{{contact.last_name}}Last name
emailNo{{contact.email}}RFC 5322 valid email
source_labelNo"ghl_workflow"Free-form tag (e.g. 'webflow_form', 'fb_lead_ads')
utm.sourceNo(not natively supported)Nested utm object (not flat)
utm.mediumNo(not natively supported)Nested utm object
utm.campaignNo(not natively supported)Nested utm object
tagsNo"tag1, tag2" or []Array of strings
pipeline.idNo(optional)Target pipeline UUID
pipeline.stage_idNo(optional)Target stage UUID

SMS Consent — CASL / Quebec Law 25

The sms_consent field is required. Canada's Anti-Spam Law (CASL) and Quebec's Law 25 require explicit consent before any commercial SMS. Capturia stores a timestamped record of this attestation for your legal protection.

Recommended pattern — consent captured at source

Add a custom consent question directly in the source form (FB Lead Form custom question, Typeform field, GHL form checkbox). Map this field to sms_consent. Consent is explicit, dated, and tied to the lead who gave it.

Alternative pattern — checkbox on the landing page

If the source form doesn't allow custom questions, add a consent checkbox on the landing page before the ad. Keep a copy of the page (Wayback Machine, dated screenshot) as evidence.

Legal grey area

Hardcoding sms_consent: true without real lead attestation = legal risk. In case of complaint, Capturia may be required to provide proof of consent. Without proof, liability falls on the SMB owner.

Step-by-step test

First select a test contact carrying your own cell number and a test email, then click Test Webhook in the action modal: GHL sends the payload to Capturia with that contact's values. A 200 response = a real lead created in Capturia (no trial mode): the record shows up under Leads and the SMS agent texts the submitted number if consent is in order. Never test with a real customer's record. Archive the test record, then save the workflow and toggle it to Publish.

Error catalog

CodeErrorCauseFix
401missing_api_keyAuthorization header absent or malformedAdd the header Authorization: Bearer cap_live_...
401invalid_api_keyKey revoked, expired, or typoCheck the key in the dashboard, generate a new one if needed
403terms_acceptance_requiredToS not accepted (or version bump forced re-consent)The SMB owner must re-accept ToS in the dashboard
403insufficient_scopeKey lacks the leads:capture scopeCreate a new key with the 'Lead capture' preset
422invalid_phonePhone format not recognizedUse E.164 (+15145551234) or NANP 10 digits (5145551234)
422missing_consentsms_consent absent, false, or unrecognized value — the field is looked up at the body root AND inside the customData object (GHL)Map the consent field to sms_consent (true / yes / oui). From GHL: fixed value true behind a condition on the consent checkbox
422invalid_payloadOther field malformed (email, utm object vs flat, etc.)Check the body format — see the leads:capture API doc
429rate_limited_keyPer-key quota exceeded (often 100 req/min)Throttle at source, or request a tier upgrade from support
429rate_limited_ipPer-IP quota exceededReduce the rate or contact support
400invalid_payloadBody is not valid JSON (Form or form-urlencoded sent instead)Configure Payload Type = JSON / Content-Type = application/json

FAQ — common pitfalls

SymptomCause
Platform test green (200 OK) but lead doesn't show up in pipelinePhone matches an existing contact — response status: idempotent_replay. The lead exists but isn't duplicated. Test sandboxes often reuse the same number.
400 "Body is not valid JSON"Payload Type set to Form instead of JSON, or body sent as x-www-form-urlencoded instead of application/json.
200 OK response but no SMS sent to leadsms_consent: true but AI agent not configured, or SMS AI agent disabled, or no Twilio number provisioned for the client.
Lead created without UTM trackingutm field sent as flat (utm_source) instead of nested object (utm.source). Server expects an object.
403 terms_acceptance_required after an update that used to workToS version bump on Capturia side forces the SMB owner to re-accept in the dashboard before the key works again.
422 missing_consent even though sms_consent is in Custom DataGHL wraps Custom Data in a customData object, which Capturia unwraps automatically (no configuration needed). If the error still occurs, check that the value sent is a fixed true, not a {{contact...}} checkbox interpolation.
422 missing_consent with sms_consent{{contact.my_checkbox}}GHL interpolates the label of the checked option (e.g. "SMS Consent"), never true. Use an If/Else condition on the checkbox and send the fixed value true in the checked branch (step 5).
The contact has a custom field named "SMS Consent" — does it conflict with the sms_consent in Custom Data?No. The GHL webhook sends every custom field of the contact under its display name on its own, in addition to your Custom Data pairs. Capturia always gives precedence to the field you mapped yourself (sms_consent in Custom Data) over those automatic fields — exactly the setup prescribed in step 5, no field renaming required.