Capturia measures its ads and site traffic with the Meta pixel and Google Analytics. You can decline at any time. Learn more

FR
GoHighLevelIntegration guide

GoHighLevel

Send Capturia the leads of a GoHighLevel form, opportunity or appointment through a workflow's Webhook action, step by step.

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).

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 it with Custom Webhook, a HighLevel premium action: the Webhook action is enough here.

Configure URL and method

URL: https://api.capturia.io/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.

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_consent → true (fixed value — see step 5)
  • source_label → ghl_workflow
  • pipeline.pipeline_slug → the identifier of the target pipeline, copied from Capturia (optional)
  • pipeline.stage_slug → the identifier of the target stage, copied from the same place (optional; without pipeline.pipeline_slug, the stage is looked up in your default pipeline)

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_consent → true (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.

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 Prospects 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.

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.sourceNoyour custom field, or a fixed valueNested utm object, or the flat key utm.source
utm.mediumNoyour custom field, or a fixed valueNested utm object, or the flat key utm.medium
utm.campaignNoyour custom field, or a fixed valueNested utm object, or the flat key utm.campaign
tagsNo"tag1, tag2" (added to the contact tags GHL already sends)Array of strings, or a comma-separated string
pipeline.pipeline_slugNoidentifier copied from Capturia (fixed value)Text identifier of the target pipeline (not its UUID). Copy it in Capturia: Administration, API Keys, "Your pipeline and stage identifiers" section. Without it, the default pipeline is targeted. Nested pipeline object, or flat key.
pipeline.stage_slugNoidentifier copied from Capturia (fixed value)Text identifier of the target stage, copied from the same place. Without it, the stage marked "Default stage" next to the identifiers. Sent without pipeline_slug, it is looked up in your default pipeline. Nested pipeline object, or flat key.

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 business that sends the lead.

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 Prospects 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
400invalid_payloadEmpty body or malformed JSON (extra comma, missing quote)Validate the JSON you send, then send again
401missing_api_keyAuthorization header absent or malformedAdd the header Authorization: Bearer cap_live_...
401invalid_api_keyKey revoked, expired, or mistypedCheck the key in Capturia, generate a new one if needed
403terms_acceptance_requiredAPI terms of use not accepted (or a new version to accept)The account owner accepts the terms in Capturia, on the API Keys page
403insufficient_scopeKey lacks the leads:capture scopeCreate a new key with the "Lead capture" preset
415invalid_content_typeBody sent as a form (Form, x-www-form-urlencoded) instead of JSONSend as JSON (Payload Type = JSON, Encoding = JSON, Body content type = application/JSON)
422invalid_phonePhone missing or format not recognizedUse E.164 (+15145551234) or NANP 10 digits (5145551234)
422invalid_emailMalformed emailSend a valid address, or leave the field out
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_payloadAnother field refused (text too long, malformed date, negative value, etc.) — details names the fieldFix the field named in details, then send again
422pipeline_not_foundpipeline.pipeline_slug matches no pipeline of the accountCopy the pipeline's text identifier again from the API Keys page (it is not its UUID)
422stage_not_foundpipeline.stage_slug matches no stage of the targeted pipelineCopy the stage's text identifier again from the API Keys page, under the right pipeline
429rate_limit_exceededThe key's request quota is exceededSpace out sends at the source (the Retry-After header gives the delay)
429rate_limited_ipMore than 100 requests per minute from the same IP addressReduce the rate or contact support

FAQ — common pitfalls

SymptomCause
Platform test green (200 OK) but lead doesn't show up in pipelineThe same contact was already sent in the last 5 minutes — response status: idempotent_replay, no duplicate (tests often reuse the same number). If the contact already existed, it keeps its stage: see the "not in the right pipeline" entry below.
415 "Unsupported Content-Type" or 400 "Body is not valid JSON"415: the tool sends a form (Payload Type set to Form, form-encoded Encoding) instead of JSON. 400: the JSON is malformed, often an extra comma or a missing quote around a variable.
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.
200 OK response but the lead is not in the right pipeline or stageRead ignored_fields in the response: it names every received field that was not applied. unknown_field: the field carries a name the API does not know (pipeline.id instead of pipeline.pipeline_slug, for instance). existing_contact_kept: the contact already existed and keeps its pipeline and stage. duplicate_field: the same field arrived twice (root and Custom Data, for instance) and the other value was kept.
Lead created without UTM trackingField sent under a name the API does not know (utm_source). Send the nested utm object or the flat key utm.source; ignored_fields names the field that was not applied.
403 terms_acceptance_required after an update that used to workA new version of the API terms of use must be accepted: the account owner accepts it in Capturia, on the API Keys page, and 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 gives your Custom Data sms_consent precedence over those automatic fields — exactly the setup prescribed in step 5, no field renaming required. A standard contact field (email, first name, phone) that you remap in Custom Data keeps the contact's value; if the two differ, the response names your copy in ignored_fields (duplicate_field).
My sub-account is linked through the connector and I also send this webhook: the lead does not change stageWith the connector, the contact often already exists in Capturia when the webhook arrives. An existing contact keeps its pipeline and stage: the webhook's pipeline block is not applied, and the response names it in ignored_fields (existing_contact_kept). Placement then follows your connector's pipeline matches.