Official SDKs
Javascript SDK
What this is: a tiny, privacy-friendly client library that tracks page views and conversions without cookies. What you get: anonymous visit counts, referrers, and optional conversion goals/amounts. What it won’t do: fingerprint, store cookies, or send PII.
Quick start
Add the loader script
Replace <WORKSPACE_ID>
with your own.
<script>
(function () {
window.scoby = window.scoby || function () {
(window.scoby.q = window.scoby.q || []).push(arguments);
};
// Optional: turn off autologging if you want to call logPageView yourself
window.scoby('init', { autoLogging: true });
})();
</script>
<script async src="https://<WORKSPACE_ID>.s3y.io"></script>
Verify it works
Open your site → devtools → Network. You should see requests to https://<WORKSPACE_ID>.s3y.io/count
after a page load and on navigation (for SPAs). If you’ve set a conversion (below), you’ll see .../count?ev=conversion...
.
API reference
scoby('init', settings?)
Configure the client. Safe to call multiple times; later calls merge settings.
scoby('init', {
autoLogging: true, // automatically logs page views + SPA navigations
// other settings may be added over time
});
scoby('logPageView', payload?)
Manually log a page view. Useful when autoLogging
is false
, or for custom SPA navigation hooks.
scoby('logPageView', {
url: location.href, // optional; default: current URL
referrer: document.referrer, // optional
segments: ['paid', 'eu'] // optional; array of short tags
});
scoby('logConversion', payload)
Log a conversion (goal completion). goal
is required.
scoby('logConversion', {
goal: 'Purchase', // required (short, human-readable)
amount: 129.99, // optional number
currency: 'EUR' // optional ISO code, e.g., 'USD', 'EUR'
});
Idempotency guard: The client de-duplicates page views by URL after cleaning (see URL cleaning). If nothing changed, it won’t send again.
How it works (in one minute)
The inline snippet creates a command queue (
window.scoby.q
) so you can callscoby(...)
before the network script loads.When the script loads, it replaces the shim with the real dispatcher, drains queued commands, and starts tracking.
If
autoLogging: true
:- Logs an initial page view.
- Hooks into
popstate
,hashchange
, andpageshow (bfcache)
for SPA navigation.
Page views are sent to
https://<WORKSPACE_ID>.s3y.io/count
withurl
, optionalref
(referrer origin), and optionalsg
(segments).Conversions are sent to the same endpoint with
ev=conversion
,gl
(goal), optionalamt
/cur
, and the cleanedurl
.
URL cleaning (UTM stripping / allow-list)
To avoid inflating metrics with tracking params, the client “cleans” URLs before sending them.
- Keep only allow-listed query params (
{{{WHITELISTED_URL_PARAMS}}}
in your build). - Remove everything else (typical:
utm_*
,gclid
,fbclid
, etc.). - If the cleaned URL equals the last sent URL, skip sending (prevents duplicates on SPA re-renders).
Note: make sure your allow-list check treats first index as found (i.e.,
indexOf(k) >= 0
, not> 0
). Otherwise the first allow-listed key may be skipped.
Patterns & examples
SPA frameworks (manual navigation hooks)
When autoLogging
is false (you want full control):
scoby('init', { autoLogging: false });
// Log first view
scoby('logPageView');
// Example: Next.js (app router)
import { usePathname, useSearchParams } from 'next/navigation';
useEffect(() => {
scoby('logPageView');
}, [usePathname(), useSearchParams()]);
// Example: React Router
import { useLocation } from 'react-router-dom';
const { pathname, search } = useLocation();
useEffect(() => {
scoby('logPageView');
}, [pathname, search]);
Recording segments (cohorts/tags)
// On login or when you know a visitor belongs to a cohort:
scoby('logPageView', { segments: ['member'] });
// Or pass segments with conversions:
scoby('logConversion', { goal: 'Signup', segments: ['beta'] });
Segments are short, non-identifying tags that help you break down reports (e.g.,
['paid', 'trial', 'nl']
). Avoid PII.
Logging conversions
Checkout thank-you page:
scoby('logConversion', { goal: 'Purchase', amount: 49, currency: 'EUR' });
Lead form success:
scoby('logConversion', { goal: 'Lead' });
Multiple steps: log each milestone with a distinct goal name ('Signup Step 1'
, 'Signup Completed'
).
Privacy & consent
- The client does not use cookies and sends no PII by default.
- Requests use
mode: "no-cors"
,credentials: "omit"
,referrerPolicy: "no-referrer"
. - Under EU ePrivacy/GDPR, basic page-view & conversion counts typically don’t require consent. If you add custom data, ensure it remains non-identifying and documented.
Configuration options (current)
Option | Type | Default | Description |
---|---|---|---|
autoLogging | boolean | true | If true , logs initial page view and SPA navigations (popstate , hashchange , pageshow(bfcache) ). Set to false if you’ll call logPageView yourself after your router updates. |
(The configuration may expand; additional keys will be merged at runtime.)
Transport details
- Endpoint:
https://<WORKSPACE_ID>.s3y.io/count
- Method:
GET
with query string - Cache busting: a random query key is appended per request
- CORS mode:
no-cors
(intentionally opaque—network tab shows the request, but JS can’t read the response)
Troubleshooting
I don’t see any requests.
- Ensure the network script URL is correct:
https://<WORKSPACE_ID>.s3y.io
loads without blockers. - Ad blockers may hide requests—try a clean profile or private window.
Only the first page view logs in my SPA.
- With
autoLogging: true
, the library listens topopstate
/hashchange
. If your router doesn’t trigger these (e.g., some custom history setups), either callscoby('logPageView')
on route changes, or setautoLogging: false
and wire it manually.
Conversions aren’t recorded.
goal
is required ({ goal: 'Purchase' }
).- Make sure you call it on the final state (thank-you page or confirmed event), not mid-checkout.
- Check for console warnings.
Referrer is missing.
- Many browsers drop cross-site referrers with strict policies; the client sends only the origin (e.g.,
https://example.com
) when available.
My analytics show too many entries for the same page.
- Verify your URL cleaning allow-list and the duplicate guard. If the allow-list differs per page, the “cleaned” URL might oscillate. Keep it consistent.
Security & performance notes
- The script is small and async; it won’t block rendering.
- No cookies, no
localStorage
, no cross-site credentials. - Requests are GET and cache-busted; they won’t be served from intermediate caches.
Example: minimal inline loader (queue + init)
Use this if you prefer the smallest possible inline snippet:
<script>
(function () {
window.scoby = window.scoby || function () {
(window.scoby.q = window.scoby.q || []).push(arguments);
};
window.scoby('init', { autoLogging: true });
})();
</script>
<script async src="https://<WORKSPACE_ID>.s3y.io"></script>
Changelog & versioning
We keep the client stable and backwards-compatible. New config keys are additive. Breaking changes—if ever necessary—will be versioned on a new script path and documented here.
Need help?
Spotted something off or have an integration we should document? Ping us at hello@scoby.io—thank you for helping keep these docs sharp!