The Clycyo handbook.
Every snippet on this page corresponds to code that is in production today: the tracker at /tracker.js, the collector at /api/collect, the public demo endpoint at /api/public/demo, and the dashboard's visitor-journey view. If a feature is on the roadmap rather than live, it is marked clearly.
Pageviews, SPA route changes, heartbeat-based duration, click auto-events, custom events, identify, UTM capture, top-referrer attribution, page-load time per pageview, and a public read-only demo endpoint are all in production code.
Revenue attribution and newsletter attribution work end-to-end via track() + identify() + UTM, with a server-side POST /api/collect path for webhook-driven revenue. The dashboard surfaces them on the visitor journey today.
Native data export (JSON, CSV), team accounts, and per-person permission scopes. See the Roadmap section at the bottom of this page or visit /changelog.
Quickstart
The shortest path to a working install. A dedicated quickstart page also lives at /docs/quickstart.
- Create a website in the dashboard and copy its tracking ID from the Add Website flow.
- Add the tracker snippet to your global
<head>. - Open your site once. The tracker fires a
pageviewimmediately, includingload_time_msfrom the browser's Performance API. - Call
window.webanalytics.identify(email)on signup or login to merge the anonymous journey into a known visitor. - Call
window.webanalytics.track('subscription_paid', { revenue, currency })the moment a customer pays.
Want to see the result before you install? Open the live demo — that's the same dashboard rendered against this site's real data.
Tracker install
The current live onboarding flow generates a single script tag that points to clycyo.com/tracker.js.
<script
defer
src="https://clycyo.com/tracker.js"
data-tracking-id="YOUR_TRACKING_ID"
></script>Optional attributes supported by the live tracker:
data-track-clicks="false"disables automatic click-event collection.data-api-urloverrides the collector endpoint if you proxy /api/collect through your own domain.
<script
defer
src="https://clycyo.com/tracker.js"
data-tracking-id="YOUR_TRACKING_ID"
data-track-clicks="false"
data-api-url="https://clycyo.com/api/collect"
></script>What the tracker collects automatically
Everything in this list is verified in the live tracker and collector code.
- Pageviews: URL, referrer, title, visitor ID, session ID, duration, and
load_time_ms. - UTM parameters:
utm_source,utm_medium,utm_campaignfrom the page URL on every pageview. - Session duration: a heartbeat is sent every 30 seconds and again on page hide to update
duration_seconds. - SPA navigations:
history.pushState,history.replaceState, and browser back/forward navigation are intercepted and emit a new pageview. - Auto-click events: tag, selector, text, href, x/y on every click (disable with
data-track-clicks="false"). - Server enrichment: device, browser, OS, country, region, city, latitude, and longitude are added by the collector from the user agent and IP.
- Pixel fallback:
GET /api/collectreturns a 1x1 GIF and stores a pageview when JavaScript is unavailable.
type: 'pageview'
fields: tracking_id, url, referrer, title, visitor_id, session_id, duration_seconds, utm_source, utm_medium, utm_campaign, load_time_msPage load time on every visit
The tracker reads PerformanceNavigationTiming on full page loads, and uses requestAnimationFrame timing for SPA route changes.
- For full document loads:
performance.getEntriesByType('navigation')[0]—loadEventEnd - startTime. - For SPA route changes:
performance.now()athistory.pushState/replaceState/popstate, measured to the next two animation frames. - Values outside
[0, 60000]ms are rejected at the collector to prevent clock-skew or background-tab anomalies polluting the report. - The visitor journey at
/dashboard/<site>/visitors/<visitor_id>shows the load time next to every pageview, e.g. "510 ms load". - Aggregate avg + p75 are exposed in
/api/public/demo.totals.avg_load_msandp75_load_ms.
Use this to answer questions like: "The visitor that converted on the pricing page, did they wait 200 ms or 2 seconds for it?"
JavaScript API
The live tracker exposes a window.webanalytics object with three public methods.
window.webanalytics.track('signup_completed', {
plan: 'pro',
source: 'pricing_page',
seats: 3,
});The collector accepts any event_name and stores arbitrary JSON in event_properties.
// Anywhere on the client. Email is the join key.
window.webanalytics.identify('mario@example.com', {
name: 'Mario Rossi',
source: 'newsletter',
provider: 'beehiiv',
segment: 'b2b',
});Identify upserts a row keyed by (website_id, visitor_id) and merges properties on each call.
window.webanalytics.getVisitorId() returns the visitor ID currently stored by the tracker. Persist it on your user record at signup so server-side revenue webhooks can reattach later — see the next section.
Revenue attribution, end-to-end
Revenue attribution is fully usable today. The journey: capture acquisition on the first pageview, identify the visitor on signup, then track the revenue event from the browser or your billing webhook.
- Capture acquisition — the tracker already saves
utm_source,utm_medium,utm_campaign, andreferreron the very first pageview. No code from you. - Persist the visitor ID at signup — read
window.webanalytics.getVisitorId()and store it on your user record. This is the single piece of code that makes server-side revenue attribution work. - Identify the visitor — call
identify(email, { source }). From this moment onward every event from this browser is associated with this email. - Track revenue from the client when the upgrade happens in-app, OR track from your Stripe / Polar / LemonSqueezy / Paddle webhook — both paths land in the same events table and are joinable to acquisition by visitor_id.
- Read the report — the dashboard shows revenue events on the visitor timeline. To roll up revenue per channel today, query
eventsJOINpageviewson(website_id, visitor_id); native UI aggregation ships with the export feature on 2026-04-30.
// At signup time, persist the Clycyo visitor_id on your user
// record so server-side webhooks can re-attach revenue later.
await api.post('/me', {
clycyo_visitor_id: window.webanalytics.getVisitorId(),
});// Browser-side revenue event (e.g. on the success page).
window.webanalytics.track('subscription_paid', {
revenue: 29,
currency: 'USD',
plan: 'Pro',
billing_provider: 'stripe',
order_id: 'ord_123',
});// Stripe webhook handler (Node.js example). The visitor_id was
// stored on your user record at signup; pass it back here.
import express from 'express';
app.post('/webhooks/stripe', async (req, res) => {
const event = req.body;
if (event.type !== 'invoice.paid') return res.json({ ok: true });
const customer = await loadCustomer(event.data.object.customer);
if (!customer.clycyo_visitor_id) return res.json({ ok: true });
await fetch('https://clycyo.com/api/collect', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
tracking_id: process.env.CLYCYO_TRACKING_ID,
type: 'event',
visitor_id: customer.clycyo_visitor_id,
event_name: 'subscription_paid',
event_properties: {
revenue: event.data.object.amount_paid / 100,
currency: event.data.object.currency.toUpperCase(),
plan: event.data.object.lines.data[0].price.nickname,
billing_provider: 'stripe',
order_id: event.data.object.id,
},
}),
});
res.json({ ok: true });
});On the roadmap (2026-04-30): a revenue rollup table on the dashboard, plus CSV / JSON export of revenue joined to first-touch acquisition. The data is already complete in the events table — only the roll-up UI is pending.
Reports the dashboard surfaces today
Each item below is rendered in the live dashboard and can be re-derived from the public demo endpoint.
Pageviews and unique visitors per day for the selected period (today / 7d / 30d / 90d). Public demo also exposes 30 days as a JSON array.
Per URL: views, unique visitors. Sorted by views, top 8 (public) or top N (dashboard).
Direct, organic, social, referral — each grouped on the raw referrer header before classification.
Source / medium / campaign breakdowns, joined to the pageview that brought them in.
UA-parsed at ingest. Visible on the overview, the visitor list, and on every individual visit.
Country, region, city, lat/lon enriched server-side via ip-api with a 10-minute IP cache.
One paragraph AI summary, plus a chronological timeline of every pageview, click, and custom event — with page load time on each pageview.
Count of visitor_identities created in the period, plus their full event timeline once joined.
track('subscription_paid', { revenue, currency, plan }) is preserved verbatim in events.event_properties — visible on every visitor timeline.
The /api/public/demo endpoint
The live demo at /open is powered by this endpoint. It is cached on the server for 60 seconds, returns aggregate-only data, and never exposes raw visitor IDs or PII.
GET https://clycyo.com/api/public/demo
{
"site": { "domain": "clycyo.com", ... },
"totals": {
"pageviews_30d": 489,
"visitors_30d": 162,
"sessions_30d": 198,
"avg_duration_seconds": 38,
"avg_load_ms": 612,
"p75_load_ms": 980,
"online_now": 4,
"last_event_at": "2026-04-27T12:08:11.302Z"
},
"chart": [{ "date": "2026-03-28", "views": 12, "visitors": 8 }, ...],
"top_pages": [...],
"top_referrers": [...],
"top_utm": [...],
"devices": [...],
"countries": [...],
"browsers": [...]
}Use it for status pages, board-room embeds, or to wire a custom "our metrics" widget into your own marketing site.
Collector reference
The collector endpoint is /api/collect. The tracker uses POST JSON by default and supports a GET pixel fallback.
Required for all POST payloads:
- tracking_id
- visitor_id
Supported types:
- pageview
- event
- heartbeat
- identify
Type-specific fields:
- pageview: url, referrer, title, session_id, duration_seconds, utm_source, utm_medium, utm_campaign, load_time_ms
- event: event_name, event_properties, url, session_id
- heartbeat: session_id, url, duration_seconds
- identify: email, name, properties, sourceGET /api/collect?tid=YOUR_TRACKING_ID&url=https%3A%2F%2Fexample.com&ref=https%3A%2F%2Fgoogle.com&t=HomepageReturns a 1x1 GIF and records a minimal pageview when tid is provided.
Troubleshooting
Use these checks before assuming the tracker is broken.
- Confirm the script uses the exact tracking ID generated when you added the website.
- Install the script globally, not only on one route.
- For revenue attribution, verify that you persisted
getVisitorId()at signup. Without it, server-side webhooks have nothing to join on. - For newsletter attribution, verify the clicked URL still contains the UTM parameters after redirects.
- For page load time:
load_time_msis null on the very first pageview if the script ran beforeloadfired and there's no PerformanceNavigationTiming entry yet — the tracker handles this by deferring untildocument.readyState === 'complete'. - For the public demo: a fresh
clycyo.comsite or a brand-new install will report—until the first events come in. Refresh after browsing two pages.
Shipping by 2026-04-30
Three explicit commitments. Tracked publicly at /changelog.
One-click export of pageviews, events, and visitor_identities to JSON and CSV, scoped per website and per period.
Invite teammates to a workspace by email. Workspace-level membership replaces the single-user model.
Owner, Admin, Editor, Viewer scopes per website inside a workspace, including read-only Viewer for board members and Stakeholder share-links.