Clycyo
Verified docs · 2026-04-27

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.

Tracker verifiedRevenue verifiedNewsletter verifiedPage-load time verifiedPublic demo endpoint liveExport · teams · permissions ship by 2026-04-30
Verified live

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.

Reliable manual workflows

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.

Shipping by 2026-04-30

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.

Start here

Quickstart

The shortest path to a working install. A dedicated quickstart page also lives at /docs/quickstart.

  1. Create a website in the dashboard and copy its tracking ID from the Add Website flow.
  2. Add the tracker snippet to your global <head>.
  3. Open your site once. The tracker fires a pageview immediately, including load_time_ms from the browser's Performance API.
  4. Call window.webanalytics.identify(email) on signup or login to merge the anonymous journey into a known visitor.
  5. 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.

Install

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-url overrides 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>
Collector

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_campaign from 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/collect returns 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_ms
Performance

Page 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() at history.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_ms and p75_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?"

Browser API

JavaScript API

The live tracker exposes a window.webanalytics object with three public methods.

track(eventName, properties)
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.

identify(email, 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.

getVisitorId()

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

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.

  1. Capture acquisition — the tracker already saves utm_source, utm_medium, utm_campaign, and referrer on the very first pageview. No code from you.
  2. 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.
  3. Identify the visitor — call identify(email, { source }). From this moment onward every event from this browser is associated with this email.
  4. 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.
  5. Read the report — the dashboard shows revenue events on the visitor timeline. To roll up revenue per channel today, query events JOIN pageviews on (website_id, visitor_id); native UI aggregation ships with the export feature on 2026-04-30.
1. Persist the visitor ID at signup
// 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(),
});
2a. Browser-side revenue event
// 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',
});
2b. Server-side revenue event (Stripe webhook example)
// 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

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.

Lifecycle

Newsletter attribution, end-to-end

Newsletter signups are first-class today through tagged links plus identify(). The same visitor record holds the newsletter email, the campaign that delivered them, and every later session, click, and revenue event.

  1. Tag every newsletter link with explicit UTM parameters. Beehiiv, Mailchimp, Resend, ConvertKit — they all let you template these in.
  2. Render a confirm or success page that calls identify(email, { source: 'newsletter', provider, list }). This merges the anonymous click-through with a known subscriber.
  3. (Optional) confirm via webhook — if your double-opt-in lives at the provider, send a server-side identify from the provider's webhook handler. You need the visitor_id you stored before the redirect.
  4. Read the report — the dashboard's UTM panel breaks down acquisition by source/medium/campaign; the visitor list shows identified subscribers; the visitor detail page shows their full journey including the newsletter click-through.
1. Tagged newsletter URL
https://clycyo.com/pricing?utm_source=beehiiv&utm_medium=email&utm_campaign=apr_2026_launch
2. Browser-side identify on confirm
// Confirm-double-opt-in success page.
window.webanalytics.identify(subscriber.email, {
  source: 'newsletter',
  provider: 'beehiiv',
  list: 'product_updates',
  consented_at: new Date().toISOString(),
});
3. Server-side identify from a provider webhook
// Provider webhook (beehiiv / Mailchimp / Resend) → Clycyo.
// Use this when the subscriber confirms outside your site.
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: 'identify',
    visitor_id: knownVisitorId, // captured pre-redirect
    email: subscriber.email,
    name: subscriber.name,
    source: 'newsletter',
    properties: { provider: 'beehiiv', list: 'product_updates' },
  }),
});
On the roadmap

On the roadmap (2026-04-30): a dedicated "Subscribers per campaign per day" report — the data is already complete in visitor_identities + pageviews.utm_*; the dedicated UI ships with data export.

Reports

Reports the dashboard surfaces today

Each item below is rendered in the live dashboard and can be re-derived from the public demo endpoint.

Daily traffic chart

Pageviews and unique visitors per day for the selected period (today / 7d / 30d / 90d). Public demo also exposes 30 days as a JSON array.

Top pages

Per URL: views, unique visitors. Sorted by views, top 8 (public) or top N (dashboard).

Top referrers

Direct, organic, social, referral — each grouped on the raw referrer header before classification.

UTM campaigns

Source / medium / campaign breakdowns, joined to the pageview that brought them in.

Devices, browsers, OS

UA-parsed at ingest. Visible on the overview, the visitor list, and on every individual visit.

Geography

Country, region, city, lat/lon enriched server-side via ip-api with a 10-minute IP cache.

Visitor detail journey

One paragraph AI summary, plus a chronological timeline of every pageview, click, and custom event — with page load time on each pageview.

Newsletter / identified visitors

Count of visitor_identities created in the period, plus their full event timeline once joined.

Revenue events

track('subscription_paid', { revenue, currency, plan }) is preserved verbatim in events.event_properties — visible on every visitor timeline.

Public read-only

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.

API

Collector reference

The collector endpoint is /api/collect. The tracker uses POST JSON by default and supports a GET pixel fallback.

POST /api/collect
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, source
GET /api/collect pixel fallback
GET /api/collect?tid=YOUR_TRACKING_ID&url=https%3A%2F%2Fexample.com&ref=https%3A%2F%2Fgoogle.com&t=Homepage

Returns a 1x1 GIF and records a minimal pageview when tid is provided.

Checks

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_ms is null on the very first pageview if the script ran before load fired and there's no PerformanceNavigationTiming entry yet — the tracker handles this by deferring until document.readyState === 'complete'.
  • For the public demo: a fresh clycyo.com site or a brand-new install will report until the first events come in. Refresh after browsing two pages.
Roadmap

Shipping by 2026-04-30

Three explicit commitments. Tracked publicly at /changelog.

Data export

One-click export of pageviews, events, and visitor_identities to JSON and CSV, scoped per website and per period.

Team accounts

Invite teammates to a workspace by email. Workspace-level membership replaces the single-user model.

Per-person permissions

Owner, Admin, Editor, Viewer scopes per website inside a workspace, including read-only Viewer for board members and Stakeholder share-links.