API docs / Webhooks

Inbound webhooks

Three ways to send a lead into CleanLeads. All three use the same API key, write to the same lists and contacts, and can produce a first-touch intro. None of them invent a person from a cookie graph.

Authentication

Create or copy your key in Settings → Security. Send it as X-API-Key or Authorization: Bearer. Events show up in Admin → Inbound webhooks.

1. Form webhook

Put this behind HighLevel, Typeform, Tally, or any form that can POST JSON. Required: email, company, and a name.

POST https://www.cleanleadsai.com/api/advisory/leads/webhook
X-API-Key: YOUR_API_KEY
Content-Type: application/json

{
  "fullName": "Alex Morgan",
  "email": "alex@acme.com",
  "company": "Acme HVAC",
  "roleTitle": "Owner",
  "phone": "+15125550123",
  "clid": "optional-clid-from-pixel",
  "leadSource": "facebook",
  "formId": "ai-advisory",
  "utm": { "utm_source": "facebook", "utm_campaign": "spring" }
}

CleanLeads confirms the person (Prospeo, then PDL if enabled), enriches the company, and writes an intro. Lists look like Webhook - Facebook - Ai Advisory. Dedupes on email + company (30 days), externalId, or clid.

2. Inbound call webhook

For Twilio, HighLevel voice, or any inbound-call system. Required: phone.

POST https://www.cleanleadsai.com/api/advisory/leads/call-webhook
X-API-Key: YOUR_API_KEY
Content-Type: application/json

{
  "phone": "+12024561111",
  "leadSource": "twilio",
  "externalId": "CAxxxxxxxx"
}

Waterfall is cheap-first: validate the number, Google business match, US public-records name, Apollo, then Harvest only if LinkedIn is still missing. Then company website and intro. Dedupes on phone (30 days) or externalId (use the carrier CallSid).

3. Website visit pixel

Leadfeeder-style company identification, not person-level anonymous deanonymization. The Cloudflare Worker reads the visitor IP and ASN organization at the edge, then posts to CleanLeads. Bots, crawlers, localhost, and staging hosts are dropped. Residential ISPs are logged but not enriched. Business networks and form fills become a company record. Settings → Site pixel has an auto-create list toggle; when it is on, useful visits land in one Site pixel list per account, not a new list per host or campaign.

Install the script

Paste this before </body> on every page you want to measure. Use the same API key as the other webhooks.

<script
  src="https://trk.cleanleadsai.com/pixel.js"
  data-pixel="px_YOUR_PIXEL_ID"
  async
></script>

Copy your ready-made snippet from Settings → Site pixel (Admin → Site pixel shows every account). Each account gets a signed px_ id — do not put the API key in public HTML. Until DNS for trk.cleanleadsai.com is live, the Worker is also at https://cleanleads-pixel.chris-f7c.workers.dev/pixel.js.

Optional attributes

  • data-require-consent="true" — only fires when window.cleanleadsConsent === true. Use this with your cookie banner.
  • data-clid-param="clid" — query parameter that becomes the first-party identity cookie (default clid).

How a pageview is handled

  1. The script no-ops if Global Privacy Control is on, or if consent is required and not granted.
  2. It stores _cl_sid (session), _cl_clid, _cl_vid (first-party visitor id), and optional _cl_email / _cl_company (180 days) as first-party cookies.
  3. It beacons page, host, title, referrer, UTM, session, clid, a first-party visitor fingerprint (user agent + screen + timezone hash), and any first-party identity it already has (email, name, phone, company, timezone, language).
  4. The Worker adds IP, city, region, country, ASN organization from Cloudflare, plus a best-effort reverse-DNS hostname for business networks.
  5. CleanLeads drops bots and test hosts first. Home-network pageviews are stored as visits only. Business networks and identified form fills resolve a company (ASN, reverse DNS, optional IPinfo, then our website lookup). Repeat visits from the same session, visitor id, or company within 24 hours update the page history and do not re-spend enrichment.
  6. When the visitor leaves or hides the tab, one final beacon reports scroll depth and time on page so intent scoring sees how far they read.

Automatic form capture

No code needed beyond the script tag. On every form submit the pixel reads email, phone, first/last/full name, company, and hidden fields (HighLevel, HubSpot, and most CRMs embed extra context there) and sends them with the event. Password, card, and SSN fields are never captured. Typing an email into a visible email field also captures it when the field loses focus — even if the form is never submitted — so abandoned forms still identify the visitor. Captured identities are stitched to all past visits from that browser via the visitor id.

