First steps
Send your first event
From one track call to a row in the dashboard — the full path, with platform parity and a troubleshooting checklist for the common failures.
Once an SDK is installed and initialized, every product Sankofa offers — analytics, replay correlation, cohorts, retention, experiment exposures — is downstream of one primitive: the event. This guide walks you through firing your first event, finding it in the dashboard, and confirming the path is healthy end-to-end.
If you haven't installed an SDK yet, pick a platform from Install first.
What an event is
An event is a named action with optional structured properties. It belongs to a distinct ID (anonymous until identify names it) and an environment (live or test, resolved by API key). The engine appends default properties — OS, device, app version, geo — and writes a single row to ClickHouse.
Three rules to keep in mind:
Names are stable contracts
Once you ship
checkout_startedin v1, never rename it — every funnel, cohort, and retention chart references the name string. Use snake_case, present tense, and verb-noun ordering. See Events deep dive for the full naming guide.Properties are typed JSON
Strings, numbers, booleans, and JSON arrays/objects are all valid. Sankofa indexes the keys you actually filter on; unindexed properties are still queryable, just slower.
`identify` is not the first call
Track first, identify when the user signs in. Sankofa stitches the pre-identify anonymous traffic onto the resolved user automatically — no second
trackcall needed.
Fire the event
Pick a real user action — a button click, a route change, a checkout step — and wire one track call to it. The function shape is the same across every SDK:
import { Sankofa } from "@sankofa/browser";
document.getElementById("checkout-button")?.addEventListener("click", () => {
Sankofa.track("checkout_started", {
cart_value: 49.99,
item_count: 3,
currency: "USD",
});
});ElevatedButton(
onPressed: () async {
await Sankofa.instance.track('checkout_started', {
'cart_value': 49.99,
'item_count': 3,
'currency': 'USD',
});
},
child: const Text('Checkout'),
);import { Sankofa } from "@sankofa/react-native";
<Pressable onPress={() => Sankofa.track("checkout_started", {
cart_value: 49.99,
item_count: 3,
currency: "USD",
})}>
<Text>Checkout</Text>
</Pressable>Button("Checkout") {
Sankofa.shared.track("checkout_started", properties: [
"cart_value": 49.99,
"item_count": 3,
"currency": "USD",
])
}button.setOnClickListener {
Sankofa.track("checkout_started", mapOf(
"cart_value" to 49.99,
"item_count" to 3,
"currency" to "USD"
))
}import { Sankofa } from "@sankofa/node";
app.post("/checkout/start", async (req, res) => {
await Sankofa.track("checkout_started", {
cart_value: 49.99,
item_count: 3,
currency: "USD",
}, { distinctId: req.user?.id ?? "anon" });
res.status(204).send();
});client.Track(ctx, sankofa.Event{
Name: "checkout_started",
DistinctID: userID,
Properties: map[string]any{
"cart_value": 49.99,
"item_count": 3,
"currency": "USD",
},
})sankofa.track("checkout_started", {
"cart_value": 49.99,
"item_count": 3,
"currency": "USD",
}, distinct_id=request.user.id)Sankofa.track("checkout_started", Map.of(
"cart_value", 49.99,
"item_count", 3,
"currency", "USD"
));Reload your app and trigger the action.
Find the event in the dashboard
Open Live events
Open app.sankofa.dev → your project → Live events in the left rail. This stream shows raw events as they land in ClickHouse, typically within 1–2 seconds.
Filter to your event name
Use the search box and type
checkout_started. The stream filters to only matching rows.Inspect the payload
Click a row to expand it. You'll see your custom properties (
cart_value, etc.) plus the SDK's default properties ($os,$browseror$device_model,$app_version,$geo_country, etc.). Confirm the environment badge saysliveortestmatching the API key you used.
Identify the user when they sign in
After authentication, call identify with your stable user ID. Sankofa rewrites the pre-identify anonymous events onto the new ID server-side so funnels, cohorts, and retention all stitch correctly.
await Sankofa.identify("user_123", {
email: "ada@example.com",
plan: "pro",
});await Sankofa.instance.identify('user_123', traits: {
'email': 'ada@example.com',
'plan': 'pro',
});Sankofa.identify("user_123", {
email: "ada@example.com",
plan: "pro",
});Sankofa.shared.identify(userId: "user_123", traits: [
"email": "ada@example.com",
"plan": "pro",
])Sankofa.identify("user_123", mapOf(
"email" to "ada@example.com",
"plan" to "pro"
))await Sankofa.identify("user_123", {
email: "ada@example.com",
plan: "pro",
});client.Identify(ctx, sankofa.Person{
DistinctID: "user_123",
Traits: map[string]any{"email": "ada@example.com", "plan": "pro"},
})sankofa.identify("user_123", traits={
"email": "ada@example.com",
"plan": "pro",
})Sankofa.identify("user_123", Map.of(
"email", "ada@example.com",
"plan", "pro"
));The dashboard's People view will now show user_123 with the events the same browser fired anonymously a moment ago.
Troubleshooting
If the event hasn't shown up after 30 seconds:
The engine resolves live vs test from the API key, not from a config flag. Check that the key in your init call matches the environment you're looking at in the dashboard. A sk_test_… key always lands in test; a sk_live_… key always lands in live.
Open the browser devtools or your server logs. SDK errors typically surface with a [sankofa] prefix when debug: true. Common failures:
- 401 Unauthorized — wrong key, or key revoked. Generate a fresh one in the dashboard.
- Network blocked — content-blockers or strict CSPs can block
cdn.sankofa.devandapi.sankofa.dev. Allow both. - Forgot init — confirm
Sankofa.init(...)ran before thetrackcall. Subsequentinitcalls are no-ops, so safest to call it at module top-level.
Sankofa accepts strings, numbers, booleans, and JSON arrays/objects. Dates and BigInt are stringified automatically. If you're seeing strings where you expect numbers, check that you didn't wrap the value in quotes at the call site.
Identity stitching is server-side. Anonymous events fired before identify get rewritten with the new distinct ID within a minute or two. If they still appear under the anonymous ID after 5 minutes, check that the user actually had a session running in the same project; cross-project stitching is not supported.