Identity and event taxonomy
Choose stable customer identifiers and a maintainable product event contract.
Identity and event quality determine whether every downstream lifecycle, journey, segment, and score is trustworthy. Decide these contracts before building a large model.
Choose the customer boundary
Kite models one customer as one account, workspace, or tenant. Send its stable internal ID as
customerId.
Good IDs are immutable and available to every backend producer:
account_42
workspace_01J...
tenant_8f31...Do not use an email address, mutable slug, display name, browser session, or individual user ID
when Customer Success operates at the account level. If a person acts for an account, keep the
account as customerId and add a non-sensitive actor ID to event properties only when needed.
IDs are opaque and isolated by project and environment. Kite does not merge aliases or maintain a user-to-account hierarchy.
Identify and track
Use identify for current customer attributes and track for immutable product facts:
await kite.identify({
customerId: 'account_42',
traits: {
plan: 'pro',
availableFeatures: ['exports', 'sso'],
},
});
await kite.track({
customerId: 'account_42',
event: 'integration.connected',
properties: { provider: 'salesforce' },
idempotencyKey: 'integration_987_connected',
});Traits are shallow-merged, so updating { plan: 'pro' } preserves other existing traits. A
track operation creates an unknown customer with empty traits when necessary.
| Operation | Use it for | Historical behavior |
|---|---|---|
identify | Current plan, seats, entitlements, CRM attributes | Mutates current traits; it is not a replayable historical fact. |
track | Account created, integration connected, report exported | Creates an immutable event with an occurrence timestamp. |
The raw API accepts identify only inside POST /v1/events/batch. The SDK handles that detail.
Design event names
Prefer stable, past-tense domain facts:
account.createdintegration.connectedreport.exportedsubscription.cancelled
Avoid UI implementation details such as modal_button_clicked, conclusions such as
customer.healthy, and values embedded in names. Put dimensions such as provider, feature, and
format in properties.
For every event, decide:
| Concern | Decision |
|---|---|
| Name | One namespaced fact with one durable meaning. |
| Producer | The backend authoritative for that fact. |
| Customer | The account or workspace receiving the outcome. |
| Timestamp | When it occurred, including an ISO 8601 timezone. |
| Properties | Bounded dimensions required by a definition or diagnosis. |
| Idempotency | Stable source operation ID when one exists. |
Declare the contract
import { defineConfig } from '@kitesdk/config';
export default defineConfig({
project: 'acme',
strictSchema: true,
events: {
'integration.connected': {
description: 'A workspace completed an integration connection',
properties: {
provider: { type: 'string', enum: ['salesforce', 'hubspot'] },
},
},
'report.exported': {
properties: {
format: { type: 'string', enum: ['csv', 'pdf'] },
actorId: { type: 'string', optional: true },
},
},
},
customerTraits: {
plan: { type: 'string', optional: true },
seats: { type: 'number', optional: true },
availableFeatures: {
type: 'array',
items: { type: 'string' },
optional: true,
},
},
});Properties are required unless optional: true. With strictSchema: true, undeclared events,
missing or unknown properties, invalid traits, and wrong types return HTTP 422. With false,
Kite accepts violations and writes diagnostics to service logs; it does not expose a durable
violation inbox.
Roll strict mode out safely:
- Declare the existing producer contract with
strictSchema: false. - Deploy to
testand exercise every producer. - Fix diagnostics and add producer contract tests.
- Enable
strictSchema: trueintest, then promote the tested version.
Delivery semantics
Use an idempotency key for every retryable track. Repeating the same key and canonical payload
returns the original event; changing the customer, event, properties, or explicit timestamp
returns 409 IDEMPOTENCY_CONFLICT.
The Node.js SDK generates a key when retries are enabled, but a stable business key also protects against duplicate calls across processes and deployments.
const result = await kite.track({
customerId: 'account_42',
event: 'report.exported',
properties: { format: 'csv' },
idempotencyKey: 'export_98765',
});
if (!result) {
// onError has received the delivery failure.
}SDK delivery methods report failures through onError and resolve to undefined; they do not
throw request failures. Batch requests preserve operation order and reject invalid input rather
than partially accepting it.
Identity migrations
There is no identity merge command. For a migration:
- Choose the canonical ID in the source system.
- Stop producers from sending the old ID.
- Backfill retained facts under the canonical ID with deterministic idempotency keys.
- Run a customer-scoped dry-run and persisted recompute.
- Verify the canonical customer before expanding the migration.
Do not copy current traits into historical event properties unless they were true when the event occurred. See Backfill and recomputation for replay semantics.
Governance checklist
- Assign an owner in your internal catalog and add a DSL
descriptionfor every event. - Emit authoritative backend facts rather than inferred browser clicks.
- Never silently change the meaning or type of an existing property.
- Exclude secrets, credentials, and unnecessary personal data.
- Validate producers in
testbefore promoting schemas tolive.