Clicktag docs
Custom Events
Clicktag records custom events for free: call window.tt_event(name, metadata) in the browser, or send one JSON POST /events from any backend with no API key. Event names are cut at 256 characters and metadata at 4,096.
Browser event tracking
Record events in the browser using window.tt_event. To record events before latest.js finishes loading, declare the asynchronous buffer queue early in your document:
<script>
window.tt_event =
window.tt_event ||
function () {
window.tt_event.q.push(Array.from(arguments));
};
window.tt_event.q = window.tt_event.q || [];
</script>
<script
async
src="https://clicktag.io/latest.js"
data-hostname="example.com"
></script>Always use standard function syntax rather than an arrow function so that arguments is captured correctly. Keep the q line: latest.js takes over tt_event only when it is missing or has a q array, and replays the queued calls in order. Any other existing tt_event function stays in place, so events are not sent while pageviews continue. Clicktag consumes only its own queue; an existing Simple Analytics sa_event queue is left untouched. To send an action to both providers, call each provider's event API separately.
Triggering events from UI
Call window.tt_event(name, metadata?, callback?) inside event handlers:
<button id="upgrade-btn">Upgrade Plan</button>
<script>
document.getElementById("upgrade-btn").addEventListener("click", function () {
if (typeof window.tt_event === "function") {
window.tt_event("upgrade_clicked", { tier: "pro", period: "annual" });
}
});
</script>TypeScript definitions
If you use TypeScript, augment the global Window interface:
declare global {
interface Window {
tt_event?: (
name: string,
metadata?: Record<string, string | number | boolean>,
callback?: () => void,
) => void;
tt_pageview?: (
path?: string,
metadata?: Record<string, string | number | boolean>,
) => void;
}
}
export {};Naming conventions and normalization
Use short, descriptive event names in lowercase snake_case, such as signup_completed or pricing_opened.
Incoming event names undergo automated normalization:
- The collector truncates names exceeding 256 characters before normalization.
- Non-alphanumeric character sequences are converted to underscores.
- Leading and trailing underscores are stripped.
Event metadata rules
Event metadata must be a plain JavaScript object:
- The collector stores at most 4,096 characters of serialized metadata JSON.
- Payloads exceeding this limit are truncated, which may result in non-parseable JSON.
- Send compact, non-sensitive strings, numbers, or booleans.
- In the browser, keys from
window.tt_metadata(set beforelatest.jsruns) are added to every pageview and event and replace event keys with the same name. A global function named bydata-metadata-collectorreceives that merged metadata plustypeandevent, and the keys it returns override the merged metadata.
Clicktag aggregates event counts by name. Event breakdown visitors counts distinct nonempty visitor hashes only. Pageview breakdowns also count the unique flag of rows without a hash, but the event breakdown does not, so imported events add no visitors. Last seen is the newest event in the selected range. Use the Goals API to measure page or event goals and ordered funnels of 2 to 6 steps. Custom metadata filtering is not supported.
Query aggregated event breakdown data using the API:
curl --fail-with-body -sS --max-time 30 \
'https://clicktag.io/api/sites/demo.bitgate.dev/breakdown?dim=events&limit=10'Event callbacks
The optional callback runs when the event's /simple.gif request loads or fails; a function passed as the second argument is used as the callback. It also runs at once, without a request, when the name is not a string, number or function, or when a name function throws or returns another type. A name that is empty after cleaning, such as "!!!", sends nothing and never calls the callback:
window.tt_event("lead_form_submitted", { source: "nav" }, function () {
console.log("Event dispatch finished");
});The callback is not a success signal; it can run even when no request was sent. It does not guarantee persistence or delivery to permanent storage. Do not gate mission-critical logic on this callback.
Automated event scripts
latest.js does not automatically record link clicks, downloads, or form submissions. Clicktag serves neither /auto.js nor /auto-events.js. The upstream Simple Analytics helper targets SA globals by default and does not route events to tt_event. Replace needed tracking with explicit window.tt_event calls; do not point Clicktag at sa_event to share that helper.
Backend server-side events
Send events directly from backend services via POST /events without loading client tracking scripts:
Use the real incoming visitor user-agent, not a fabricated browser string. This example runs in a backend with fetch support:
async function recordSignup(hostname: string, visitorUserAgent: string) {
if (!hostname || !visitorUserAgent) {
throw new Error(
"A registered hostname and the real visitor user-agent are required",
);
}
const response = await fetch("https://clicktag.io/events", {
method: "POST",
headers: { "Content-Type": "application/json" },
signal: AbortSignal.timeout(10_000),
body: JSON.stringify({
hostname,
type: "event",
event: "signup_completed",
path: "/signup",
metadata: { plan: "starter" },
ua: visitorUserAgent,
}),
});
const result = await response.text();
if (!response.ok) {
throw new Error(`Event collector ${response.status}: ${result}`);
}
return result;
}Server-side requirements and behaviors:
- Send a JSON-encoded object. Use
Content-Type: application/jsonfor clarity, although the handler parses JSON regardless of that header. Form-encoded bodies are not accepted. - Payload must be a single JSON object. Batch requests are not supported.
- Always set
"type": "event". Omitting this field records the request as a standard pageview. - Returns HTTP 200 with plain text
okon success, or HTTP 400 plain text on malformed input. - Pass the visitor user-agent string via the
uafield. - Country is inferred from the supplied browser IANA
timezone, not the request IP. Pass the end user’s timezone when available; absent, unknown, or non-geographic zones remain unknown. Client IP overrides are not accepted. - Timestamps are assigned on server arrival. You cannot pass custom timestamps or backdate events.
- There is no accepted user-ID or client-IP override. Visitor hashes incorporate the UTC day, site, connection IP, user-agent, and a random salt that is replaced every UTC day and deleted once the day ends, so a shared server does not recreate end-user visitor identity. Bot-marked rows are excluded from normal metrics; bot classification includes the effective user-agent and the
botpayload field. - Assign one stable nonempty
idper logical event. Bounded collector replay suppression and report reconciliation are not durable exactly-once delivery. Do not blindly retry after an ambiguous timeout or issue a new ID for the same event.
Review the Collector Specification for low-level protocol details.