[ Web Proxy ]
URL:
Viewing: https://docs.kitbase.dev/proxy [Back]  [Original]

Proxy / Custom Domain | Kitbase
Skip to content
Main Navigation HomeQuick StartWeb AnalyticsSDKsDashboardChangelog
Menu
Return to top
Sidebar Navigation

Getting Started

AI Visibility

Site Audit

Keyword Research

Workflows

On this page
    Copy page

    Proxy / Custom Domain

    Ad blockers and privacy extensions block requests to known analytics domains like ingest.kitbase.dev. You can avoid this by proxying analytics requests through your own domain so they appear as first-party traffic.

    Why proxy?

    • Ad blockers -- requests to your own domain are not blocked
    • Content Security Policy -- no need to allowlist a third-party domain
    • Brand consistency -- all traffic stays on your domain
    • Privacy -- analytics data never visibly leaves your infrastructure

    How It Works

    Browser  your-domain.com/kb/*  ingest.kitbase.dev/*
    Browser  your-domain.com/lite.js  kitbase.dev/lite.js

    Your proxy sits between the browser and the Kitbase API. The SDK sends requests to your proxy URL, and the proxy forwards them to Kitbase. Two things need proxying:

    1. API endpoints -- POST /sdk/v1/logs, POST /sdk/v1/logs/batch, POST /sdk/v1/identify
    2. Tracking script (optional) -- the lite.js file loaded via script tag

    SDK Configuration

    Point the SDK at your proxy by setting the baseUrl option.

    NPM Package

    typescript
    import { init } from '@kitbase/analytics';
    
    const kitbase = init({
      sdkKey: 'YOUR_SDK_KEY',
      baseUrl: 'https://your-domain.com/kb',
    });

    Script Tag

    html
    <script>
      window.KITBASE_CONFIG = {
        sdkKey: 'YOUR_SDK_KEY',
        baseUrl: 'https://your-domain.com/kb',
      };
    </script>
    <script defer src="https://your-domain.com/lite.js"></script>

    The SDK sends all API requests to baseUrl instead of https://ingest.kitbase.dev. The x-sdk-key header is included automatically -- no changes needed on the auth side.

    Proxy Setup Examples

    Every example below does the same two things: forward your-domain.com/kb/* to ingest.kitbase.dev/*, then point the SDK's baseUrl at /kb. Pick the one that matches your stack hosting platforms first, then JavaScript frameworks, web servers, PHP, and hosted CMSs. Whichever you choose, follow the header and caching rules in Important Notes.

    Vercel

    For any project on Vercel (on Next.js, prefer the next.config.js rewrite below). Add vercel.json at the project root:

    json
    {
      "rewrites": [
        { "source": "/kb/:path*", "destination": "https://ingest.kitbase.dev/:path*" }
      ]
    }

    Then set baseUrl: '/kb'.

    Netlify

    Add a redirect to netlify.toml (or public/_redirects). Status 200 makes Netlify proxy the request instead of redirecting:

    toml
    [[redirects]]
      from = "/kb/*"
      to = "https://ingest.kitbase.dev/:splat"
      status = 200
      force = true

    Then set baseUrl: '/kb'. Proxied bytes count toward your Netlify bandwidth.

    Cloudflare Pages

    A Pages _redirects file can only rewrite to internal assets, so external proxying needs a Pages Function. Create functions/kb/[[path]].js:

    js
    // functions/kb/[[path]].js
    export async function onRequest({ request, params }) {
      const path = (params.path || []).join('/');
      const { search } = new URL(request.url);
    
      const headers = new Headers(request.headers);
      headers.set('X-Forwarded-For', request.headers.get('CF-Connecting-IP') || '');
    
      return fetch(`https://ingest.kitbase.dev/${path}${search}`, {
        method: request.method,
        headers,
        body: request.body,
      });
    }

    Then set baseUrl: '/kb'.

    Next.js (Rewrites)

    Next.js rewrites require zero extra dependencies. Add this to your next.config.js:

    js
    /** @type {import('next').NextConfig} */
    const nextConfig = {
      async rewrites() {
        return [
          {
            source: '/kb/:path*',
            destination: 'https://ingest.kitbase.dev/:path*',
          },
        ];
      },
    };
    
    module.exports = nextConfig;

    Then set baseUrl to /kb (relative URL):

    typescript
    const kitbase = init({
      sdkKey: 'YOUR_SDK_KEY',
      baseUrl: '/kb',
    });

    Nuxt

    Nuxt 3 (Nitro) has built-in route-rule proxying no extra dependencies. In nuxt.config.ts:

    ts
    export default defineNuxtConfig({
      routeRules: {
        '/kb/**': { proxy: 'https://ingest.kitbase.dev/**' },
      },
    });

    Then set baseUrl: '/kb'.

    SvelteKit

    Add a catch-all server route at src/routes/kb/[...path]/+server.ts:

    ts
    import type { RequestHandler } from './$types';
    
    const UPSTREAM = 'https://ingest.kitbase.dev';
    
    const proxy: RequestHandler = async ({ request, params, url, getClientAddress }) => {
      const headers = new Headers(request.headers);
      headers.delete('host');           // let fetch set the upstream Host
      headers.delete('content-length'); // recomputed from the forwarded body
      headers.set('X-Forwarded-For', getClientAddress());
    
      const method = request.method;
      return fetch(`${UPSTREAM}/${params.path}${url.search}`, {
        method,
        headers,
        body: method === 'GET' || method === 'HEAD' ? undefined : await request.text(),
      });
    };
    
    export const GET = proxy;
    export const POST = proxy;

    Then set baseUrl: '/kb'.

    Astro

    Astro needs an SSR adapter (@astrojs/node, Vercel, Netlify, ). Add a server endpoint at src/pages/kb/[...path].ts:

    ts
    import type { APIRoute } from 'astro';
    
    export const prerender = false;
    
    const UPSTREAM = 'https://ingest.kitbase.dev';
    
    export const ALL: APIRoute = async ({ request, params, url }) => {
      const headers = new Headers(request.headers);
      headers.delete('host');
      headers.delete('content-length');
    
      const method = request.method;
      return fetch(`${UPSTREAM}/${params.path}${url.search}`, {
        method,
        headers,
        body: method === 'GET' || method === 'HEAD' ? undefined : await request.text(),
      });
    };

    Then set baseUrl: '/kb'.

    Node.js (Express)

    Use http-proxy-middleware to forward requests:

    bash
    pnpm add http-proxy-middleware
    js
    const express = require('express');
    const { createProxyMiddleware } = require('http-proxy-middleware');
    
    const app = express();
    
    app.use(
      '/kb',
      createProxyMiddleware({
        target: 'https://ingest.kitbase.dev',
        changeOrigin: true,
        pathRewrite: { '^/kb': '' },
      })
    );
    
    app.listen(3000);

    Nginx

    nginx
    location /kb/ {
        proxy_pass https://ingest.kitbase.dev/;
        proxy_set_header Host ingest.kitbase.dev;
        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;
        proxy_ssl_server_name on;
    }

    Apache

    With access to the server config or a <VirtualHost> (requires mod_proxy + mod_proxy_http):

    SSLProxyEngine On
    ProxyPreserveHost Off
    
    ProxyPass        /kb/  https://ingest.kitbase.dev/
    ProxyPassReverse /kb/  https://ingest.kitbase.dev/

    ProxyPreserveHost Off sends ingest.kitbase.dev as the upstream Host, which its TLS termination requires.

    On shared hosting where you can only edit .htaccess, use the mod_rewrite proxy flag instead note SSLProxyEngine On must already be set by your host, since it can't live in .htaccess:

    RewriteEngine On
    RewriteRule ^kb/(.*)$ https://ingest.kitbase.dev/$1 [P,L]

    Then set baseUrl: '/kb'.

    Caddy

    In your Caddyfile:

    your-domain.com {
        handle /kb/* {
            uri strip_prefix /kb
            reverse_proxy https://ingest.kitbase.dev {
                header_up Host ingest.kitbase.dev
            }
        }
    
        # ... the rest of your site
    }

    Caddy adds X-Forwarded-For automatically. Then set baseUrl: '/kb'.

    PHP

    A minimal curl-based proxy script:

    php
    <?php
    // proxy.php  place at /kb/proxy.php and rewrite /kb/* to this file
    $path = ltrim($_SERVER['PATH_INFO'] ?? '', '/');
    $url  = 'https://ingest.kitbase.dev/' . $path;
    
    $headers = [];
    foreach (getallheaders() as $key => $value) {
        // Forward relevant headers
        $lower = strtolower($key);
        if (in_array($lower, ['content-type', 'x-sdk-key', 'user-agent'])) {
            $headers[] = "$key: $value";
        }
    }
    
    // Forward the client IP
    $headers[] = 'X-Forwarded-For: ' . $_SERVER['REMOTE_ADDR'];
    
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $_SERVER['REQUEST_METHOD']);
    
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        curl_setopt($ch, CURLOPT_POSTFIELDS, file_get_contents('php://input'));
    }
    
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
    curl_close($ch);
    
    http_response_code($httpCode);
    header('Content-Type: ' . ($contentType ?: 'application/json'));
    echo $response;

    WordPress

    If you control the web server, use the Nginx or Apache config above it's the most efficient option. On a managed host where you can only add code, drop a must-use plugin at wp-content/mu-plugins/kitbase-proxy.php that streams /kb/* to the API:

    php
    <?php
    // wp-content/mu-plugins/kitbase-proxy.php
    add_action('muplugins_loaded', function () {
        $prefix = '/kb/';
        $uri = $_SERVER['REQUEST_URI'] ?? '';
        if (strncmp($uri, $prefix, strlen($prefix)) !== 0) {
            return; // not an analytics request  let WordPress handle it
        }
    
        $path   = substr(parse_url($uri, PHP_URL_PATH), strlen($prefix));
        $query  = $_SERVER['QUERY_STRING'] ?? '';
        $target = 'https://ingest.kitbase.dev/' . $path . ($query ? "?$query" : '');
    
        $headers = ['X-Forwarded-For: ' . ($_SERVER['REMOTE_ADDR'] ?? '')];
        if (!empty($_SERVER['CONTENT_TYPE']))    $headers[] = 'Content-Type: ' . $_SERVER['CONTENT_TYPE'];
        if (!empty($_SERVER['HTTP_X_SDK_KEY']))  $headers[] = 'x-sdk-key: ' . $_SERVER['HTTP_X_SDK_KEY'];
        if (!empty($_SERVER['HTTP_USER_AGENT'])) $headers[] = 'User-Agent: ' . $_SERVER['HTTP_USER_AGENT'];
    
        $ch = curl_init($target);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_CUSTOMREQUEST  => $_SERVER['REQUEST_METHOD'],
            CURLOPT_HTTPHEADER     => $headers,
            CURLOPT_POSTFIELDS     => file_get_contents('php://input'),
        ]);
        $body = curl_exec($ch);
    
        http_response_code(curl_getinfo($ch, CURLINFO_HTTP_CODE));
        header('Content-Type: ' . (curl_getinfo($ch, CURLINFO_CONTENT_TYPE) ?: 'application/json'));
        curl_close($ch);
        echo $body;
        exit;
    });

    Then set baseUrl: '/kb'. Because WordPress starts booting on every analytics request, prefer the server-level proxy whenever you can.

    Cloudflare Workers

    js
    export default {
      async fetch(request) {
        const url = new URL(request.url);
    
        // Rewrite /kb/*  ingest.kitbase.dev/*
        url.hostname = 'ingest.kitbase.dev';
        url.pathname = url.pathname.replace(/^\/kb/, '');
    
        const headers = new Headers(request.headers);
        headers.set('X-Forwarded-For', request.headers.get('CF-Connecting-IP'));
    
        return fetch(url.toString(), {
          method: request.method,
          headers,
          body: request.body,
        });
      },
    };

    AWS CloudFront

    CloudFront needs three pieces of configuration:

    1. Add an origin pointing to ingest.kitbase.dev (HTTPS only).
    2. Add a cache behavior for path pattern /kb/* that origin, with:
      • Cache policy: CachingDisabled (analytics requests are all POST)
      • Origin request policy: AllViewerExceptHostHeader (forwards the visitor's headers and query string, sends the origin's own Host)
      • Allowed methods: GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE
    3. Attach a CloudFront Function (viewer request) to strip the /kb prefix, since CloudFront forwards the full path:
    js
    function handler(event) {
      var request = event.request;
      request.uri = request.uri.replace(/^\/kb/, '');
      return request;
    }

    CloudFront adds X-Forwarded-For automatically. Then set baseUrl: '/kb'.

    Shopify, Webflow, Wix & other hosted builders

    Fully hosted site builders don't give you a reverse proxy or server config, so you can't make analytics requests first-party the way the platforms above do. Two options:

    • Use the standard third-party snippet (kitbase.dev/lite.js + ingest.kitbase.dev). It works everywhere; the only cost is that visitors running aggressive blockers aren't counted.
    • Proxy through a subdomain you control. If your site's DNS is on Cloudflare, run a Worker on a subdomain of your own domain e.g. kb.your-domain.com ingest.kitbase.dev and set baseUrl to https://kb.your-domain.com. Because that subdomain is served from your own zone (not a CNAME pointed at kitbase.dev), it survives the CNAME-uncloaking that uBlock Origin and Firefox apply to bare-CNAME trackers, and it shares your site's registrable domain, so it reads as first-party. Serve the script from the same subdomain too.

    Self-Hosting the Script File

    To also proxy the tracking script (lite.js), serve it from your domain. This prevents the script tag itself from being blocked.

    Option A: Static file -- download https://kitbase.dev/lite.js and serve it as a static asset. Re-download periodically to pick up SDK updates.

    Option B: Rewrite rule -- proxy the request like the API:

    nginx
    # Nginx
    location = /lite.js {
        proxy_pass https://kitbase.dev/lite.js;
        proxy_set_header Host kitbase.dev;
        proxy_ssl_server_name on;
        proxy_cache_valid 200 1h;
    }
    js
    // next.config.js
    {
      source: '/lite.js',
      destination: 'https://kitbase.dev/lite.js',
    }

    Then load the script from your domain:

    html
    <script defer src="https://your-domain.com/lite.js"></script>

    Important Notes

    Forward Client IP Headers

    Kitbase uses client IP addresses for geolocation enrichment (country, region, city). Your proxy must forward the original client IP so geo data is accurate. The backend reads these headers in order:

    • CF-Connecting-IP (Cloudflare)
    • X-Forwarded-For (standard proxy header)
    • X-Real-IP (Nginx)
    • True-Client-IP (Akamai)

    Most reverse proxies add X-Forwarded-For automatically. If yours doesn't, add it explicitly (see the examples above).

    Keep the User-Agent Header

    The backend parses User-Agent to extract device type, browser, and OS. Make sure your proxy forwards this header unchanged.

    Don't Cache POST Responses

    The SDK sends analytics data via POST requests. Caching POST responses will cause events to be silently dropped. Only cache GET requests (like lite.js).

    Update Your Content Security Policy

    If you use CSP headers, update connect-src to allow your proxy path instead of (or in addition to) ingest.kitbase.dev:

    script-src 'self';
    connect-src 'self';

    Since both the script and API calls come from your own domain, no third-party domains need to be allowlisted.

    Next steps

    Pager

    Released under the MIT License.


    Web Proxy Viewer  |  New URL  |  Original Page