Skip to content

Proxy / Custom Domain

Ad blockers and privacy extensions block requests to known analytics domains like api.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/* ──► api.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://api.kitbase.dev. The x-sdk-key header is included automatically -- no changes needed on the auth side.

Proxy Setup Examples

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://api.kitbase.dev/:path*',
      },
    ];
  },
};

module.exports = nextConfig;

Then set baseUrl to /kb (relative URL):

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

Node.js (Express)

Use http-proxy-middleware to forward requests:

bash
npm install http-proxy-middleware
js
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');

const app = express();

app.use(
  '/kb',
  createProxyMiddleware({
    target: 'https://api.kitbase.dev',
    changeOrigin: true,
    pathRewrite: { '^/kb': '' },
  })
);

app.listen(3000);

Nginx

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

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://api.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;

Cloudflare Workers

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

    // Rewrite /kb/* → api.kitbase.dev/*
    url.hostname = 'api.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,
    });
  },
};

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) api.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.

Released under the MIT License.