Skip to main content

Tracking Events

Events are the raw material for everything in Joryio: segments, journey triggers, campaign filters, and analytics all run on the events your apps send. This guide covers the conventions and mechanics that are shared across all SDKs - Web, iOS, Android, and React Native. For installation and platform-specific setup, see the individual SDK pages.

How tracking works

Every SDK follows the same pipeline:

  1. You call track(eventName, properties) in your app.
  2. The SDK queues the event locally (with offline persistence) and sends it in batches - by default every 5 seconds or every 50 events, whichever comes first.
  3. The batch is delivered to POST /v1/track/batch, authenticated with your app's SDK key (format jry_sdk_<platform>_<random>, one per app - see Apps Overview).
  4. The server resolves the user (anonymous or identified), stores the events, and fans them out to segments, journey triggers, and analytics.

A batch may contain at most 500 events. Timestamps are set by the SDK at call time; the server accepts epoch milliseconds or ISO-8601 strings, and falls back to server time if the timestamp is missing or invalid (so a bad device clock never causes a rejected event).

Event naming conventions

An event name is a string of up to 255 characters. Beyond that, Joryio does not enforce a format - but consistency matters, because event names are how you find events later in segment builders, journey triggers, and the Event Explorer.

Recommendations:

  • Use Title Case with spaces. Joryio's built-in events follow the industry-standard e-commerce event taxonomy (Object + past-tense Action, Title Case): Product Viewed, Product Added, Checkout Started, Order Completed from the store integrations, so Title Case custom events (Trial Started, Signup Completed) keep the catalog uniform. Snake case also works - but pick one convention and stick to it; mixing creates duplicate-looking events.
  • Name the action, not the UI. Order Completed survives a redesign; Green Button Clicked does not.
  • Use object + past-tense verb. Subscription Upgraded, Video Played, Search Performed.
  • Keep variability in properties, not names. One Product Viewed event with a category property beats fifty Viewed <Category> events - segments and triggers match on the event name first, then filter on properties.
  • Avoid overly generic events. Clicked with no properties tells you nothing you can act on.

Event names are case-sensitive: order_placed and Order_Placed are two different events.

Properties and data types

Properties are a JSON object attached to each event. Any JSON value is accepted:

TypeExampleNotes
String"currency": "USD"Also use for dates, as ISO-8601 strings
Number"total": 149.99Integers and floats
Boolean"first_order": true
Array"item_ids": ["SKU-1", "SKU-2"]
Object"shipping": { "method": "express" }Nested objects allowed

Server-side limits per event:

LimitValue
Event name length255 characters
Total properties size (serialized JSON)50 KB
Top-level property keys200
Nesting depth5 levels
Events per batch request500

Events exceeding these limits are rejected. Property names follow the same advice as event names: pick a convention (product_id, not sometimes productId) and keep types stable - an order_id that is a string in one event and a number in another makes filtering unreliable.

Properties prefixed with $ (such as $platform, $session_id, $app_id) are added automatically - some by the SDKs ($device_id, device data on Session Start) and some by Joryio's ingestion pipeline when the event is received ($app_id, $session_id, $is_identified). Treat that prefix as reserved and do not use it for your own properties.

User attribute key rules

Attribute keys (the object you pass to setAttributes / setAttribute) have two constraints beyond the property advice above, and a key that breaks either one is dropped - the rest of the call is stored normally:

Not allowedWhy
A . anywhere in the keyA dot is read as a PATH separator, not a character. "profile.email" would store nested profile: { email }, so a segment on profile.email would match nothing and you would have no way to see why.
A leading $Reserved, exactly as for event properties above.
An empty keyNothing to store.

Keys are dropped rather than renamed on purpose: renaming profile.email to profile_email would report success while putting your data somewhere you never query. The dropped keys are named in the server log, and the SDKs warn about them in your console during integration.

identify vs track

The two core calls do different jobs:

  • identify(userId) says who the user is. It binds the current device/session to your stable user ID and merges any anonymous history into that profile. Attributes set with setAttributes describe the user (email, plan, name) and live on the profile.
  • track(eventName, properties) says what happened. Properties describe the event, not the user, and are immutable once recorded.

