Everything you need to get CWho tracking your sites.
Add the CWho tracking snippet to every page you want to track.
Place it inside the <head> of your HTML.
The snippet is under 1KB minified, loads asynchronously, and never blocks rendering.
Go to your site's settings in the CWho dashboard to copy your unique snippet with
your data-site key pre-filled:
<script
defer
data-site="YOUR_TRACKING_KEY"
src="https://cwho.com/t/cwho.min.js"
></script>
<head>, not at the bottom of
<body>. The defer attribute means it won't block page rendering
regardless of placement.
Add the snippet to your theme's header.php, or use a plugin like
Insert Headers and Footers to add it site-wide without editing theme files.
Go to Online Store → Themes → Edit Code and add the snippet to your
theme.liquid file inside the <head> section.
Add the snippet to your custom _document.js or use the
<Script> component in your layout:
import Script from 'next/script';
export default function Layout({ children }) {
return (
<>
<Script
defer
data-site="YOUR_TRACKING_KEY"
src="https://cwho.com/t/cwho.min.js"
strategy="afterInteractive"
/>
{children}
</>
);
}
After installing the snippet, visit your site from a different browser or incognito window. Then check your CWho dashboard — you should see a real-time visitor within seconds.
You can also verify manually:
collect.https://cwho.com/api/collect.200 OK response confirms tracking is working.data-site key matches
the tracking key shown in the CWho dashboard. Keys are case-sensitive.
CWho automatically detects client-side navigation via the
popstate event and history.pushState / history.replaceState
overrides. For most SPAs (React Router, Vue Router, Next.js), no extra configuration is needed.
If you're using a custom routing system, you can manually trigger a pageview:
// Trigger a manual pageview (e.g. after router navigation)
window.cwho.trackPageview();
// Or with custom URL and title
window.cwho.trackPageview({
url: '/dashboard/reports',
title: 'Reports'
});
Track any user action — button clicks, form submissions, purchases, video plays —
with cwho.track().
// Simple event with just a name
window.cwho.track('signup_completed');
// Event with a value property (used for goal tracking)
window.cwho.track('purchase', { value: '99.00' });
// Event with a descriptive value
window.cwho.track('plan_selected', { value: 'pro' });
<button onclick="window.cwho.track('cta_clicked', {value: 'hero_signup'})">
Sign up free
</button>
CWho automatically captures outbound link clicks (links to other domains) as
outbound_click events. The destination URL is stored as the event value.
No configuration required.
You can disable automatic outbound tracking with the data-track-outbound="false"
snippet attribute (see Snippet Options).
Goals let you define and track specific actions as conversions. Create goals in your site's settings under the Goals tab.
Three goal types are supported:
/thank-you). No code changes needed.cwho.track('event_name').Customise snippet behaviour with data-* attributes:
| Attribute | Default | Description |
|---|---|---|
data-site |
required | Your unique tracking key. Found in site settings. |
data-track-outbound |
"true" |
Set to "false" to disable automatic outbound link tracking. |
data-exclude |
"false" |
Set to "true" to exclude the current browser from tracking (stores a localStorage flag). |
data-api-host |
"https://cwho.com" |
Override the API host. Only needed for self-hosted deployments. |
To prevent your own visits from being counted, run this in your browser console on your site. It sets a localStorage flag that tells the snippet to ignore all tracking for that browser:
window.localStorage.setItem('cwho_exclude', '1');
To re-enable tracking in that browser:
window.localStorage.removeItem('cwho_exclude');
No. CWho is completely cookie-free. Visitor identification uses a daily-rotating hash of the IP address and User-Agent string. The hash is never stored with the IP address, and it changes every day, making long-term fingerprinting impossible. No consent banners are required.
Yes. CWho does not collect any Personally Identifiable Information (PII). IP addresses are used only for GeoIP lookup and are never stored. You do not need to add CWho to your GDPR consent flow.
No. The snippet is under 1KB minified, loaded with defer,
and fires after the page has finished rendering. It has no measurable impact
on Core Web Vitals or Lighthouse scores.
Yes. Each domain is a separate "site" in CWho, with its own tracking key. Add as many sites as your plan allows from the dashboard.
CWho creates a queue (window.cwho = window.cwho || []) before the
script loads. Any calls to window.cwho.track() made before the
snippet initialises are stored in the queue and flushed automatically once loaded.
Yes. Events from localhost, 127.0.0.1, and private
IP ranges are accepted but tagged as internal in your dashboard so you can
filter them out.
Email us at support@cwho.com. We typically respond within one business day.
CWho provides a REST API for programmatic access to your sites, analytics, and uptime data. The API is available on Pro and Agency plans.
403 Forbidden response.
Base URL for all API requests:
https://cwho.com/api/v1
All responses are JSON. All timestamps are ISO 8601 in UTC.
Authenticate by passing your API key in the X-API-Key request header.
Find your API key under Settings → API Key in the dashboard.
Keep your API key secret — it has read access to all your sites and data.
# Example: authenticated request with curl
curl https://cwho.com/api/v1/sites \
-H "X-API-Key: YOUR_API_KEY"
All errors follow a consistent shape:
{
"error": "Unauthorized",
"message": "Invalid or missing API key.",
"status": 401
}
| HTTP Status | Meaning |
|---|---|
200 | Success |
400 | Bad request — missing or invalid parameter |
401 | Unauthorized — missing or invalid API key |
403 | Forbidden — plan does not include API access |
404 | Resource not found (or not owned by your account) |
429 | Rate limited — slow down requests |
500 | Internal server error |
Returns all sites associated with your account.
GET /api/v1/sites
# Request
curl https://cwho.com/api/v1/sites \
-H "X-API-Key: YOUR_API_KEY"
# Response 200
{
"sites": [
{
"id": 42,
"name": "My Blog",
"domain": "example.com",
"tracking_key": "abc123def456",
"current_status": "up",
"current_response_ms": 214,
"visitors_today": 1082,
"last_check_at": "2026-04-07T10:30:00Z",
"created_at": "2025-11-15T09:00:00Z"
}
],
"total": 1
}
Returns details for one site by its ID.
GET /api/v1/sites/:id
# Request
curl https://cwho.com/api/v1/sites/42 \
-H "X-API-Key: YOUR_API_KEY"
# Response 200
{
"id": 42,
"name": "My Blog",
"domain": "example.com",
"tracking_key": "abc123def456",
"current_status": "up",
"current_response_ms": 214,
"visitors_today": 1082,
"visitors_yesterday": 937,
"ssl_expires_at": "2026-09-12T00:00:00Z",
"domain_expires_at": "2027-03-01T00:00:00Z",
"last_check_at": "2026-04-07T10:30:00Z",
"created_at": "2025-11-15T09:00:00Z"
}
Returns aggregated analytics data for a site over the specified number of days. Includes daily pageview counts, top pages, referrers, countries, and device breakdown.
GET /api/v1/sites/:id/analytics?days=7
| Parameter | Type | Default | Description |
|---|---|---|---|
days |
integer | 7 |
Number of days to look back. Allowed values: 1, 7,
30, 90. Limited by your plan's retention period.
|
# Request
curl "https://cwho.com/api/v1/sites/42/analytics?days=7" \
-H "X-API-Key: YOUR_API_KEY"
# Response 200
{
"site_id": 42,
"period_days": 7,
"summary": {
"pageviews": 8241,
"unique_visitors": 3107,
"sessions": 4512,
"bounce_rate_pct": 42.3,
"avg_duration_ms": 94500
},
"pageviews_by_day": [
{ "date": "2026-04-01", "pageviews": 1102, "visitors": 410 },
{ "date": "2026-04-02", "pageviews": 987, "visitors": 371 }
],
"top_pages": [
{ "path": "/", "pageviews": 2100 },
{ "path": "/blog", "pageviews": 940 }
],
"top_referrers": [
{ "domain": "google.com", "visitors": 820 },
{ "domain": "twitter.com", "visitors": 214 }
],
"top_countries": [
{ "country_code": "US", "visitors": 1540 },
{ "country_code": "GB", "visitors": 312 }
],
"devices": {
"desktop": 58.2,
"mobile": 36.5,
"tablet": 5.3
}
}
Returns uptime statistics, recent check results, and open incidents for a site.
GET /api/v1/sites/:id/uptime?days=30
| Parameter | Type | Default | Description |
|---|---|---|---|
days |
integer | 30 |
Number of days to look back. Allowed values: 1, 7, 30, 90. |
# Request
curl "https://cwho.com/api/v1/sites/42/uptime?days=30" \
-H "X-API-Key: YOUR_API_KEY"
# Response 200
{
"site_id": 42,
"period_days": 30,
"monitors": [
{
"id": 7,
"url": "https://example.com",
"current_status": "up",
"uptime_pct_30d": 99.87,
"avg_response_ms": 218,
"last_check_at": "2026-04-07T10:30:00Z"
}
],
"incidents": [
{
"id": 3,
"monitor_id": 7,
"started_at": "2026-03-22T14:05:00Z",
"resolved_at": "2026-03-22T14:23:00Z",
"duration_seconds": 1080,
"cause": "Connection refused"
}
],
"recent_checks": [
{
"monitor_id": 7,
"status": "up",
"response_time_ms": 204,
"status_code": 200,
"checked_at": "2026-04-07T10:30:00Z"
}
]
}
429 Too Many Requests response.
The Retry-After header indicates how many seconds to wait.