Skip to Content
SourcesTracking SDKSelf-Hosting

Self-Hosting

Self-hosting is optional. Most users can use the CDN snippet to load the SDK from cdn.vendodata.com — no deployment needed for the SDK itself. You still need to deploy the ingestion API to receive events, but the SDK can be served from the CDN.

The ingestion API is designed to run on Google Cloud Run, but it works anywhere you can run a Node.js service (container, VM, serverless).

Prerequisites

  • Google Cloud project with BigQuery enabled
  • Service account with BigQuery Data Editor role
  • Docker (for building the container image)
  • gcloud CLI configured

Step 1: Provision BigQuery

Create the dataset and tables before starting the API. You have three options:

  1. Onboarding CLI (recommended) — Run the one-time setup CLI from the web-tracking-onboarding repo
  2. Manual — Create tables from the JSON schemas in packages/schemas/bigquery
  3. Admin API (demo only) — Enable admin endpoints and call POST /admin/setup with a service account key

Step 2: Build the Container

cd packages/ingest-api docker build -t vendo-event-streaming -f Dockerfile ../..

The Dockerfile builds both the SDK and the API, so the container can serve the SDK at /v1/sdk.js.

Step 3: Push to Container Registry

export PROJECT_ID=your-gcp-project export REGION=us-central1 export IMAGE=gcr.io/$PROJECT_ID/vendo-event-streaming:latest docker tag vendo-event-streaming $IMAGE docker push $IMAGE

Step 4: Deploy to Cloud Run

gcloud run deploy vendo-event-streaming \ --image=$IMAGE \ --region=$REGION \ --platform=managed \ --allow-unauthenticated \ --concurrency=3 \ --memory=4Gi \ --max-instances=3 \ --timeout=3600s \ --port=8080 \ --set-env-vars="WRITE_KEY=your-secret-write-key" \ --set-env-vars="BQ_PROJECT=$PROJECT_ID" \ --set-env-vars="BQ_DATASET=web_tracking"

Step 5: Set Up a First-Party Domain

To avoid ad blockers, serve the API from a subdomain of your site:

  1. Create a CNAME record: track.yourdomain.com → your Cloud Run URL
  2. Configure the custom domain in Cloud Run or use a load balancer
  3. Update the SDK snippet to use your domain:
vendo('init', 'YOUR_WRITE_KEY', { host: 'https://track.yourdomain.com', trackPageViews: true });

When using a first-party domain, event data is sent to your domain as a first-party request. If you also serve the SDK from your domain (via /v1/sdk.js), both the script and the data stay on your domain.


Environment Variables

Required (Static Auth Mode)

VariableDescription
WRITE_KEYSecret write key that the SDK must include in every request
BQ_PROJECTGoogle Cloud project ID
BQ_DATASETBigQuery dataset name (e.g., web_tracking)

Required (Firebase Auth Mode)

VariableDescription
FIREBASE_AUTH_ENABLEDSet to true to enable per-merchant authentication
FIREBASE_SERVICE_ACCOUNT_KEYBase64-encoded Firebase service account JSON (omit on Cloud Run for ADC)

Optional

VariableDefaultDescription
PORT8080HTTP port
BQ_TABLE_EVENTSeventsEvents table name
BQ_TABLE_USERSusersUsers table name
BQ_TABLE_GROUPSgroupsGroups table name
BQ_TABLE_ALIASESaliasesAliases table name
BQ_DISABLEDfalseSet to true to skip BigQuery inserts (local dev)
SDK_JS_PATHAuto-detectedOverride the path to the compiled SDK file

Demo/Admin (Disable in Production)

VariableDescription
DEMO_MODESet to true to enable demo features
ADMIN_SETUP_ENABLEDSet to true to enable admin endpoints
ADMIN_SETUP_TOKENShared secret for admin endpoint auth

Local Development

Run the API locally without BigQuery:

npm install npm run build:sdk npm run build:api WRITE_KEY=dev \ BQ_PROJECT=test \ BQ_DATASET=test \ BQ_DISABLED=true \ node packages/ingest-api/dist/index.js

The API starts on http://localhost:8080. The SDK is served at http://localhost:8080/v1/sdk.js.

Demo UI

A built-in demo UI is available for testing:

npm run dev

This starts a demo server on http://localhost:3001 with a visual interface to send events and inspect responses.


Architecture

Client Browser Your Domain (track.yourdomain.com) ├── GET /v1/sdk.js → Serves compiled SDK ├── POST /collect → Receives events │ │ │ ├── Validate (JSON schema) │ ├── Normalize (extract fields, enrich) │ └── Insert into BigQuery │ │ │ ├── events table (track + page) │ ├── users table (identify) │ ├── groups table (group) │ └── aliases table (alias) └── GET /health → Health check

Multi-Tenant Mode

When FIREBASE_AUTH_ENABLED=true, the API supports multiple merchants:

  1. Each merchant gets a unique write key stored in Firestore (tracking_write_keys collection)
  2. The API looks up the write key on each request to find the merchant’s config
  3. Events are routed to the merchant’s configured destinations (Mixpanel, Segment, Customer.io, OneSignal, BigQuery)

See the API Reference for details.


Monitoring

Health Check

Configure your load balancer to poll GET /health. Returns { "ok": true }.

Event Stats

GET /admin/events/stats returns insert counts since server start:

{ "ok": true, "stats": { "events": { "total": 1542, "lastInsertAt": "2026-02-08T12:00:00Z" }, "users": { "total": 23, "lastInsertAt": "2026-02-08T11:55:00Z" }, "groups": { "total": 0, "lastInsertAt": null }, "aliases": { "total": 2, "lastInsertAt": "2026-02-08T10:30:00Z" } } }

Logging

The API logs to stdout. In production, Cloud Run captures these automatically. Key log messages:

  • Ingest API listening on :8080 — Startup
  • SDK loaded from /path/to/sdk.js — SDK file found
  • collect error — Request processing failure (includes error details)

Verify Deployment

  1. Health check: curl https://track.yourdomain.com/health — should return {"ok":true}
  2. SDK serving: Open https://track.yourdomain.com/v1/sdk.js in a browser — should return JavaScript
  3. Event collection: Send a test event:
curl -X POST https://track.yourdomain.com/collect \ -H "Content-Type: application/json" \ -d '{ "writeKey": "your-write-key", "type": "track", "messageId": "test-123", "timestamp": "2026-02-08T12:00:00Z", "anonymousId": "test-anon", "event": "Test Event", "properties": { "source": "curl" } }'
  1. BigQuery: Confirm rows appear in the events table

Last updated on