Skip to content

Track Custom Events

Kitbase lets you track custom events with rich metadata to understand what your users are doing, monitor business metrics, and trigger real-time notifications. Events are sent asynchronously to the Kitbase API and processed in the background.

All events are sent via POST /sdk/v1/logs with API key authentication (X-API-Key header) and return 202 Accepted for async processing.

Basic Usage

typescript
import { init } from '@kitbase/analytics';

const kitbase = init({ sdkKey: 'YOUR_SDK_KEY' });

await kitbase.track({
  channel: 'payments',
  event: 'Purchase Completed',
  tags: {
    amount: 99.99,
    currency: 'USD',
    plan: 'premium'
  }
});

Track Options

The track method accepts a TrackOptions object with the following fields:

typescript
interface TrackOptions {
  channel: string;              // Required: event category (e.g., 'payments', 'users', 'errors')
  event: string;                // Required: event name (e.g., 'Purchase Completed')
  user_id?: string;             // Optional: explicit user identifier
  icon?: string;                // Optional: emoji icon for dashboard
  notify?: boolean;             // Optional: trigger real-time notification
  description?: string;         // Optional: human-readable event description
  tags?: Record<string, unknown>; // Optional: metadata (string, number, boolean values)
}
ParameterTypeRequiredDescription
channelstringYesCategory for the event (e.g., "payments", "users")
eventstringYesName of the event (e.g., "Purchase Completed")
user_idstringNoExplicit user identifier
iconstringNoEmoji icon displayed in the dashboard
notifybooleanNoTrigger a real-time notification
descriptionstringNoHuman-readable event description
tagsobjectNoKey-value metadata (string, number, boolean)

Response

A successful call returns a TrackResponse with the event's unique identifier and timestamp:

typescript
interface TrackResponse {
  id: string;        // Unique event ID (UUID)
  event: string;     // Event name
  timestamp: string; // ISO 8601 timestamp
}

Channels

Channels organize your events into logical categories. Use consistent channel names across your application to keep your dashboard clean and filterable.

ChannelUse Case
paymentsPurchases, refunds, subscription changes
usersSignups, logins, profile updates
errorsApplication errors, failed requests
notificationsAlerts, messages, delivery status
apiAPI calls, webhooks, integrations
systemInfrastructure events, deployments

You are not limited to these defaults. Custom channels are fully supported -- use whatever names make sense for your domain.

Channel Naming

Use lowercase, plural nouns for channel names (e.g., payments, errors, users). This keeps your events organized and easy to filter in the dashboard.

Tags

Tags are key-value metadata attached to events. They allow you to store additional context that you can filter and analyze later.

Supported Types

TypeExample
string"premium"
number99.99
booleantrue

Example

typescript
tags: {
  plan_name: 'premium',      // string
  amount: 99.99,             // number
  is_upgrade: true,          // boolean
}

Tag Naming

Use snake_case for tag names:

  • plan_name, user_type, error_code
  • Not: PlanName, planName, ERROR-CODE

Server-Side Tag Enrichment

The Kitbase backend automatically enriches every event with additional context derived from the request. These enriched fields are prefixed with __ and stored as dedicated columns, available as first-class filter dimensions in the dashboard.

Browser and Device

TagDescription
__browserBrowser name (Chrome, Firefox, Safari)
__browser_versionBrowser version
__osOperating system (Windows, macOS, iOS)
__os_versionOS version
__deviceDevice type — see the taxonomy below
__brandDevice brand (Apple, Samsung, Google)
__modelDevice model (iPhone 15, Pixel 8)
__device_confidenceDetection confidence, 0.01.0 (see bands below)
__signal_sourceWhich request signal produced the device type
__in_app1 when the request came from an in-app WebView/Electron context

Device taxonomy (__device): desktop, mobile, tablet, smart_tv, set_top_box, game_console, handheld_game_console, wearable, automotive, xr, e_reader, voice, home_appliance, bot, unknown. Device type is detected entirely server-side (no client-provided hints are trusted) and an unidentifiable client stays unknown — it is never silently coerced to desktop.

Detection precedence & confidence. Detection runs a bot gate first, then prefers the strongest available signal:

Signal (__signal_source)Source__device_confidence
BOT_RULEknown bot ruleset / robot UA0.99
FORM_FACTORSSec-CH-UA-Form-Factors client hint0.95
LOW_ENTROPY_HINTSSec-CH-UA-Mobile + platform hints0.85
UA_FUSED / UA_ONLYUser-Agent parse (with / without hints)0.75
UA_FUSED / UA_ONLY (Unknown)parsed but class unresolved0.60
NONEno usable signal0.30

