Overview
Setting up a proxy for your analytics tracking provides several benefits:
- Bypass ad blockers: Many ad blockers block requests to analytics domains like
api.tinybird.co
- First-party tracking: Route traffic through your own domain for better cookie handling
- Privacy compliance: Keep data within your infrastructure before sending to Tinybird
- Custom middleware: Add authentication, validation, or data enrichment
Proxy Parameters
The tracking script supports two proxy configuration options:
Your domain URL. The script will automatically append /api/tracking to construct the proxy endpoint.Example: data-proxy="https://analytics.myapp.com" will send events to https://analytics.myapp.com/api/tracking
Complete proxy URL endpoint. Use this when you need to specify a custom tracking endpoint beyond just the domain.Example: data-proxy-url="https://myapp.com/track/events"
Important: You cannot use both data-proxy and data-proxy-url together. Choose one based on your needs.
The tracking script enforces this:
if (proxy && proxyUrl) {
console.error('Error: Both data-proxy and data-proxy-url are specified. Please use only one of them.');
throw new Error('Both data-proxy and data-proxy-url are specified. Please use only one of them.');
}
URL Construction
The tracking script constructs the tracking URL based on your configuration:
let url;
if (proxyUrl) {
// Use the full proxy URL as provided
url = proxyUrl;
} else if (proxy) {
// Construct the proxy URL from the proxy domain
url = `${proxy}/api/tracking`;
} else if (host) {
// Use custom Tinybird host
url = `${host}/v0/events?name=${DATASOURCE}&token=${token}`;
} else {
// Default to Tinybird API
url = `https://api.tinybird.co/v0/events?name=${DATASOURCE}&token=${token}`;
}
Implementation Options
Option 1: Using data-proxy
Simplest setup - specify your domain and the script handles the rest:
<script
defer
src="https://unpkg.com/@tinybirdco/flock.js"
data-token="YOUR_TRACKER_TOKEN"
data-proxy="https://analytics.myapp.com"
></script>
Events will be sent to: https://analytics.myapp.com/api/tracking
Option 2: Using data-proxy-url
For custom endpoint paths:
<script
defer
src="https://unpkg.com/@tinybirdco/flock.js"
data-token="YOUR_TRACKER_TOKEN"
data-proxy-url="https://myapp.com/track/analytics"
></script>
Events will be sent to: https://myapp.com/track/analytics
Proxy Server Implementation
The starter kit includes a reference implementation using Vercel Edge Functions:
Vercel Edge Function
import fetch from 'node-fetch';
export const config = {
runtime: 'experimental-edge',
};
const DATASOURCE = 'analytics_events';
/**
* Post event to Tinybird HFI
*/
const _postEvent = async event => {
const options = {
method: 'post',
body: event,
headers: {
'Authorization': `Bearer ${process.env.TINYBIRD_TOKEN}`,
'Content-Type': 'application/json'
}
};
const response = await fetch(
`https://api.tinybird.co/v0/events?name=${DATASOURCE}`,
options
);
if (!response.ok) {
throw response.statusText;
}
return response.json();
};
export default async (req) => {
await _postEvent(req.body);
return new Response('ok', {
headers: {
'access-control-allow-credentials': true,
'access-control-allow-origin': process.env.CORS_ALLOW_ORIGIN || '*',
'access-control-allow-methods': 'OPTIONS,POST',
'access-control-allow-headers': 'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version',
'content-type': 'text/html'
},
});
};
Deploy to Vercel
- Create a new Vercel project or add to existing:
# In your project root
mkdir -p api
cp middleware/api/tracking.js api/tracking.js
- Set environment variables in Vercel:
vercel env add TINYBIRD_TOKEN
# Enter your Tinybird tracker token when prompted
vercel env add CORS_ALLOW_ORIGIN
# Enter your domain, e.g., https://myapp.com
- Deploy:
- Configure your script to use the proxy:
<script
defer
src="https://unpkg.com/@tinybirdco/flock.js"
data-token="YOUR_TRACKER_TOKEN"
data-proxy="https://your-project.vercel.app"
></script>
Cloudflare Workers
export default {
async fetch(request, env) {
// Only handle POST requests
if (request.method !== 'POST') {
return new Response('Method not allowed', { status: 405 });
}
const body = await request.text();
// Forward to Tinybird
const response = await fetch(
'https://api.tinybird.co/v0/events?name=analytics_events',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${env.TINYBIRD_TOKEN}`,
'Content-Type': 'application/json',
},
body: body,
}
);
return new Response('ok', {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Content-Type': 'application/json',
},
});
},
};
Deploy with:
wrangler secret put TINYBIRD_TOKEN
wrangler deploy
Next.js API Route
// app/api/tracking/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
const body = await request.text();
const response = await fetch(
'https://api.tinybird.co/v0/events?name=analytics_events',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.TINYBIRD_TOKEN}`,
'Content-Type': 'application/json',
},
body: body,
}
);
if (!response.ok) {
return NextResponse.json(
{ error: 'Failed to track event' },
{ status: 500 }
);
}
return NextResponse.json({ success: true });
}
export async function OPTIONS(request: NextRequest) {
return new NextResponse(null, {
status: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
},
});
}
Configure the tracker:
<script
defer
src="https://unpkg.com/@tinybirdco/flock.js"
data-token="YOUR_TRACKER_TOKEN"
data-proxy-url="https://myapp.com/api/tracking"
></script>
Express.js Middleware
const express = require('express');
const fetch = require('node-fetch');
const app = express();
app.use(express.json());
app.post('/api/tracking', async (req, res) => {
try {
const response = await fetch(
'https://api.tinybird.co/v0/events?name=analytics_events',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.TINYBIRD_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(req.body),
}
);
if (!response.ok) {
throw new Error('Failed to forward event');
}
res.json({ success: true });
} catch (error) {
console.error('Tracking error:', error);
res.status(500).json({ error: 'Failed to track event' });
}
});
app.listen(3000);
Security Considerations
Do: Store your Tinybird token as an environment variable, never in client code
Do: Implement rate limiting to prevent abuse
Do: Validate request origin to prevent unauthorized usage
Do: Set appropriate CORS headers for your domain
Don’t: Expose your admin token - use a dedicated tracker token with APPEND-only permissions
Enhanced Security Example
export default async (req) => {
// Verify origin
const origin = req.headers.get('origin');
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || [];
if (!allowedOrigins.includes(origin)) {
return new Response('Forbidden', { status: 403 });
}
// Rate limiting (example with Vercel KV)
const ip = req.headers.get('x-forwarded-for');
const rateLimitKey = `rate-limit:${ip}`;
const requests = await kv.incr(rateLimitKey);
if (requests === 1) {
await kv.expire(rateLimitKey, 60); // 60 second window
}
if (requests > 100) {
return new Response('Too many requests', { status: 429 });
}
// Validate payload size
const body = await req.text();
if (body.length > 10240) { // 10KB limit
return new Response('Payload too large', { status: 413 });
}
// Forward to Tinybird
await _postEvent(body);
return new Response('ok', {
headers: {
'Access-Control-Allow-Origin': origin,
'Access-Control-Allow-Credentials': 'true',
},
});
};
Testing Your Proxy
Verify your proxy is working correctly:
curl -X POST https://your-proxy.com/api/tracking \
-H "Content-Type: application/json" \
-d '{
"timestamp": "2024-01-15T10:30:00Z",
"action": "page_hit",
"version": "1",
"session_id": "test-session",
"tenant_id": "",
"domain": "",
"payload": "{\"pathname\":\"/test\",\"href\":\"https://test.com/test\"}"
}'
Check your Tinybird workspace to confirm the event was received.
Custom Domain Setup
For a complete first-party solution, set up a custom subdomain:
-
Add DNS CNAME record:
analytics.myapp.com CNAME your-project.vercel.app
-
Configure domain in Vercel:
vercel domains add analytics.myapp.com
-
Update tracking script:
<script
defer
src="https://unpkg.com/@tinybirdco/flock.js"
data-token="YOUR_TRACKER_TOKEN"
data-proxy="https://analytics.myapp.com"
></script>
Now all analytics traffic goes through analytics.myapp.com, appearing as first-party requests.
Monitoring & Debugging
Add logging to your proxy for debugging:
export default async (req) => {
console.log('Received tracking request:', {
origin: req.headers.get('origin'),
userAgent: req.headers.get('user-agent'),
timestamp: new Date().toISOString(),
});
try {
const result = await _postEvent(req.body);
console.log('Successfully forwarded to Tinybird');
return new Response('ok', { status: 200 });
} catch (error) {
console.error('Failed to forward event:', error);
return new Response('error', { status: 500 });
}
};