CRM Outcome Webhook

Integration guide for developers

Send a lead's real disposition back to ClickLens after your sales team works it. A form-fill that fires a “Lead” conversion looks identical whether it becomes a customer or gets thrown out as spam, and Smart Bidding and Advantage+ keep buying more of whatever you feed them. When your CRM marks a lead closed_won or disqualified, post that outcome here. ClickLens reconciles it against the conversion it already scored.

This is the unbiased ground truth the submit-time score cannot have: whether a human filled the form is not the same question as whether the lead was worth money.

Get your URL and secret

  1. Open Settings → CRM Outcome Webhook.
  2. Click Generate secret. The signing secret is shown once. Copy it before you leave the page — it is never displayed again.
  3. Copy the Webhook URL. It contains your account's webhook id and looks like https://app.clicklens.io/api/v1/webhooks/crm/clw_<id>.

Regenerating the secret rotates it. Any recipe still signing with the old secret stops working until you update it.

Send an outcome

Make a POST to your webhook URL with a JSON body and an HMAC-SHA256 signature.

Headers

Header Value
content-type application/json
x-clicklens-signature Hex HMAC-SHA256 of the exact request body, keyed by your secret.

Body

Field Type Required Notes
outcome string Yes closed_won or disqualified. Case-insensitive.
clickId string One of these three The gclid, fbclid, msclkid, or ttclid your CRM captured.
sessionKey string One of these three The ClickLens session key, if you stored it on the lead.
conversionEventId string One of these three The ClickLens conversion event id, the most precise key.
value number No The lead's reconciled revenue. Used to restate value on a down-weight.

Supply at least one of clickId, sessionKey, or conversionEventId. ClickLens matches in that order of precision and reconciles the most recent matching conversion. For lead-gen, clickId is the practical key: most CRMs already capture the gclid in a hidden field.

Sign the body

Compute the signature over the raw bytes you send, not a re-serialised copy. In Node:

import { createHmac } from 'node:crypto';

const body = JSON.stringify({ outcome: 'disqualified', clickId: 'EAIaIQ...' });
const signature = createHmac('sha256', SECRET).update(body, 'utf8').digest('hex');

await fetch(WEBHOOK_URL, {
  method: 'POST',
  headers: { 'content-type': 'application/json', 'x-clicklens-signature': signature },
  body,
});

What each outcome does

Outcome Effect
disqualified The conversion's verdict is set to retract and re-queued for write-back. On Google and Microsoft Ads it becomes a retraction keyed by click-id; Meta and TikTok have no per-click adjustment API, so the retraction there is report-only.
closed_won The conversion is confirmed genuine. A pending suppression is cancelled, so a real lead is never retracted. The recorded outcome also measures how often a flagged lead was in fact real.

Write-back to your ad platform is in measured rollout. Today, posting an outcome records the disposition and shows the adjustment ClickLens would make on your Conversion Protection dashboard; it does not yet change data on the ad platform. Each gate runs in shadow mode until its false-positive rate is measured on real traffic, after which a disqualified outcome on Google or Microsoft Ads is retracted on the next sync. See Conversion Protection for the full rollout discipline.

Responses

Status Body Meaning
200 { "received": true, "reconciled": true, "outcome": "..." } The matching conversion was reconciled.
200 { "received": true, "reconciled": false, "reason": "no_matching_conversion" } Authenticated, but no conversion matched the key. Not an error — do not retry.
400 { "error": "..." } Missing or invalid outcome, or no key supplied.
401 { "error": "..." } Missing or invalid signature, or unknown webhook id.

A 200 with reconciled: false is deliberate: a click that never converted through ClickLens has nothing to reconcile, and retrying will not change that.

Recipes

Zapier

  1. Trigger: your CRM's “Deal stage changed” (or equivalent) event.
  2. Filter to the stages you treat as closed_won and disqualified.
  3. Action: Code by Zapier (JavaScript). Build the JSON body, compute the x-clicklens-signature with crypto.createHmac, and return both.
  4. Action: Webhooks by Zapier → POST to your webhook URL, passing the body and the signature header from the previous step.

Store the secret in a Zapier “Storage” or environment value, not inline in the step.

HubSpot

  1. Create a Workflow triggered on deal stage entering “Closed won” or “Closed lost”.
  2. Add a Custom code action (Node 18). Read the deal's stored gclid and amount, build the signed request as in the Node example above, and POST it.
  3. Put the secret in the workflow's Secrets store and read it with process.env.

Salesforce

  1. Add an Apex trigger or Flow on Opportunity stage change.
  2. In an Apex callout, build the JSON body and compute the signature with Crypto.generateMac('hmacSHA256', body, secret), then EncodingUtil.convertToHex.
  3. Send the POST with the signature in the x-clicklens-signature header. Store the secret in a protected Custom Metadata or Named Credential.

Security

The signature is the only thing standing between your conversion data and a forged outcome. An attacker who could post an unsigned closed_won would launder a fraud conversion into your ground truth; one who could post a forged disqualified would retract real conversions. ClickLens verifies the HMAC in constant time and rejects anything that does not match before reading the body. Keep the secret server-side, rotate it if it leaks, and never expose it in client-side code.