To recover accurate detection on Chromium (whose User-Agent is frozen by User-Agent Reduction), the backend advertises UA Client Hints via Accept-CH, with the device-critical subset (Sec-CH-UA-Form-Factors, Sec-CH-UA-Mobile, Sec-CH-UA-Platform) also sent as Critical-CH so even first-hit requests can be classified from hints. Filter low-confidence rows by __signal_source/__device_confidence in the dashboard.

Known limitation: iPadOS Safari presents a macOS desktop User-Agent and sends no distinguishing hints, so some iPads are counted as desktop (a documented undercount). We do not fingerprint to work around this.

Bot & Crawler Attribution

Every request is classified by a server-side attribution pipeline that not only detects bots but tries to verify their claimed identity — because a bot User-Agent is trivially spoofable.

TagDescription
__actor_typehuman, verified_bot, spoofed_bot, suspected_bot, or unknown
__is_bot1 for any kind of bot (the flag used to exclude bots from human/billing counts)
__bot_nameAttributed bot name (e.g. GPTBot, Googlebot)
__bot_vendorOperating vendor (e.g. OpenAI, Google)
__actor_categoryai_training, ai_search, ai_user_fetch, search_engine, social_preview, seo, monitoring, security_scanner, archiver, feed_fetcher, automation
__actor_verified1 when the bot's identity was cryptographically/network confirmed
__verification_methodweb_bot_auth, cidr, reverse_dns, or none
__actor_confidenceClassification confidence, 0.01.0
__is_ai1 when this is an AI crawler/agent

Verification hierarchy (strongest → weakest), short-circuiting on the first decisive result:

  1. Web Bot Auth — RFC 9421 Ed25519 HTTP Message Signatures, verified against the signer's /.well-known/http-message-signatures-directory. Unspoofable.
  2. Exact CIDR membership — the client IP must fall inside the vendor's published IP ranges (not just the same ASN, which spoofers share).
  3. Forward-confirmed reverse DNS — for vendors that publish no IP list (Yandex, Baidu, Amazon, Apple).

A recognized bot UA that fails verification (e.g. claims GPTBot from a non-OpenAI IP) is flagged spoofed_bot — the high-value fraud signal. A UA-only match with no verification is suspected_bot.

Drop vs. store is controlled by your project's bot detection setting:

  • Bot detection ON → bot events are stored, flagged with the attribution above (is_bot), and excluded from human visitor and billing counts.
  • Bot detection OFF → bot traffic is dropped entirely.

Capturing non-JS crawlers: crawlers like GPTBot and Googlebot fetch your pages without running the JavaScript SDK, so they never reach this enrichment path. To detect them, forward your server/edge request signals to Kitbase — see the Bot & Crawler Detection setup guides for your platform, or the endpoint reference. Both run the same attribution pipeline.

Geolocation

TagDescription
__countryCountry (derived from IP)
__regionRegion or state
__cityCity

Page and Attribution

TagDescription
__pathPage path
__referrerReferrer URL
__utm_sourceUTM source parameter
__utm_mediumUTM medium parameter
__utm_campaignUTM campaign parameter
__utm_termUTM term parameter
__utm_contentUTM content parameter

Query Filters

These enriched fields are available as filter dimensions in the dashboard. Filter events by browser, country, device, UTM parameters, and more directly from the events list.

Notifications

When notify: true is set, the event triggers a real-time notification in the Kitbase dashboard. Combine with icon and description to make notifications informative at a glance.

typescript
await kitbase.track({
  channel: 'payments',
  event: 'Large Purchase',
  icon: '💰',
  notify: true,
  description: 'A customer just made a $500+ purchase',
  tags: { amount: 549.99 }
});

Good candidates for notifications:

  • New signups and subscriptions
  • Failed payments and billing issues
  • Critical application errors
  • Security events (login from new device)
  • Large or unusual transactions

Revenue Tracking

Track revenue with the dedicated trackRevenue method. Revenue events are automatically sent to the __analytics channel.

typescript
await kitbase.trackRevenue({
  amount: 99.99,
  currency: 'USD',
  orderId: 'order_123',
  productId: 'prod_premium',
  tags: { plan: 'premium' }
});

This provides structured revenue data in the dashboard, separate from general custom events, so you can track MRR, ARPU, and other financial metrics out of the box.

Timed Events

Measure how long user flows take by starting a timer and letting the SDK automatically attach the duration when the event is tracked.

typescript
// Start timing
kitbase.timeEvent('Checkout Flow');

// ... user completes checkout ...