Rules of thumb:

  • Call identify as early as you know the user - at login, and on app start if a session is restored. Use the same user ID on every platform so web and mobile activity land on one profile (see Multi-Platform Tracking).
  • Before identify, events are tracked under an anonymous ID. When you later identify, the server merges the anonymous history into the identified profile, so pre-signup events (first visit, attribution) are not lost.
  • Use alias(userId) at signup to explicitly link the anonymous user to the new account, then identify(userId).
  • Put per-occurrence data in event properties (total, coupon), and durable facts about the person in attributes (plan, lifetime_value).
  • Call reset() on logout so the next user on the device does not inherit the profile.

The same event in every SDK

The track call is intentionally identical in shape across SDKs. Here is the same Order Completed event on all four platforms.

import JoryioSDK from '@joryio/web-sdk';

const joryio = new JoryioSDK({ sdkKey: 'jry_sdk_web_...' });

joryio.track('Order Completed', {
order_id: 'ORD-2024-001',
total: 149.99,
currency: 'USD',
item_count: 3,
coupon: 'SAVE10',
});

Because the name and properties are identical, one segment condition or journey trigger matches the event no matter which platform it came from.

For standard e-commerce activity (product views, carts, checkout, orders), prefer the SDKs' built-in e-commerce trackers - they emit the standardized event names Joryio's e-commerce features expect. See the cross-SDK E-Commerce Tracking guide.

What happens server-side

Once a batch is accepted, each event:

  • Is stored in the analytics store, attached to the resolved user profile (identified or anonymous).
  • Is evaluated against journey triggers. A journey whose entry trigger matches the event name (and property filters) enrolls the user immediately - this is how "abandoned cart" or "welcome" journeys start.
  • Feeds segments. Event-based segment conditions ("performed Order Completed in the last 30 days") update from the event stream, and campaigns targeting those segments pick up the change.
  • Appears in analytics - the Event Explorer, funnels, and per-app analytics.
  • Can update the profile. Some events have server-managed side effects: for example, Session Start events refresh the user's device record and set profile attributes such as country (derived from the request IP).

Two server behaviors worth knowing:

  • Bot flagging. Requests from known bot user agents are flagged (events are tagged, and analytics exclude them); profile-mutating calls such as identify and setAttributes are skipped for bots. Traffic from headless browsers in your own test automation may therefore not create profiles.
  • Lenient validation. Unknown extra fields in the payload are stripped rather than rejected, so an SDK version mismatch does not drop your events.

Verifying and debugging

Check that an event arrived

  1. Trigger the event in your app.
  2. In the Joryio dashboard, open Analytics → Event Explorer. Filter by event name and you should see the event within seconds of the SDK flushing its batch (default flush interval: 5 seconds).
  3. For a per-app view, open Settings → Apps and click View Analytics on the app - it shows total events, the last event timestamp, and top event names, which quickly confirms whether anything from that SDK key is arriving.

If events do not show up

  • Force a flush. Events are batched; call flush() (available in every SDK) to send immediately instead of waiting for the timer.
  • Enable debug logging. Every SDK has an enableDebug option that logs each queued event and each network request with its response.
  • Check the SDK key. It must match the app's platform - jry_sdk_web_... for the Web SDK, jry_sdk_ios_... for iOS, jry_sdk_android_... for Android. A regenerated key invalidates the old one immediately.
  • Check the app is active under Settings → Apps - events sent with the key of a deactivated app are rejected.
  • Watch the network response. A batch response includes success and, on partial failure, failedIndices telling you which events in the batch were not stored. Oversized properties (over 50 KB, over 200 keys, or nesting deeper than 5 levels) are the usual cause of rejected events.
  • Web only: the SDK tracking endpoints allow any origin (Access-Control-Allow-Origin: *), so CORS errors usually mean a wrong apiEndpoint or a blocking browser extension - check the console. Also confirm the site runs over HTTPS so localStorage-based queue persistence works.
  • Testing from automation? Remember bot flagging above - verify with a real browser or device.