Skip to main content

Overview

event() records a single behavioral event. Events are the primary signal ChurnKit uses to calculate churn risk — frequent, diverse events indicate healthy engagement, while gaps or negative events (e.g. support tickets) raise risk scores.

Signature

churn.event(
  userId: string,
  event: string,
  properties?: EventProperties,
  options?: CallOptions
): Promise<{ ok: boolean }>

Parameters

userId
string
required
The user who performed the action.
event
string
required
Event name — use snake_case by convention. Must be non-empty.
properties
EventProperties
Optional key-value metadata. Values must be string | number | boolean | null.
options.signal
AbortSignal
Cancel the request via an AbortSignal.

What events to track

Track events that signal engagement or disengagement:
// Core feature usage — strongest retention signal
await churn.event('user_123', 'feature_used', { feature: 'analytics_dashboard' })
await churn.event('user_123', 'report_exported')
await churn.event('user_123', 'team_member_invited')
await churn.event('user_123', 'api_called')
await churn.event('user_123', 'integration_connected', { provider: 'slack' })

Examples

Simple event

await churn.event('user_123', 'dashboard_viewed')

Event with properties

await churn.event('user_123', 'feature_used', {
  feature: 'csv_export',
  rows_exported: 1500,
  format: 'csv',
})

In a Next.js Route Handler

import { churn } from '@/lib/churnkit'

export async function POST(req: Request) {
  const { userId, feature } = await req.json()
  await churn.event(userId, 'feature_used', { feature })
  return Response.json({ ok: true })
}

Fire and forget (non-blocking)

// Don't await if you don't need to block the response
void churn.event(userId, 'page_viewed', { path: '/settings' })

Return value

{ ok: true }

Errors

CodeWhen
VALIDATION_ERRORuserId or event is empty
UNAUTHORIZEDAPI key is invalid
RATE_LIMITEDToo many requests — back off and retry
TIMEOUTRequest exceeded timeout
For high-throughput scenarios (e.g. tracking every API call), use the EventBatcher instead of calling event() individually.