Attribution and Events
Install first-party attribution, identify customers, and ingest trusted conversions or revenue.
Kliq connects redirects, external customer identifiers, events, conversions, and revenue through first-party first-touch attribution. Browser events use a publishable site key. Trusted server events use a secret Admin API key. Never put an Admin API key in browser code.
Create a tracking site
Open Organization → Attribution, then create one tracking site for each destination application or group of applications that share the same allowed origins.
- Give the site a recognizable name.
- Add every production hostname that may send browser events. Enter hostnames without paths; full URLs are accepted and normalized.
- Add preview, staging, or local hostnames explicitly when they need tracking. Wildcards are not accepted.
- Hostnames match exactly.
example.comandwww.example.comare two separate entries: listing only the apex silently rejects every event from thewwwhost withforbidden_origin. Add both whenever both resolve. - Choose an attribution window from 7 to 90 days. The default is 30 days.
A site accepts up to 20 hostnames. When a visitor lands on one domain and converts on another, read Multi-Domain Attribution before deciding whether to create a second site.
Kliq creates a publishable key and a copy-ready configuration snippet immediately. The key is intentionally public and allowed origins protect ordinary browser use; they are not proof that an event is trusted. Treat browser events as product telemetry, and send conversions, entitlements, and revenue from your authenticated backend. Disable the tracking site to revoke browser ingestion without deleting historical attribution.
Install the browser SDK
Copy the generated snippet from Organization → Attribution. The hosted, framework-free client works in any browser application without a package-registry login or build dependency:
<script src="https://kliq.sh/kliq.js"></script>
<script>
window.kliq = Kliq.create({
endpoint: "https://kliq.sh/tracking",
publishableKey: "kliq_pk_YOUR_PUBLISHABLE_KEY",
attributionWindowDays: 30,
});
</script>Kliq.create also publishes the instance as window.kliq, so the rest of your
application can reach it. This matters in a bundled application: a const
declared in the install snippet lives in that classic script's scope, which your
components cannot see. Always read the client as window.kliq.
The endpoint is https://kliq.sh/tracking, a first-party path on
Kliq itself that forwards to the tracking backend. Keep it as generated;
the backend host is an implementation detail and may change.
By default, initialization captures lf_id from the current URL, stores it in
first-party localStorage, and removes only that parameter from the visible
URL. Other query parameters and the URL fragment are preserved.
Load the script in the browser before initializing it. You can also pass a
compatible storage implementation. Set captureOnInit: false only when the
application calls kliq.capture() itself.
Guard your integration against the script failing to load — a blocked request,
a content blocker, or a stale URL leaves window.Kliq undefined:
const clickId = window.kliq?.getClickId() ?? readClickIdFromUrl();Reading lf_id from location.search as a fallback keeps the landing a visitor
just arrived on attributable even when the SDK never initialized. The SDK strips
the parameter once it has stored it, so the fallback only fires when nothing
else did.
The conversion funnel
Kliq models attribution as three steps, in the same order as the visit itself:
- Click — the redirect records a click and hands
lf_idto the destination application, where the SDK stores it first-party. - Lead — the first moment you learn who the visitor is. An email from a form is enough; a signed-in user ID is better. The lead binds the stored click to a customer.
- Sale — revenue for a customer that already has a lead. Every sale inherits the link attribution the lead established.
Only the lead step needs the click ID, so it must run in the browser (or receive
a clickId you forwarded to your backend). Sales are pure server calls keyed by
the customer.
Capture a lead
Use lead when the application does not have a user ID yet — a newsletter
form, a waitlist, a demo request. The email becomes the customer key.
await kliq.lead("melvyn@example.com", {
name: "Melvyn",
eventName: "Sign up",
metadata: { form: "newsletter" },
});trackLead is the full form when you have both an email and your own ID:
await kliq.trackLead({
externalCustomerId: currentUser.id,
customerEmail: currentUser.email,
customerName: currentUser.name,
eventName: "Trial started",
idempotencyKey: `lead:${currentUser.id}`,
});eventName defaults to Sign up. When externalCustomerId is omitted the
normalized email is used as the customer key.
lead(email) then identify(userId) is the supported path. The SDK sends the
stored email as previousCustomerKey so Kliq rekeys that row onto your user
id instead of creating a second person. If both rows already exist (two
devices, identify without the stored email), they are merged into the user id.
A later lead(email) does not overwrite the stored user id.
A brand-new browser lead still needs a real same-org click ID. After that first
lead, identify may run without a click if the attribution cookie has expired.
Identify a customer
Call identify after the destination application knows its stable internal
customer identifier. Prefer an internal ID over an email here; use lead when
the email is all you have.
const result = await kliq.identify(currentUser.id, {
idempotencyKey: `identify:${currentUser.id}`,
});
if (!result.ok) {
console.error(result.code, result.retryAfter);
}The SDK attaches the stored click when one is still in the window. If the click
cookie has expired but lead(email) already created the customer, identify
still rekeys that row.
Track a browser event
await kliq.track("signup.completed", {
metadata: {
plan: "pro",
source: "onboarding",
},
idempotencyKey: `signup.completed:${currentUser.id}`,
});Browser ingestion accepts named product events, but it deliberately rejects trusted conversion revenue. Send conversions and money from a server.
Verify the installation
- Open an active short link in a new browser session.
- Confirm the destination initially receives
lf_idand the SDK then removes it from the visible address. - Confirm
kliq:attributionexists in first-party local storage. - Sign in or complete the identified action in the destination application.
- Open the Kliq parent link. Its detail page shows whether attribution is configured, and its Analytics page shows identified users and events.
- Open Customers to inspect the attributed journey.
An invalid_key response means the key does not exist or the site is disabled.
A forbidden_origin response means the request's exact hostname is missing
from the tracking site. A rate_limited response includes retryAfter in
milliseconds.
A customer appears with no attributed link
The customer reached Kliq, so ingestion works; the click ID did not travel with them. Server leads are trusted and accept a missing click rather than failing, which is what turns a broken browser install into a quiet stream of unattributed customers. Work backwards:
- Is the SDK loaded? Check
window.Kliqandwindow.kliqin the console on the destination page. Ifwindow.Kliqis undefined the script itself never loaded — usually a stale or wrongsrc. - Did the click store? Look for
kliq:attributionin that origin's local storage after opening a short link. - Is the hostname allowed? A browser lead from an unlisted host, including
a
wwwvariant you forgot to add, returnsforbidden_origin. - Does your server call forward the click? If leads are sent from your
backend, confirm it receives
kliq.getClickId()from the browser and passes it asclickId.
Clicks recorded with zero identified users on the Analytics page point at the destination install, not at the link.
List customers
curl "https://kliq.sh/api/v1/customers?limit=50" \
-H "Authorization: Bearer $KLIQ_API_KEY"