Product events
Track any user action with a single push call. Events carry arbitrary metadata, link automatically to the current session and replay, and — when you tag them with a category, feature, and surface — feed the AAARRR funnel reports out of the box.
01Basic event tracking
Any user-visible action is a candidate for a product event.
Simple event
Sumidata.push('event', ['button_click'])Event with properties
Sumidata.push('event', ['form_submitted', {
formId: 'newsletter-signup',
fields: 5
}])02Event payload
Nested objects, arrays, numbers, booleans — anything JSON-serializable is fine.
Sumidata.push('event', ['feature_used', {
featureId: 'export_csv',
rowCount: 1200,
format: 'xlsx',
filters: ['last_30d', 'segment:enterprise']
}])JSONExtractString, JSONExtract*) or ask the AI Analyst. Keep payloads small (a few KB) so ClickHouse JSON parsing stays cheap at report time.03SDK commands
Four push commands cover the full lifecycle — event, identify, reset, identifyExperiment.
Every method is GTM-style: Sumidata.push('method', [args]). Calls made before the SDK finishes loading are queued and flushed on init — the stub <script> tag swallows the intent even if the main bundle hasn't arrived yet.
event
Fire a product event or conversion. See section 04 for conversion payloads.
Sumidata.push('event', ['page_viewed', { path: '/pricing' }])identify
Stamp the session with your stable user ID. If a different user was previously identified on this device, the SDK automatically rotates the session — the old session stays attached to the old user, a new session is created for the new user. This prevents history merge on shared devices.
// On login
Sumidata.push('identify', [user.id])reset
Clears the stored user ID, rotates the session, and starts a fresh replay. Always call this on logout — otherwise the next user on the same device inherits the prior identity.
// On logout
Sumidata.push('reset', [])identifyExperiment
Register A/B variant assignments for the current session. Sticky: the first assignment per (sessionId, experimentId) wins. See Experiments for the full guide.
Sumidata.push('identifyExperiment', [[
{ id: 'pricing-v3', variant: 'control' }
]])Sumidata.getSessionId() — useful when you need to carry the browser session ID back to your server for linked server-side conversions. Returns null until session creation finishes.04Conversions
Any event with orderId + totalAmount is treated as a conversion. Extra fields add depth to reports.
A conversion is a product event with two required fields: orderId (identifier, used for dedup) and totalAmount (net revenue). Everything else is optional but the richer the payload, the deeper the revenue, campaign, and cohort reports go.
Sumidata.push('event', ['purchase', {
// --- required
orderId: 'ord_01HW…',
totalAmount: 79.99,
currency: 'USD',
// --- product
productId: 'plan_pro_monthly',
productName: 'Pro (monthly)',
productCategory: 'subscription',
quantity: 1,
unitPrice: 79.99,
// --- discounts
couponCode: 'LAUNCH20',
discountAmount: 20.00,
// --- lifecycle
isPrimarySale: true
}])For the full field reference, multi-line-item handling, and backend ingest, see Conversions & attribution and Server-side ingest.
05Funnel metadata (AAARRR)
Three reserved keys (_category, _feature, _surface) bucket the event into AAARRR funnels at ingest.
Sumidata does not auto-classify events by name. Category is something you declare — by passing three reserved keys alongside the event. The server validates them, strips them out of the stored payload, and writes them into a per-project dictionary (event_metadata_dict) so every future event with the same name joins the same funnel.
| Field | Allowed values |
|---|---|
_category | awareness · acquisition · activation · revenue · retention · referral |
_surface | marketing_site · app · mobile_app · docs |
_feature | Lowercase alphanumeric + underscores only, ^[a-z0-9_]+$ |
// A signup — acquisition stage, marketing site, auth feature
Sumidata.push('event', ['signup', {
_category: 'acquisition',
_feature: 'auth',
_surface: 'marketing_site'
}])
// A purchase — revenue stage, checkout feature, in the app
Sumidata.push('event', ['purchase', {
orderId: order.id, totalAmount: order.total, currency: 'USD',
_category: 'revenue', _feature: 'checkout', _surface: 'app'
}])400. The error body is { "errors": { "events[i]._category": ["Invalid category. Allowed: …"] } } (and analogous for _surface / _feature). Keep your taxonomy stable — the first event you send with a given name seeds the dictionary entry that every subsequent event resolves against at query time.06Payload budget
Practical budgets — they aren't enforced server-side today, but staying under them keeps reports fast.
There is no hard 400 on payload size or key count at ingest today. Keep each event under these targets so ClickHouse JSON extraction at query time stays cheap and the UI renders payloads cleanly:
- ≤ 32 top-level keys per payload.
- ≤ 8 KB of JSON per payload.
- ≤ 64 characters per event name, snake_case by convention.
If you need to stash large blobs (full API responses, rendered documents), store them somewhere else and put the URL or hash on the event.
07Server-side ingest
For events that don't originate in a browser, post to the same SDK ingest endpoint with source: 'backend'.
Not every event fires from the browser. Stripe webhooks, background-job results, emailed receipts — all of these originate on your server. Post them to the same POST /sdk/ingest endpoint the SDK uses, with source: 'backend':
curl "https://api.sumidata.io/sdk/ingest" \
-X POST \
-H "Content-Type: application/json" \
-d '{"projectId":"proj_…","deviceId":"00000000-0000-0000-0000-000000000000","externalUserId":"user_7e2a9c","source":"backend","events":[{"name":"subscription_renewed","_category":"retention","_feature":"billing","_surface":"app"}]}'See Server-side ingest for the full API reference — request body, error codes, attribution fallback, and AI-agent spec.
