vTilt
By roleFoundersKnow what to fix firstMarketersSee what happens after the clickCMOsSee what your budget really buys
By businessSaaSTurn signups into active usersEcommerceSee why shoppers abandon cartsAgenciesProve your work to clientsEnterpriseOne customer record for every team
MeasureWeb AnalyticsFind where buyers leaveSession ReplaySee why they left, not just that they didAdsKnow what paid visitors do nextIntegrationsOne snippet feeds your stack
ThinkPeopleOne profile per customerCustomer MemoryvTilt remembers every visitorSite KnowledgeAnswers and audits from your own pages
ActAsk AIAnswers from real behaviorAI ChatChat that knows the visitorEmail CampaignsEmail from real visits
PricingWhy vTiltDocs
Docs / Reverse proxy
Getting Started
OverviewInstallInitializeIdentify usersTrack eventsLogout & resetVerify eventsCommon mistakes
Guides
Event forwardingReverse proxyRealtime dashboardSite knowledgeStudio
Frontend frameworks
Next.jsNuxt.jsVue.jsReactReact RouterRemixGatsbySvelte / SvelteKitAstroAngularTanStack StartDocusaurus
Backend frameworks
NestJSHonoCloudflare WorkersDjangoFlaskLaravelPhoenixRuby on Rails
Backend languages
PythonPHPRubyElixirGoJava.NET / C#Rust
Stack guides
Vue + PHP
API Reference
Browser SDK
Script bundlesAutocaptureWeb VitalsSession recordingChat widgetOutbound messagesFeature readinessRemote configurationDebug logging
Node SDK
Install & setupCapture, identify & aliasContext & shutdownGlobal properties & opt-outError tracking
MCP server
Guides
OverviewAuthenticationOAuthAgent skills (prompts)AI intelligenceSite knowledgeGoogle AdsMeta Ads
Client setup
CursorClaude DesktopVS CodeCodex

Getting Started

OverviewInstallInitializeIdentify usersTrack eventsLogout & resetVerify eventsCommon mistakes

Guides

Event forwardingReverse proxyRealtime dashboardSite knowledgeStudio

API Reference

MCP server

On this page

On this page

  • Why use a reverse proxy?
  • SDK endpoints
  • SDK configuration
  • Choosing your own path for the bundles
  • Next.js rewrites
  • Nginx
  • Cloudflare Workers
  • Script tag with proxy
DocsGuidesReverse proxy

Reverse proxy

Last updated August 17, 2026

Route SDK requests through your own domain to avoid ad blockers and keep data collection first-party.

Route SDK requests through your own domain to avoid ad blockers and keep data collection first-party. When a reverse proxy is configured, all requests appear to originate from your domain instead of a third-party analytics host.

#Why use a reverse proxy?

Ad blockers commonly block requests to known analytics domains and paths. A reverse proxy on your own domain makes SDK traffic indistinguishable from your regular API calls.

  • Ad blocker avoidance — requests go through your domain, not a third-party analytics host.
  • First-party cookies — cookies are set on your domain, improving identification accuracy.
  • Privacy compliance — data stays within your infrastructure boundary.

Note

Modern versions of the SDK authenticate with the x-api-key request header rather than a ?token= query parameter, so the URL stays clean and is less likely to match the standard filter rules. The server still accepts the legacy ?token= query parameter for backward compatibility, so older SDK builds keep working unchanged. The only path that still uses ?token= is navigator.sendBeacon on page unload, because beacons cannot carry custom headers.

#SDK endpoints

The SDK uses these short paths to minimise payload size and avoid ad-blocker pattern matching. Your reverse proxy must forward all of them — including the x-api-key request header — to your vTilt instance.

