Getting Started with JAMP

Add privacy-first analytics to your website in under 2 minutes. No cookies, no trackers, strictly business.

Installation Guides

Add JAMP to your website by following the framework-specific guide below. Replace YOUR_WEBSITE_ID with the ID from your Settings page.

For Next.js App Router, inject the script into your root layout.

app/layout.tsx
export default function Layout({ children }) {
  return (
    <html>
      <head>
        <script defer
          data-website-id="YOUR_ID"
          src="https://jamp.io/tracker.js" />
      </head>
      <body>{children}</body>
    </html>
  )
}

If you are using the Next.js App Router, remember that Server Components cannot handle user interactions or access the window object. To track clicks, you must create a dedicated Client Component:

components/TrackButton.tsx
"use client";

export default function TrackButton() {
  const handleClick = () => {
    if (typeof window !== "undefined" && window.jamp) {
      window.jamp.track('signup_click');
    }
  };

  return <button onClick={handleClick}>Sign Up</button>;
}

You can then safely import and use <TrackButton /> inside any of your Server Components.

That's all you need. Pageviews are tracked automatically. Zero configuration required unless you are setting up an ad-blocker bypass proxy.

Custom Event Tracking

Track button clicks, signups, purchases, and contextual workflows seamlessly using a one-line JavaScript call:

app.js
// Track a standard signup
window.jamp.track('signup')

// Track a custom purchase conversion
window.jamp.track('purchase')

// Apply tracking to specific standard DOM nodes
document.getElementById('cta').addEventListener('click', () => {
  window.jamp.track('cta_click')
})

Custom events render natively in your dashboard under the Events tab. Note that they do count structurally towards your monthly event quota.

TypeScript Support

Optionally inject the tracking bindings via your global ambient declarations wrapper.

// Include inside your global.d.ts or similar types file
declare global {
  interface Window {
    jamp: { track: (eventName: string) => void }
  }
}

Real-User Vitals (Core Web Vitals)

Measure LCP, INP, CLS, FCP and TTFB on your real visitors' devices — the field data Google ranks on. Add this second script alongside the standard snippet. It loads separately so your core tracker stays tiny and batches every metric into a single beacon. To keep cost low, vitals are sampled at ~25% of pageloads (more than enough for accurate p75s), and those sampled beacons count toward your event quota. Tune it with data-sample-rate — e.g. "0.1" for 10%, or "1" to capture every load.

index.html
<script defer
  data-website-id="YOUR_WEBSITE_ID"
  data-sample-rate="0.25"
  src="https://www.jamp.io/rum.js">
</script>

Results appear under Monitor → Real-User Vitals, reported as p75 (the 75th percentile Google uses) per page, so a single slow outlier doesn't distort your numbers. Lab-based scores can't see this — only your actual visitors can.

JavaScript Error Tracking

Catch uncaught exceptions and unhandled promise rejections from real visitors, grouped by cause. Messages and stack traces are scrubbed of emails, tokens and long numbers on our servers before storage. The module dedups within each page load and caps sends so an error loop can't flood anything. Error tracking is completely free and never counts toward your quota.

index.html
<script defer
  data-website-id="YOUR_WEBSITE_ID"
  src="https://www.jamp.io/errors.js">
</script>

Already handling an error in a try/catch? Report it yourself so it still shows up, flagged as handled:

app.js
try {
  riskyThing()
} catch (err) {
  window.jampError.capture(err)
}

Errors land under Monitor → JS Errors, grouped by a stable fingerprint so a thousand copies of the same crash collapse into one row.

Source Maps & Symbolication

Minified production bundles turn every stack trace into noise like app.4f3a.js:1:48210. Upload your build's source maps and JAMP maps each frame back to the real file, line and function — so the same crash reads handleSubmit · Checkout.tsx:88:12.

Maps are private — we read them server-side only to symbolicate, and never serve them to browsers. Generate a per-site upload key under Settings → Source maps, then upload from CI after your production build.

1 · Save the uploader

Drop this small script into your repo (e.g. scripts/upload-sourcemaps.mjs). It recursively finds every .js.map under a directory and posts them for one release:

ci.sh
# after your production build, with maps emitted to ./dist
JAMP_SOURCEMAP_KEY="csm_xxx" \
  node upload-sourcemaps.mjs --dir ./dist --release "$GIT_SHA"

Or skip the script and call the API directly — POST a JSON body of { release, files: [{ fileName, content }] } with your key as a bearer token:

curl
curl -X POST https://www.jamp.io/api/sourcemaps \
  -H "Authorization: Bearer $JAMP_SOURCEMAP_KEY" \
  -H "Content-Type: application/json" \
  -d '{"release":"v1.4.2","files":[{"fileName":"app.4f3a.js","content":"<map json>"}]}'

Use the same release tag your tracker reports via data-release (see below) — that's how we line a crash up with the right map. Re-running CI for a release simply overwrites its maps, so it's safe to run on every deploy.

Keep maps out of your public bundle

