Documentation

Up and running in minutes

The complete developer reference: installation, configuration, goals and custom events, the REST API, webhooks, and the exact definitions behind every number on your dashboard.

Quick start
1

Add the snippet

Paste this into the <head> of your site. That's the whole installation, under 1KB, deferred, invisible to Core Web Vitals.

index.html
<script defer data-site="YOUR_SITE_ID" src="https://cdn.bobray.com/b.js"></script>
2

Track a goal

Fire a custom event from anywhere in your JavaScript, then define it as a goal in the dashboard:

checkout.js
// after a successful signup window.bobray('signup', { plan: 'growth' })
3

Read your data

Pull stats from the REST API with a token from Settings → API (Scale plan):

terminal
curl https://api.bobray.com/v1/stats?site=YOUR_SITE_ID&period=30d \ -H "Authorization: Bearer YOUR_TOKEN"
Using a CMS? Install in two clicks with our plugins, no code required: WordPress, Shopify, or paste the snippet into any platform's code-injection field.
Configuration

Snippet options

Every option is a data attribute on the script tag. The defaults are right for most sites; change them only when you have a reason.

AttributeDefaultWhat it does
data-siterequiredYour site ID from the dashboard. The only mandatory attribute.
data-spaonAutomatically counts History-API route changes as pageviews. Set "off" to call window.bobray('pageview') yourself.
data-hashoffTreats #/route hash changes as pageviews, for hash-routed apps.
data-outboundonRecords clicks on links leaving your domain as an outbound event with the destination host.
data-downloadsonRecords clicks on common file links (pdf, zip, dmg, csv…) as a download event.
data-excludenoneComma-separated path patterns to never count, e.g. "/admin/*,/preview/*".

Deeper guides: The tracking snippet · Single-page applications · Content-Security-Policy · Localhost & staging

Goals & events

Measure outcomes, not just visits

An event is anything you fire from code. A goal is an event (or a URL visit) you've told the dashboard to treat as a conversion, giving you rates, funnels, and webhooks.

Firing events

events.js
// simplest form: just a name window.bobray('newsletter-signup') // with metadata (strings and numbers only, never personal data) window.bobray('purchase', { value: 49.99, plan: 'growth' }) // a manual pageview, for data-spa="off" setups window.bobray('pageview')
Event names are lowercased, max 64 chars. Metadata is limited to 10 keys per event and is aggregated immediately, individual event rows with metadata are never exposed, only totals and breakdowns.

Defining goals and funnels

In Dashboard → Goals → New, a goal is either an event name (purchase) or a URL pattern (/thank-you). Chain up to eight goals into a funnel under Goals → Funnels to see stage-by-stage drop-off, for example /pricing → checkout-started → purchase. Goals and funnels are available on Growth and above.

REST API

Your numbers, programmatically

The v1 API (Scale plan) returns the same figures as the dashboard, same time zone, same definitions, as clean JSON. Authenticate with a bearer token from Settings → API.

EndpointReturns
GET /v1/statsHeadline aggregates: visitors, pageviews, avg. time, bounce rate for the period.
GET /v1/pagesRanked pages with visitors and pageviews. Supports limit (≤1000) and offset.
GET /v1/sourcesRanked traffic sources grouped as Search, Direct, Social, Email, Referral.
GET /v1/goalsGoal totals with conversions and conversion_rate.
terminal
# top pages for Search visits, last 30 days curl "https://api.bobray.com/v1/pages?site=br_7fk2mq&period=30d&filter=source==Search&limit=10" \ -H "Authorization: Bearer $TOKEN"

Shared parameters: period (day, 7d, 30d, month, custom + from/to), and combinable filter expressions like page==/pricing;country==NL. Rate limit is 600 req/min per token with standard 429 + Retry-After. Full details: Authentication · Endpoints reference · Data export.

Webhooks

Push, when polling is silly

Goal completions, traffic-spike alerts, and report-ready events can POST to your endpoint seconds after they happen, signed, retried, and containing zero visitor-identifying data.

webhook payload
POST https://yourapp.example/hooks/bobray X-BobRay-Signature: sha256=8f3a… { "event": "goal.completed", "site": "br_7fk2mq", "goal": "signup", "page": "/pricing", "meta": { "plan": "growth" }, "occurred_at": "2026-06-29T14:03:22Z" }

Verify the HMAC-SHA256 signature, expect at-least-once delivery with retries at 1/5/30 minutes, and make handlers idempotent. Full guide: Webhooks.

Definitions

What each metric means, exactly

Analytics disagreements are usually definition disagreements. These are ours, stated plainly so your numbers are never a mystery.

MetricDefinition
PageviewOne page load, or one route change in an SPA. Query-string-only changes don't count by default; known bots are filtered before counting.
Visitor / visitPageviews grouped by an anonymous, day-scoped token, derived without cookies and reset daily. Daily uniques are therefore conservative.
Avg. timeMedian engaged seconds per visit, tab must be visible; medians resist the overnight-open-tab problem.
Bounce rateShare of visits with exactly one pageview and no events.
SourceReferrer host grouped into Search / Direct / Social / Email / Referral, with UTM parameters taking precedence when present. See why referrers go missing.
Conversion rateGoal completions ÷ visitors in the period, per goal.
Integrations

Works with your stack

Drop-in plugins and packages for the tools you already use, each linking to its full setup guide.

Developer FAQ

Good to know

No. The tracker is under 1KB and loads with defer, so it never blocks rendering, and each pageview sends one ~300-byte beacon. Most sites see a Core Web Vitals improvement after switching from heavier tools.
Yes, two directives: allow cdn.bobray.com in script-src and api.bobray.com in connect-src. Nothing else is contacted. Full walkthrough in the CSP guide.
Yes, route changes are tracked automatically via the History API. Prefer manual control? Set data-spa="off" and call window.bobray('pageview') on navigation. Hash routers add data-hash="on". Details in the SPA guide.
Add your IP ranges or hostnames under Settings → Exclusions; localhost and common preview domains are filtered automatically. The staging guide covers team setups.
Yes, pass a flat object of strings/numbers as the second argument, e.g. { value: 49.99 }. Metadata is aggregated on arrival; you get totals and breakdowns, never individual rows, and it must never contain personal data.
Deliberately not. One function with two arguments covers pageviews and events; everything analytical lives server-side behind the REST API. Fewer surfaces, fewer bugs, smaller script.
Identical, both read the same store, updated within seconds of ingestion, using the site's reporting time zone for day boundaries. If the numbers ever differ, that's a bug we want to hear about.
Any view exports to CSV; Settings → Export → Full export produces a complete zipped dump; Scale can schedule nightly warehouse drops. Your data stays exportable for 30 days after cancellation, see Exporting your data.

Got a question the docs missed?

Our support team answers technical questions within one business day, and then we fix the docs.