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
| Field | Type | Description |
|---|---|---|
email | string | Customer email address (or send hashed_email instead). Hashed server-side before sending to ad platforms. |
event | string | Event name (e.g., purchase, lead, signup) |
Optional Fields
| Field | Type | Description |
|---|---|---|
value | number | Monetary value of the conversion |
currency | string | ISO 4217 currency code (default: USD) |
orderId | string | Order or transaction identifier |
firstName | string | Customer first name |
lastName | string | Customer last name |
phone | string | Customer phone number |
hashed_email | string | Pre-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
- Create a Zap triggered by your CRM event
- Add a Webhooks by Zapier action (POST)
- URL:
https://cleanclicks.yourdomain.com/__cc/inbound - Headers:
X-CC-Api-Key: cc_your_key - Body: map fields from your trigger to the JSON format above
Make.com (Integromat)
- Create a scenario with your trigger module
- Add an HTTP Make a request module
- Method: POST
- URL:
https://cleanclicks.yourdomain.com/__cc/inbound - Headers:
X-CC-Api-Key: cc_your_key,Content-Type: application/json - 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