Serve the SDK from your own domain
Route spectry.js and its events through your own domain so content blockers treat them as first-party - and keep the SDK updating itself.
Content blockers decide what to block mostly by hostname. A script served from cdn.spectry.io that reports to api.spectry.io is recognisably third-party analytics, and filter lists block it. Depending on your audience that is typically 10–30% of visitors, and technical audiences run much higher.
First-party hosting puts both halves behind your own domain. Your server forwards two paths to Spectry, and from the browser's point of view nothing leaves your site.
What you are setting up
Two paths on a domain you control, pointed at Spectry:
| Path on your domain | Forwards to | Carries |
|---|---|---|
https://example.com/a1b2c3/s.js | https://cdn.spectry.io/spectry.js | The SDK bundle |
https://example.com/a1b2c3/api/* | https://api.spectry.io/api/* | Every event the SDK sends |
Both paths, or neither. Proxying only the script is the most common mistake and the hardest to notice: the SDK loads perfectly, then sends every event to api.spectry.io, where the blocker drops it. You see a working script tag and an empty dashboard.
This is a proxy, not a download
Do not download spectry.js and commit it to your project. A copy stops receiving fixes, browser-compatibility updates and new features the day you commit it, and nothing will tell you it has gone stale.
A reverse proxy re-fetches from our CDN on a cache interval you choose, so SDK releases reach your visitors on their own, exactly as they do on our CDN. The recipes below cache for one hour, which is a good default: releases land within the hour and your origin is not fetching on every page load.
Step 1 — Pick your paths
In Spectry, open Install code for your site and switch Serve the SDK from to Your own domain. You will be asked for three things:
- Your domain — the site visitors actually load, e.g.
https://example.com. - Path prefix — a short segment the two routes live under, e.g.
/a1b2c3. - Script filename — what the bundle is called, e.g.
s.js.
Choose your own prefix and filename. Spectry suggests values unique to your site rather than a shared default on purpose. If every customer used/spectry/spectry.js, that path would be one line in a filter list away from being blocked again — which is the entire thing you are working around. Avoid obvious words likeanalytics,track,statsorpixel; several are already matched by common lists.
The prefix must not collide with your own routes. Everything under it is forwarded to Spectry, so pick a segment your application does not already serve.
Step 2 — Add the proxy rule
The install page generates a ready-to-paste rule for Cloudflare Workers, Nginx, Apache, Caddy, Vercel, Next.js and Netlify with your own paths already filled in. Copy it from there rather than adapting the examples by hand.
Whatever platform you use, the rule has to do four things. Each one fails silently if you get it wrong, so they are worth reading once:
| Requirement | What breaks if it is missing |
|---|---|
| Forward the query string | The SDK loses ?id=, and page-exit events sent with sendBeacon lose the ?_wk= key fallback. Those events are dropped. |
| Forward custom request headers | The site write key travels as X-Spectry-Key on every event. Strip it and events are rejected once strict key checking is on. |
Send an Origin header |
Spectry attributes incoming events to your site by origin. With none, every event is refused with 403 origin_required. Forward the browser's header, or set it explicitly in the rule — setting it is safer, because it does not depend on your site's Referrer-Policy. |
| Pass the script response through unchanged | The bundle is stored pre-compressed and served with Content-Encoding: gzip. A proxy that forwards the bytes while rebuilding the response headers drops that, and the browser receives a binary blob it will not run. |
Use a proxy, never a redirect
A 301 or 302 to cdn.spectry.io is not first-party hosting. The browser follows it and makes the third-party request anyway, so the blocker stops it exactly as before. On Netlify and similar platforms this is the difference between status = 200 (a proxy) and the default redirect.
Step 3 — Check it
Once the rule is live, press Check setup on the install page. Spectry requests both paths from outside your network and reports what actually arrived — whether the script is really our bundle, and whether the query string, custom headers and Origin survived the hop. Each failure names the specific thing to fix.
Step 4 — Install the snippet
The snippet on the install page updates as you type, and already carries the extra attribute that matters:
<script async>
(function(s, p, e, c, t, r, y) {
y = 'YOUR_SITE_ID';
t = p.createElement(e);
r = p.getElementsByTagName(e)[0];
t.async = 1;
t.src = c;
t.id = 'spectry-script';
t.dataset.siteId = y;
t.dataset.writeKey = 'wk_...';
t.dataset.apiHost = 'https://example.com/a1b2c3';
r.parentNode.insertBefore(t, r);
})(window, document, 'script', 'https://example.com/a1b2c3/s.js?id=YOUR_SITE_ID');
</script>
data-api-host is what tells the SDK to send events to your proxy instead of api.spectry.io. Without it you have first-party script delivery and third-party event delivery, which collects nothing.
You can also set data-api-host="auto", which derives the API base from the script tag's own src. That is the same value in a standard single-prefix setup, and it keeps working if you move the site to another domain.
What about a CNAME instead?
Pointing cdn.example.com at Spectry with a DNS CNAME looks simpler, and it is worse in three specific ways:
- Blockers already uncloak CNAMEs. uBlock Origin on Firefox resolves the chain and blocks on the destination, so a CNAME to a known analytics host is blocked much like the host itself.
- It only covers the script. Your events would still go to
api.spectry.iounless you add a second record and we terminate TLS for it — at which point you have done more work than the proxy. - It needs a certificate for your hostname on our infrastructure. That is provisioning and renewal on our side for every customer domain.
A reverse proxy needs no DNS change, no certificate work, and covers both halves. That is what we support.
Troubleshooting
| Symptom | Cause |
|---|---|
| Script URL returns 404 | The rule's path pattern never matched. Check the prefix and filename against the rule exactly. |
| Browser console: script failed to parse, or garbled characters | The proxy dropped Content-Encoding: gzip while forwarding compressed bytes. Pass the upstream response through instead of rebuilding its headers. |
| Script loads, dashboard stays empty | Usually data-api-host is missing, or only the script path is proxied. Both paths have to be mapped. |
403 origin_required in the network tab | The proxy is not sending Origin. Set it explicitly in the rule. |
403 origin_not_allowed | The Origin being sent does not match the site URL configured in Spectry. Add it under Site settings → Allowed domains, or correct the rule. |
| Events arrive but stop at page exit | The query string is being stripped, so sendBeacon loses its key fallback. It cannot set headers, which is why the query string matters. |
| Everything worked, then broke after a deploy | A platform config file (vercel.json, netlify.toml, next.config.js) was replaced without the rule. Keep it in version control. |
Limitations
- CMS plugins are not covered. The WordPress, Shopify, WooCommerce, Magento and Drupal plugins load the SDK from our CDN. To serve first-party on those platforms, remove the plugin and use the manual JavaScript install.
- You own the uptime of that path. If your proxy is down, the SDK does not load. It fails silently and never blocks your page, but no data is collected for that period.
- Blocking is still possible. First-party hosting defeats hostname-based blocking, which is nearly all of it. A visitor who blocks all JavaScript, or who adds your specific path by hand, is still not tracked — and should not be.