Skip to main content

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:
data-proxy
string
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
data-proxy-url
string
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

  1. Create a new Vercel project or add to existing:
# In your project root
mkdir -p api
cp middleware/api/tracking.js api/tracking.js
  1. 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
  1. Deploy:
vercel deploy
  1. 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>

Alternative Platforms

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:
  1. Add DNS CNAME record:
    analytics.myapp.com CNAME your-project.vercel.app
    
  2. Configure domain in Vercel:
    vercel domains add analytics.myapp.com
    
  3. 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 });
  }
};

Build docs developers (and LLMs) love