PathPurposeMethod
/api/eEvent ingestion (pageviews, custom events, identify).POST
/api/dRemote configuration.GET
/api/sSession recording snapshots.POST
/gt/*Google Tag gateway (gtag.js, collect, conversion) when destinations use proxied mode.GET / POST
/api/chat/*Chat widget (settings, messages, channels).GET / POST
/api/o/*Outbound messages (which banner to show, plus impression and click events).GET / POST
/dist/*The SDK bundles themselves, including the lazy feature files.GET

Note

Outbound moved from /api/outbound/widget/* to /api/o/*. The long path still serves the same handlers, so nothing breaks — but a browser holding a cached copy of the previous array.js keeps requesting it until that bundle refreshes. Forward both paths for a day if you are switching an already-live proxy.

Important

/dist/* is easy to miss. Once api_host points at your proxy, the SDK loads its optional features — chat.js, recorder.js, web-vitals.js, outbound.js — from {api_host}/dist/, so leaving that path unproxied disables every one of them while basic event tracking keeps working. If you would rather serve the bundles from vTilt directly and proxy only the data paths, set script_host to https://www.vtilt.com alongside api_host — or keep them first-party on a path of your own choosing, see below.

#SDK configuration

Set api_host to your proxy domain so all SDK traffic routes through it.

vt.init('YOUR_PROJECT_TOKEN', {
  api_host: 'https://your-domain.com',
})
typescript

#Choosing your own path for the bundles

script_host does not have to be a bare origin — it can include a path prefix, and the SDK appends the filename to it directly with no /dist/ segment. That lets you serve the bundles from any path on your domain and drop /dist/* from the table above:

vt.init('YOUR_PROJECT_TOKEN', {
  api_host: 'https://your-domain.com',
  script_host: 'https://your-domain.com/x',
})
typescript

Pair it with a single rewrite:

{ source: '/x/:file*', destination: 'https://www.vtilt.com/dist/:file*' }
javascript

The SDK then requests /x/array.js, /x/chat.js, /x/outbound.js, and so on. Point the src in your install snippet at the same prefix (https://your-domain.com/x/array.js), since the snippet loads the core bundle before any config is read.

Tip

A short, project-specific prefix like /x is also the most blocker-resistant option available to you — filter lists cannot pattern-match a path they have never seen.

#Next.js rewrites

Add these rewrites to your next.config.js to proxy vTilt requests through your Next.js app.

/** @type {import('next').NextConfig} */
const nextConfig = {
  async rewrites() {
    return [
      { source: '/api/e', destination: 'https://www.vtilt.com/api/e' },
      { source: '/api/d', destination: 'https://www.vtilt.com/api/d' },
      { source: '/api/s', destination: 'https://www.vtilt.com/api/s' },
      { source: '/gt/:path*', destination: 'https://www.vtilt.com/gt/:path*' },
      { source: '/api/chat/:path*', destination: 'https://www.vtilt.com/api/chat/:path*' },
      { source: '/api/o/:path*', destination: 'https://www.vtilt.com/api/o/:path*' },
      { source: '/dist/:path*', destination: 'https://www.vtilt.com/dist/:path*' },
    ]
  },
}

module.exports = nextConfig
javascript

#Nginx

Add location blocks to forward each path to your vTilt instance.

location /api/e {
    proxy_pass https://www.vtilt.com/api/e;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

location /api/d {
    proxy_pass https://www.vtilt.com/api/d;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

location /api/s {
    proxy_pass https://www.vtilt.com/api/s;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

location /gt/ {
    proxy_pass https://www.vtilt.com/gt/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

location /api/o/ {
    proxy_pass https://www.vtilt.com/api/o/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

location /dist/ {
    proxy_pass https://www.vtilt.com/dist/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

location /api/chat/ {
    proxy_pass https://www.vtilt.com/api/chat/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}
nginx

#Cloudflare Workers

Deploy a Cloudflare Worker on your domain to proxy SDK requests.

const VTILT_HOST = 'https://www.vtilt.com'

export default {
  async fetch(request) {
    const url = new URL(request.url)

    if (
      url.pathname.startsWith('/api/e') ||
      url.pathname.startsWith('/api/d') ||
      url.pathname.startsWith('/api/s') ||
      url.pathname.startsWith('/gt/') ||
      url.pathname.startsWith('/api/chat/') ||
      url.pathname.startsWith('/api/o/') ||
      url.pathname.startsWith('/dist/')
    ) {
      const target = new URL(url.pathname + url.search, VTILT_HOST)
      const proxyReq = new Request(target, {
        method: request.method,
        headers: request.headers,
        body: request.body,
      })
      proxyReq.headers.set(
        'X-Forwarded-For',
        request.headers.get('CF-Connecting-IP') || '',
      )
      return fetch(proxyReq)
    }

    return fetch(request)
  },
}
javascript

#Script tag with proxy

When using the script tag snippet, set data-host to your proxy domain.

<script
  data-token="YOUR_PROJECT_TOKEN"
  data-host="https://your-domain.com"
  src="https://your-domain.com/dist/array.js"
></script>
html
PreviousEvent forwardingGuidesNextRealtime dashboardGuides