Skip to main content

Pixel Pipeline

The pixel pipeline is the core data collection system. It handles all client-side events from merchant storefronts.

Overview

"Pixels" are small JavaScript events sent from the storefront to Cloudflare Workers. Each pixel type represents a specific signal or action.

Storefront JS → POST /api/pixels → pixel-router.js → [specific handler] → D1

Pixel Types

TypeDescriptionHandler
session_initNew visitor/sessionsession-init-handler.js
basic_securityContent protection triggeredbasic-security-handler.js
bot_detectionBot signal detectedbot-detection-handler.js
spy_detectionSpy tool foundspy-detection-handler.js
ip_blockingIP/country blockedip-blocking-pixel-handler.js
behavior_analyticsMouse/scroll patternsbehavior-analytics-handler.js
checkout_sessionCheckout page reachedcheckout-session-handler.js
test_pixelValidation/testingtest-pixel-handler.js

Pixel Router

All pixels arrive at a single endpoint and are routed by type:

// pixel-router.js
export async function routePixel(request, env) {
const payload = await request.json();
const { type, shop, ...data } = payload;

switch (type) {
case 'session_init':
return handleSessionInit(shop, data, env);
case 'bot_detection':
return handleBotDetection(shop, data, env);
case 'basic_security':
return handleBasicSecurity(shop, data, env);
// ... other handlers
default:
return new Response('Unknown pixel type', { status: 400 });
}
}

Common Pixel Payload

Every pixel includes these base fields:

interface BasePixel {
type: string; // Pixel type identifier
shop: string; // Shopify shop domain
sessionId: string; // Unique session ID
visitorId: string; // Fingerprint-based visitor ID
timestamp: number; // Client timestamp
page: string; // Current page URL
userAgent: string; // Browser user agent
}

Session Init Handler

The most important pixel - creates visitor identity and session tracking.

// session-init-handler.js
export async function handleSessionInit(shop, data, env) {
const { visitorId, sessionId, fingerprint, deviceInfo } = data;

// 1. Upsert visitor identity
await env.DB.prepare(`
INSERT INTO VisitorIdentity (id, shop, fingerprint, first_seen, last_seen, visit_count)
VALUES (?, ?, ?, ?, ?, 1)
ON CONFLICT (id) DO UPDATE SET
last_seen = excluded.last_seen,
visit_count = visit_count + 1
`).bind(visitorId, shop, fingerprint, Date.now(), Date.now()).run();

// 2. Create session snapshot
await env.DB.prepare(`
INSERT INTO SessionSnapshot (id, shop, visitor_id, started_at, device_info)
VALUES (?, ?, ?, ?, ?)
`).bind(sessionId, shop, visitorId, Date.now(), JSON.stringify(deviceInfo)).run();

// 3. Track daily unique visitors (for billing)
await env.DB.prepare(`
INSERT OR IGNORE INTO DailyUniqueVisitors (shop, date, visitor_id)
VALUES (?, ?, ?)
`).bind(shop, getToday(), visitorId).run();

// 4. Update daily metrics
await incrementMetric(env.DB, shop, 'sessions');

return new Response('OK');
}

Bot Detection Handler

Processes bot-related signals:

// bot-detection-handler.js
export async function handleBotDetection(shop, data, env) {
const {
visitorId,
sessionId,
signalType, // 'selenium' | 'headless' | 'honeypot' | 'trap_link'
confidence,
details
} = data;

// 1. Store raw signal
await env.DB.prepare(`
INSERT INTO BotSignal (id, shop, visitor_id, session_id, signal_type, confidence, details, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`).bind(
generateId(), shop, visitorId, sessionId,
signalType, confidence, JSON.stringify(details), Date.now()
).run();

// 2. Update aggregates
await incrementMetric(env.DB, shop, 'bot_events');
await updateTopIPs(env.DB, shop, data.ip);
await updateTopPages(env.DB, shop, data.page);

return new Response('OK');
}

Aggregation Pattern

Every handler follows the same aggregation pattern:

async function incrementMetric(db, shop, metric) {
const today = new Date().toISOString().split('T')[0];

await db.prepare(`
INSERT INTO DailyMetrics (shop, date, ${metric})
VALUES (?, ?, 1)
ON CONFLICT (shop, date)
DO UPDATE SET ${metric} = ${metric} + 1
`).bind(shop, today).run();
}

async function updateTopIPs(db, shop, ip) {
const today = new Date().toISOString().split('T')[0];

await db.prepare(`
INSERT INTO TopIPsDaily (shop, date, ip, count)
VALUES (?, ?, ?, 1)
ON CONFLICT (shop, date, ip)
DO UPDATE SET count = count + 1
`).bind(shop, today, ip).run();
}

Response Handling

All pixel handlers return quickly to not block the client:

// Fast response pattern
export async function handler(request, env, ctx) {
const data = await request.json();

// Use waitUntil for non-blocking writes
ctx.waitUntil(processPixel(data, env));

// Return immediately
return new Response('OK', { status: 200 });
}

Error Handling

Pixels should never fail the client request:

export async function handlePixel(request, env, ctx) {
try {
const data = await request.json();
ctx.waitUntil(processPixel(data, env));
return new Response('OK');
} catch (error) {
// Log error but don't fail the request
console.error('Pixel error:', error);
return new Response('OK'); // Still return OK
}
}

Testing Pixels

The test_pixel type validates the entire pipeline:

// test-pixel-handler.js
export async function handleTestPixel(shop, data, env) {
// Write to multiple tables to verify connectivity
await env.DB.batch([
env.DB.prepare('INSERT INTO VisitorIdentity ...'),
env.DB.prepare('INSERT INTO SessionSnapshot ...'),
env.DB.prepare('INSERT INTO BotSignal ...'),
env.DB.prepare('INSERT INTO DailyMetrics ...'),
]);

return new Response(JSON.stringify({ success: true, timestamp: Date.now() }));
}

Monitoring

Key metrics to watch:

  • Pixel volume: Requests/second to /api/pixels
  • Error rate: Failed pixel writes
  • Latency: Time from request to response
  • D1 writes: Write operations per second