// Track with automatic $duration tag (in milliseconds)
await kitbase.track({
  channel: 'conversions',
  event: 'Checkout Flow',
  tags: { items: 3 }
});
// The $duration tag is automatically added to the event

Methods

MethodDescription
timeEvent(eventName)Start timing an event
cancelTimeEvent(eventName)Cancel a timed event without tracking
getTimedEvents()Get list of event names currently being timed
getEventDuration(eventName)Get current elapsed duration in milliseconds

Example

typescript
// Check active timers
const activeTimers = kitbase.getTimedEvents();
// => ['Checkout Flow', 'Video Watched']

// Get duration without tracking
const duration = kitbase.getEventDuration('Checkout Flow');
// => 45000 (ms)

// Cancel a timer
kitbase.cancelTimeEvent('Video Watched');

Super Properties

Super properties are automatically included with every event you track. They are useful for attaching persistent context like app version, environment, or user segment.

typescript
// Register properties (overwrites existing)
kitbase.register({ app_version: '2.1.0', environment: 'production' });

// Register only if not already set
kitbase.registerOnce({ first_visit_source: 'google' });

// Remove a specific super property
kitbase.unregister('first_visit_source');

// Get all registered super properties
kitbase.getSuperProperties();

// Clear all super properties
kitbase.clearSuperProperties();

Methods

MethodDescription
register(properties)Set properties (overwrites existing)
registerOnce(properties)Set properties only if not already set
unregister(key)Remove a specific property
getSuperProperties()Get all registered properties
clearSuperProperties()Remove all super properties

In-Memory Storage

Super properties are stored in memory and reset on page reload. For persistent properties, store them externally (e.g., localStorage) and call register() on initialization.

Data Attribute Events

Track events declaratively from HTML without writing JavaScript. The SDK automatically binds event listeners based on data-kb-* attributes.

Click Tracking

html
<button
  data-kb-track-click="Add to Cart"
  data-kb-click-channel="ecommerce"
>
  Add to Cart
</button>

When the button is clicked, an event named "Add to Cart" is tracked on the "ecommerce" channel.

Visibility Tracking

html
<section
  data-kb-track-visibility="Pricing Section Viewed"
  data-kb-visibility-channel="marketing"
  data-kb-visibility-threshold="0.75"
>
  <!-- content -->
</section>

When 75% of the section becomes visible in the viewport, an event named "Pricing Section Viewed" is tracked on the "marketing" channel.

Error Handling

The SDK provides typed error classes so you can handle specific failure modes precisely.

typescript
import {
  AuthenticationError,
  ValidationError,
  TimeoutError,
  ApiError,
  KitbaseError
} from '@kitbase/analytics';

try {
  await kitbase.track({ channel: 'payments', event: 'Purchase' });
} catch (error) {
  if (error instanceof AuthenticationError) {
    // 401: Invalid or missing API key
  } else if (error instanceof ValidationError) {
    // 400/422: Missing or invalid fields - check error.field
  } else if (error instanceof TimeoutError) {
    // Request timed out (default: 30s)
  } else if (error instanceof ApiError) {
    // Other API error - check error.statusCode and error.response
  } else if (error instanceof KitbaseError) {
    // Generic SDK error
  }
}
ErrorHTTP StatusDescription
AuthenticationError401Invalid or missing API key
ValidationError400/422Missing or invalid required fields
TimeoutError--Request exceeded 30s timeout
ApiError4xx/5xxAPI returned an error response

For detailed error handling patterns including retry logic and best practices, see Error Handling.

REST API

You can send events directly to the Kitbase REST API without the SDK. This is useful for server-side integrations or languages without an official SDK.

Single Event

bash
curl -X POST https://ingest.kitbase.dev/sdk/v1/logs \
  -H "X-API-Key: YOUR_SDK_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "event": "Purchase Completed",
    "properties": {
      "channel": "payments",
      "tags": { "amount": 99.99 }
    }
  }'

Response (202 Accepted):

json
{
  "id": "evt_abc123",
  "event": "Purchase Completed",
  "timestamp": "2026-02-13T12:00:00.000Z"
}

Batch Events

Send multiple events in a single request for better performance:

POST /sdk/v1/logs/batch

Request body:

json
{
  "events": [
    {
      "event": "Page Viewed",
      "properties": { "channel": "analytics", "tags": { "path": "/pricing" } }
    },
    {
      "event": "CTA Clicked",
      "properties": { "channel": "conversions", "tags": { "button": "signup" } }
    }
  ]
}

Response (202 Accepted):

json
{
  "accepted": 2,
  "total": 2
}

The batch endpoint accepts an array of events and returns the count of accepted events vs. total submitted.

Next steps

Released under the MIT License.