HighLevel recipe (widget / iframe forms)

HighLevel renders widget forms inside a cross-origin iframe, where no analytics pixel can read fields — ours included. The fix is server-side: a GHL workflow webhook posts the submission straight to CleanLeads. It works for every widget form you have.

  1. In HighLevel: Automation → Workflows → Create Workflow.
  2. Add trigger Form Submitted (optionally filter to a specific form, e.g. your report-download form).
  3. Add action Webhook, method POST, URL https://www.cleanleadsai.com/api/advisory/leads/site-webhook with header X-API-Key: YOUR_API_KEY.
  4. Use this JSON body (empty merge tags are ignored automatically):
{
  "kind": "form",
  "leadSource": "ghl-form",
  "email": "{{contact.email}}",
  "firstName": "{{contact.first_name}}",
  "lastName": "{{contact.last_name}}",
  "phone": "{{contact.phone}}",
  "company": "{{contact.company_name}}",
  "page": "{{funnel.page_url}}"
}

CleanLeads creates or updates the contact, runs the full company enrichment waterfall, and writes the first-touch intro — same as an on-page form fill.

For purchases and revenue events (quote accepted, invoice paid, deal won), add a second workflow action posting to the conversions endpoint — this is what feeds the Attribution panel and the Meta Conversions API relay:

POST https://www.cleanleadsai.com/api/attribution/conversions
X-API-Key: YOUR_API_KEY

{
  "email": "{{contact.email}}",
  "eventName": "purchase",
  "value": 4997,
  "currency": "USD",
  "metadata": { "product": "ai-advisory" }
}

Server-to-server (no script)

POST https://www.cleanleadsai.com/api/advisory/leads/site-webhook
X-API-Key: YOUR_API_KEY
Content-Type: application/json

{
  "page": "https://yoursite.com/pricing",
  "host": "yoursite.com",
  "sessionId": "optional",
  "visitorId": "optional-first-party-visitor-id",
  "kind": "view | form | identify | engage",
  "engagement": { "scrollDepthPercent": 75, "timeOnPageMs": 42000 },
  "clid": "optional-contact-id",
  "email": "optional-first-party@acme.com",
  "company": "optional company from form or thank-you URL",
  "ip": "203.0.113.10",
  "asOrganization": "Acme HVAC LLC",
  "ptrHostname": "optional reverse-dns hostname, e.g. mail.acme.com",
  "city": "Austin",
  "region": "Texas",
  "country": "US",
  "utm": { "utm_source": "linkedin" }
}

CLID stitching

CLID is how a known person and an anonymous session become the same contact. Put the CleanLeads contact id (or any id you already stored) on outbound links:

https://yoursite.com/pricing?clid=CONTACT_UUID

The pixel copies that value into _cl_clid. Later pageviews and form posts that include the same clidattach to that contact instead of opening a new one. This is first-party identity — they clicked your link or submitted a form — not a broker cookie.

The pixel also keeps a first-party visitor id: a one-way hash of user agent, screen size, and timezone stored in the _cl_vid cookie. When a visitor submits any form or types their email, all of their past anonymous visits from that browser attach to the same contact — retroactive identification without third-party cookies or cross-site tracking. Stitching order is clid, then email, then visitor id.

Home broadband visits stay labeled as local network. To illuminate those visitors into a business, pass first-party fields the site already has: work email, company, name, or phone on the URL, or call identify after a HighLevel form:

window.cleanleadsIdentify({
  email: "{{contact.email}}",
  clid: "{{contact.id}}",
  firstName: "{{contact.first_name}}",
  lastName: "{{contact.last_name}}",
  company: "{{contact.company_name}}",
  phone: "{{contact.phone}}"
});

A work email domain (not Gmail/Yahoo) becomes the company website hint and we run the same company card lookup. Personal emails and empty HighLevel merge tags are ignored. The visitor id is a first-party, single-site identifier used only to stitch that visitor's own visits together — it is never shared across sites or matched against third-party cookie graphs.

Consent and privacy

  • US default: the pixel may run as first-party analytics. Honor GPC automatically.
  • EU/UK/CA: set data-require-consent="true" and flip window.cleanleadsConsent = true only after the visitor accepts analytics/marketing.
  • We identify companies on business networks. Home ISPs stay labeled as local broadband unless the visitor already gave first-party identity (clid, work email, company). We do not sell a name, email, or postal address from an anonymous cookie.
  • Disclose website analytics and company-level identification in your privacy policy. Sign a processor agreement before using the pixel on a client site.

Need endpoint-level tool docs? Open the API reference.