Upload the maps, then delete the .js.map files (or the //# sourceMappingURL comment) from what you ship, so they're never downloadable by visitors. JAMP keeps the only copy it needs.

Next.js: build with webpack

Symbolication relies on each emitted chunk.js.map sharing the filename of the chunk.js the browser actually loads — the standard behaviour of webpack, Vite, esbuild and Rollup. Turbopack (the default for next build in Next.js 16) emits maps whose names don't match the served chunks, so they won't line up. Until that's supported, build the deploy that uploads maps with next build --webpack and productionBrowserSourceMaps: true.

Releases & Site Health

Add a data-release attribute to any of the three scripts and JAMP attributes every pageview, crash and vitals sample to that deploy. Use a version, a git SHA — anything stable per release.

index.html
<script defer
  data-website-id="YOUR_WEBSITE_ID"
  data-release="v1.4.0"
  src="https://www.jamp.io/tracker.js">
</script>

Then Monitor → Site Health plots traffic, errors and performance on one timeline with your deploys marked as vertical lines. When errors spike, the release that caused it sits right there on the same chart — and the Release impact table ranks every deploy by error rate per 1k views and LCP, so “did that ship make things worse?” becomes a one-glance answer instead of a cross-tab investigation.

Uptime & Heartbeats

Uptime monitoring is a synthetic check run from our servers — nothing in your visitors' browser, and it never counts toward your quota. Turn it on under Settings → Uptime monitoring and we'll request your site every 5 minutes, emailing you the moment it goes down — and again when it recovers, with the total downtime. Results live under Monitor → Uptime.

A single failing run is double-probed before we ever call it down, so transient blips don't page you. Two optional add-ons sharpen the check:

  • Keyword check — give us a string that should appear on the page. If a 200 OK comes back without it, we treat the site as down. Catches blank pages and error shells that still return a healthy status code.
  • SSL expiry — for HTTPS targets we read the TLS certificate and email you when it's within 14 days of expiring, once per certificate. No setup; it just watches.

Heartbeats (dead-man's-switch)

Uptime watches your site; heartbeats watch your jobs. For a cron, backup or scheduled task, create a heartbeat under Monitor → Uptime → Heartbeat monitors and have the job hit its ping URL on every successful run. If we stop hearing from it within its interval (plus a short grace), we email you. It's the inverse of uptime: we alert on silence.

crontab
# ping JAMP at the END of the job, so it only checks in on success
0 3 * * *  /usr/local/bin/backup.sh && curl -fsS https://www.jamp.io/api/heartbeat/hb_your_token

Any GET or POST counts as a ping. Put the curl after your task's success condition (as above) so a failed job goes quiet — and triggers the alert — instead of pinging anyway.

Choosing what gets emailed

Every alert across all pillars — uptime, SSL, heartbeats, errors, performance, traffic — is toggled per-site under Dashboard → Alerts. Turn off anything you don't want in your inbox without disabling the monitoring itself.

Hosted Status Page

Turn your uptime data into a public, branded status page your visitors can check during an incident. Enable it under Settings → Public status page, pick a URL slug, and it goes live at:

https://jamp.io/status/your-slug

The page shows current status, uptime percentages over 24 hours / 7 days / 90 days, a 90-day daily history strip and your recent incidents — all driven automatically by the same synthetic checks, with no separate upkeep. Link to it from your app's footer as a “System status” link. Requires uptime monitoring to be on for that site.

Coming soon

Custom domains (e.g. status.yoursite.com) are on the roadmap. For now the hosted jamp.io/status/… path is live and free.

Ad-Blocker Bypass (Proxy)

By routing analytical tracking networks directly through your application's apex namespace, standard uBlock Origin heuristics treat data endpoints as legitimate first-party resource calls and will never block payloads.

Only the beacon (the data-host endpoint) needs proxying — that's the request filter lists target. Add one wildcard rewrite, then point each script's data-host at the proxied path. The script files themselves load from our CDN as normal.

Next.js / Vercel Configurations:

next.config.ts
module.exports = {
  async rewrites() {
    return [
      {
        source: '/api/jamp/:path*',
        destination: 'https://www.jamp.io/api/:path*',
      },
    ]
  },
}

Then point each script's data-host at the proxied route. The src stays on our domain:

index.html
<script defer data-website-id="YOUR_WEBSITE_ID" data-host="/api/jamp/collect" src="https://www.jamp.io/tracker.js"></script>
<script defer data-website-id="YOUR_WEBSITE_ID" data-host="/api/jamp/rum"     src="https://www.jamp.io/rum.js"></script>
<script defer data-website-id="YOUR_WEBSITE_ID" data-host="/api/jamp/errors"  src="https://www.jamp.io/errors.js"></script>

Nginx Infrastructure:

nginx.conf
location /api/jamp/ {
  proxy_pass https://www.jamp.io/api/;
  proxy_set_header Host www.jamp.io;
  proxy_set_header X-Forwarded-For $remote_addr;
}

Cloudflare Workers Edge:

worker.js
export default {
  async fetch(request) {
    const url = new URL(request.url)
    if (url.pathname.startsWith('/api/jamp/')) {
      const target = 'https://www.jamp.io' + url.pathname.replace('/api/jamp', '/api')
      return fetch(target, request)
    }
    return fetch(request)
  }
}

API Reference

window.jamp.track(eventName: string)

Sends a custom event beacon asynchronously to your JAMP dashboard.

Payload Parameters

eventName — A standard string identifier sequence (max 100 char buffer overflow protect). Allowed e.g.: 'signup'

Heuristic Notes

  • Events utilize Navigator.sendBeacon() protocols for unblocked tracking on close bindings.
  • Global window binding initializes lazily immediately post script append.
  • Standard events strictly track via case-sensitive identifier values.