| [ Web Proxy ] |
| Viewing: https://docs.kitbase.dev/proxy | [Back] [Original] |
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?
Browser your-domain.com/kb/* ingest.kitbase.dev/*
Browser your-domain.com/lite.js kitbase.dev/lite.jsYour 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:
POST /sdk/v1/logs, POST /sdk/v1/logs/batch, POST /sdk/v1/identifylite.js file loaded via script tagPoint the SDK at your proxy by setting the baseUrl option.
import { init } from '@kitbase/analytics';
const kitbase = init({
sdkKey: 'YOUR_SDK_KEY',
baseUrl: 'https://your-domain.com/kb',
});<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.
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.
For any project on Vercel (on Next.js, prefer the next.config.js rewrite below). Add vercel.json at the project root:
{
"rewrites": [
{ "source": "/kb/:path*", "destination": "https://ingest.kitbase.dev/:path*" }
]
}Then set baseUrl: '/kb'.
Add a redirect to netlify.toml (or public/_redirects). Status 200 makes Netlify proxy the request instead of redirecting:
[[redirects]]
from = "/kb/*"
to = "https://ingest.kitbase.dev/:splat"
status = 200
force = trueThen set baseUrl: '/kb'. Proxied bytes count toward your Netlify bandwidth.
A Pages _redirects file can only rewrite to internal assets, so external proxying needs a Pages Function. Create functions/kb/[[path]].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 require zero extra dependencies. Add this to your next.config.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):
const kitbase = init({
sdkKey: 'YOUR_SDK_KEY',
baseUrl: '/kb',
});Nuxt 3 (Nitro) has built-in route-rule proxying no extra dependencies. In nuxt.config.ts:
export default defineNuxtConfig({
routeRules: {
'/kb/**': { proxy: 'https://ingest.kitbase.dev/**' },
},
});Then set baseUrl: '/kb'.
Add a catch-all server route at src/routes/kb/[...path]/+server.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 needs an SSR adapter (@astrojs/node, Vercel, Netlify, ). Add a server endpoint at src/pages/kb/[...path].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'.
Use http-proxy-middleware to forward requests:
pnpm add http-proxy-middlewareconst 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);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;
}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'.
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'.
A minimal curl-based proxy script:
<?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;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
// 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.
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,
});
},
};CloudFront needs three pieces of configuration:
ingest.kitbase.dev (HTTPS only)./kb/* that origin, with: POST)Host)GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE/kb prefix, since CloudFront forwards the full path: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'.
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:
kitbase.dev/lite.js + ingest.kitbase.dev). It works everywhere; the only cost is that visitors running aggressive blockers aren't counted.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.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
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;
}// next.config.js
{
source: '/lite.js',
destination: 'https://kitbase.dev/lite.js',
}Then load the script from your domain:
<script defer src="https://your-domain.com/lite.js"></script>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).
The backend parses User-Agent to extract device type, browser, and OS. Make sure your proxy forwards this header unchanged.
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).
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.
Released under the MIT License.
Copyright 2024-present Kitbase
| Web Proxy Viewer | New URL | Original Page |