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
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:
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)
}| Parameter | Type | Required | Description |
|---|---|---|---|
channel | string | Yes | Category for the event (e.g., "payments", "users") |
event | string | Yes | Name of the event (e.g., "Purchase Completed") |
user_id | string | No | Explicit user identifier |
icon | string | No | Emoji icon displayed in the dashboard |
notify | boolean | No | Trigger a real-time notification |
description | string | No | Human-readable event description |
tags | object | No | Key-value metadata (string, number, boolean) |
Response
A successful call returns a TrackResponse with the event's unique identifier and timestamp:
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.
| Channel | Use Case |
|---|---|
payments | Purchases, refunds, subscription changes |
users | Signups, logins, profile updates |
errors | Application errors, failed requests |
notifications | Alerts, messages, delivery status |
api | API calls, webhooks, integrations |
system | Infrastructure 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
| Type | Example |
|---|---|
string | "premium" |
number | 99.99 |
boolean | true |
Example
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
| Tag | Description |
|---|---|
__browser | Browser name (Chrome, Firefox, Safari) |
__browser_version | Browser version |
__os | Operating system (Windows, macOS, iOS) |
__os_version | OS version |
__device | Device type — see the taxonomy below |
__brand | Device brand (Apple, Samsung, Google) |
__model | Device model (iPhone 15, Pixel 8) |
__device_confidence | Detection confidence, 0.0–1.0 (see bands below) |
__signal_source | Which request signal produced the device type |
__in_app | 1 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_RULE | known bot ruleset / robot UA | 0.99 |
FORM_FACTORS | Sec-CH-UA-Form-Factors client hint | 0.95 |
LOW_ENTROPY_HINTS | Sec-CH-UA-Mobile + platform hints | 0.85 |
UA_FUSED / UA_ONLY | User-Agent parse (with / without hints) | 0.75 |
UA_FUSED / UA_ONLY (Unknown) | parsed but class unresolved | 0.60 |
NONE | no usable signal | 0.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.
| Tag | Description |
|---|---|
__actor_type | human, verified_bot, spoofed_bot, suspected_bot, or unknown |
__is_bot | 1 for any kind of bot (the flag used to exclude bots from human/billing counts) |
__bot_name | Attributed bot name (e.g. GPTBot, Googlebot) |
__bot_vendor | Operating vendor (e.g. OpenAI, Google) |
__actor_category | ai_training, ai_search, ai_user_fetch, search_engine, social_preview, seo, monitoring, security_scanner, archiver, feed_fetcher, automation |
__actor_verified | 1 when the bot's identity was cryptographically/network confirmed |
__verification_method | web_bot_auth, cidr, reverse_dns, or none |
__actor_confidence | Classification confidence, 0.0–1.0 |
__is_ai | 1 when this is an AI crawler/agent |
Verification hierarchy (strongest → weakest), short-circuiting on the first decisive result:
- Web Bot Auth — RFC 9421 Ed25519 HTTP Message Signatures, verified against the signer's
/.well-known/http-message-signatures-directory. Unspoofable. - Exact CIDR membership — the client IP must fall inside the vendor's published IP ranges (not just the same ASN, which spoofers share).
- 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
| Tag | Description |
|---|---|
__country | Country (derived from IP) |
__region | Region or state |
__city | City |
Page and Attribution
| Tag | Description |
|---|---|
__path | Page path |
__referrer | Referrer URL |
__utm_source | UTM source parameter |
__utm_medium | UTM medium parameter |
__utm_campaign | UTM campaign parameter |
__utm_term | UTM term parameter |
__utm_content | UTM 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.
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.
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.
// 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 eventMethods
| Method | Description |
|---|---|
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
// 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.
// 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
| Method | Description |
|---|---|
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
<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
<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.
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
}
}| Error | HTTP Status | Description |
|---|---|---|
AuthenticationError | 401 | Invalid or missing API key |
ValidationError | 400/422 | Missing or invalid required fields |
TimeoutError | -- | Request exceeded 30s timeout |
ApiError | 4xx/5xx | API 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
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):
{
"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/batchRequest body:
{
"events": [
{
"event": "Page Viewed",
"properties": { "channel": "analytics", "tags": { "path": "/pricing" } }
},
{
"event": "CTA Clicked",
"properties": { "channel": "conversions", "tags": { "button": "signup" } }
}
]
}Response (202 Accepted):
{
"accepted": 2,
"total": 2
}The batch endpoint accepts an array of events and returns the count of accepted events vs. total submitted.
Next steps
- Autocapture — every event Kitbase tracks for you automatically.
- Identify Users — attach events to real users instead of anonymous IDs.
- Events dashboard guide — viewing, filtering, and analyzing events.
- API reference — track events over REST without an SDK.