GuidesInbound Webhooks

Guide: Inbound Webhooks

Send conversion events to CleanClicks from CRMs, automation tools, or custom backends using the inbound webhook API.

Inbound webhooks let you send conversion events to CleanClicks from any external system: CRMs, marketing automation tools, Zapier, Make.com, n8n, or custom backends.

When to Use Inbound Webhooks

Use inbound webhooks when conversions happen outside the browser:

  • CRM events: A lead is qualified in HubSpot, Salesforce, or your CRM
  • Backend purchases: An order is completed through a custom checkout
  • Phone calls: A call tracking system logs a completed call
  • Form processors: A form submission is handled by a third-party system
  • Manual events: Any event you want to push to CleanClicks programmatically

Endpoint

POST https://cleanclicks.yourdomain.com/__cc/inbound

Authentication

Include your API key in the X-CC-Api-Key header:

X-CC-Api-Key: cc_your_api_key_here

Create Inbound Integration Keys in Configuration > Inbound keys tab. See Inbound Integration Keys.

Request Format

Send a JSON payload:

{
  "email": "customer@example.com",
  "event": "purchase",
  "value": 149.99,
  "currency": "USD",
  "orderId": "ORD-12345"
}

Required Fields

FieldTypeDescription
emailstringCustomer email address (or send hashed_email instead). Hashed server-side before sending to ad platforms.
eventstringEvent name (e.g., purchase, lead, signup)

Optional Fields

FieldTypeDescription
valuenumberMonetary value of the conversion
currencystringISO 4217 currency code (default: USD)
orderIdstringOrder or transaction identifier
firstNamestringCustomer first name
lastNamestringCustomer last name
phonestringCustomer phone number
hashed_emailstringPre-SHA-256-hashed, lowercased email (send instead of email to hash it client-side)

Response

Success (200)

{
  "ok": true,
  "event": "purchase",
  "results": {
    "googleads": { "ok": true },
    "meta": { "ok": true }
  }
}

The results object shows the per-platform dispatch outcome. A duplicate event within the dedup window returns { "ok": true, "deduped": true }; a filtered event returns { "ok": true, "filtered": true }.

Error (400/401/403)

{
  "ok": false,
  "error": "Invalid API key"
}

Authentication and validation errors return { "ok": false, "error": "..." }, e.g. Invalid API key (401), email or hashed_email required (400), Inbound webhook not enabled (403). After repeated wrong keys from the same domain, further attempts return Too many failed authentication attempts with status 429 instead of 401. Malformed requests (bad content type, oversized body, invalid JSON) return a plain-text error with the matching status.

How Attribution Works

An inbound webhook carries no browser context, so CleanClicks hashes the email you send and looks it up against the identifiers stored for that hashed email: the click IDs (gclid, fbclid, ttclid, msclkid, and the rest) and UTM fields captured on the visitor's earlier visit to your site.

That stored record only exists if the same email was captured during that visit. The tag writes it when the visitor submits a form carrying their email address (or when your code calls ccRelay.identifyEmail()). No earlier identify means no record, so the lookup finds nothing and the conversion is sent with no click ID attached.

Consent can also suppress the lookup. Under a confirmed opt-out, or when the consent record cannot be read, no identifiers are returned at all.

A matching email does not guarantee a matching ad click. Without a click ID, a destination may skip the event or accept it without attributing it to an ad. Google Ads in particular drops click conversions that carry no gclid, wbraid, or gbraid.

To confirm a test event landed, read the results object in the response: it names each platform and whether that platform accepted the event. Then look up the order ID you sent in the destination platform. A 200 with "ok": true alone does not prove every destination received or attributed the conversion.

Examples

Zapier

  1. Create a Zap triggered by your CRM event
  2. Add a Webhooks by Zapier action (POST)
  3. URL: https://cleanclicks.yourdomain.com/__cc/inbound
  4. Headers: X-CC-Api-Key: cc_your_key
  5. Body: map fields from your trigger to the JSON format above

Make.com (Integromat)

  1. Create a scenario with your trigger module
  2. Add an HTTP Make a request module
  3. Method: POST
  4. URL: https://cleanclicks.yourdomain.com/__cc/inbound
  5. Headers: X-CC-Api-Key: cc_your_key, Content-Type: application/json
  6. Body: JSON with mapped fields

cURL (Testing)

curl -X POST https://cleanclicks.yourdomain.com/__cc/inbound \
  -H "Content-Type: application/json" \
  -H "X-CC-Api-Key: cc_your_api_key" \
  -d '{
    "email": "test@example.com",
    "event": "purchase",
    "value": 99.99,
    "currency": "USD"
  }'

Python

import requests

response = requests.post(
    "https://cleanclicks.yourdomain.com/__cc/inbound",
    headers={
        "Content-Type": "application/json",
        "X-CC-Api-Key": "cc_your_api_key"
    },
    json={
        "email": "customer@example.com",
        "event": "purchase",
        "value": 149.99,
        "currency": "USD",
        "orderId": "ORD-12345"
    }
)

print(response.json())

Node.js

const response = await fetch('https://cleanclicks.yourdomain.com/__cc/inbound', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-CC-Api-Key': 'cc_your_api_key'
  },
  body: JSON.stringify({
    email: 'customer@example.com',
    event: 'purchase',
    value: 149.99,
    currency: 'USD',
    orderId: 'ORD-12345'
  })
});

const data = await response.json();
console.log(data);

Security Notes

  • Always use HTTPS. The endpoint enforces TLS.
  • Keep API keys server-side. Never put them in browser JavaScript.
  • One key per integration. Easier to revoke if compromised.
  • Email is hashed automatically. You send the raw email; CleanClicks SHA-256 hashes it before relaying to ad platforms.

Related: API Keys | Connections Overview