Skip to main content

Tracking Events

An event is a record of something a person did—signed up, viewed a screen, completed a purchase, or abandoned a cart. Events are the behavioural layer of Conviso: they describe what people do, while attributes describe who people are.

Once events are flowing in, you can search them, inspect their properties, build segments, trigger campaigns, measure broadcast and campaign goals, and personalise messages with live event data.

This page is the working guide: how to send events, how to find them in Conviso, and how to use them.


Do this first: send and find one event​

If you have never sent an event before, complete this sequence once. You will know the pipeline works when you can see the event on a person in the dashboard.

1. Get a workspace API key​

  1. Open your Conviso workspace
  2. Go to Settings → Workspace → API keys
  3. Create a key (or copy an existing one)
  4. Keep it somewhere safe — you will send it as the Authorization header

2. Identify a test person​

curl -X POST "https://api.opencdp.io/gateway/data-gateway/v1/persons/identify" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"identifier": "user_123",
"properties": {
"email": "[email protected]",
"first_name": "Ada",
"last_name": "Lovelace",
"created_at": "2026-09-07T12:00:00.000Z",
"plan": "pro"
}
}'

Successful response:

{
"success": true,
"message": "Identification complete",
"data": {}
}

3. Track a test event for that same person​

curl -X POST "https://api.opencdp.io/gateway/data-gateway/v1/persons/track" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"identifier": "user_123",
"eventName": "purchase_completed",
"properties": {
"order_id": "ord_789",
"amount": 99.99,
"currency": "USD",
"item_count": 1
}
}'

Successful response:

{
"success": true,
"message": "Track event recorded",
"data": {}
}

A 200 means the event was accepted. It is processed asynchronously, so it may take a few seconds to appear in the dashboard.

4. Find the event in Conviso​

From Events (by event name):

  1. Go to Events in the left navigation
  2. Search for purchase_completed
  3. Click the event name
  4. In Activities, confirm Ada Lovelace (or user_123) appears
  5. Click the row — the drawer shows the JSON properties (order_id, amount, currency)

From People (by person):

  1. Go to People
  2. Search for user_123 or [email protected]
  3. Open the profile
  4. Open Activity
  5. Confirm purchase_completed is in the timeline

If both places show the event with the properties you sent, you are ready to instrument your product.


How events work​

Every tracked event belongs to a person and has:

FieldWhat it isExample
Event nameStable label for the action (case-sensitive)purchase_completed
IdentifierThe same person ID you used in identifyuser_123
PropertiesOptional metadata about this occurrence{ "amount": 99.99 }
TimestampWhen the action happened2026-09-07T12:00:00.000Z

You do not create event types in the dashboard. The first time Conviso receives a new eventName, it appears under Events. After that, every occurrence is stored against the person who performed it.

Two different records are involved:

  • Event type — the named action in your workspace (purchase_completed). One row in the Events table. Shows last tracked time, occurrence count, and how many people have done it.
  • Tracked event — one occurrence by one person at one time. These are the rows in Activities, and on the person’s Activity tab.

A typical flow:

  1. Your app or backend identifies a person
  2. Your app or backend tracks an event with that same identifier
  3. Conviso attaches the event to the person’s profile
  4. The event becomes available for segments, campaigns, goals, analytics, and personalisation

Common use cases:

  • Triggering a campaign when someone abandons a cart
  • Building a segment of people who purchased in the last 30 days
  • Measuring whether a broadcast caused people to complete onboarding
  • Personalising an email with the product they just viewed
  • Analysing which events drive conversion in a journey

Identify, then track​

Events are only useful when they attach to the right person. Always use a consistent identifier—typically your internal user ID—across identify, track, device registration, and messaging.

// 1. Create or update the person
await client.identify('user_123', {
email: '[email protected]',
first_name: 'Ada',
last_name: 'Lovelace',
created_at: new Date().toISOString(),
plan: 'pro',
});

// 2. Track behaviour with the same ID
await client.track('user_123', 'purchase_completed', {
order_id: 'ord_789',
amount: 99.99,
currency: 'USD',
});

⚠️ Important:
Do not mix identifiers. If you identify with user_123 and later track with the person’s email, Conviso treats those as two different people. Events will not appear on the profile you expect, campaigns will not fire, and segments will under-count.

Rules:

  • Use your internal user ID (user_123), not an email, as identifier
  • Use that same string on every identify, track, registerDevice, and transactional send
  • For mobile apps the identifier must not be an email address — see Identity and devices
  • Include email, first_name / firstName, and created_at on the first identify so the person is searchable and usable in email

Learn more about creating people in Adding people and People and their profiles.


Sending events into Conviso​

You can send events from any of the following sources. Choose the option that matches where the action happens.

SourceBest forStart here
Direct APIAny language, curl, ETL, first testDirect API
Node.js SDKServer-side apps, APIs, web backendsNode.js SDK
Python SDKPython backends and workersPython SDK
Flutter SDKCross-platform iOS and Android appsFlutter SDK
Android SDKNative Android (Kotlin/Java)Android SDK
iOS SDKNative iOS (Swift/Objective-C)iOS SDK
React Native SDKReact Native / Expo development buildsReact Native SDK
PostHog webhookProduct analytics already in PostHogPostHog webhook

All of these sources write to the same Data Gateway. Once an event lands, it behaves the same in Conviso regardless of how it was sent.

Base URL (production): https://api.opencdp.io/gateway/data-gateway

Browse the full catalogue on the Integrations page, or start from Get started with SDKs.

Authentication​

Every source authenticates with a workspace API key:

  1. Go to Settings → Workspace → API keys
  2. Create or copy a key
  3. Direct API / PostHog: send it in the Authorization header (raw key value — not Bearer unless your key already includes that prefix)
  4. SDKs: pass it as cdpApiKey (or cdp_api_key / api_key, depending on the SDK)
Authorization: YOUR_API_KEY
Content-Type: application/json

Keep API keys out of git and out of public web pages. Mobile apps still need a key to talk to the Data Gateway; treat it like any other client-side secret and rotate it if it leaks.


Direct API (complete)​

Use this when you do not have an SDK, or when you want to verify the pipeline with curl.

These are the same endpoints the SDKs call.

OperationMethodPath
Identify a personPOST/v1/persons/identify
Track an eventPOST/v1/persons/track
Register a devicePOST/v1/persons/registerDevice

Identify​

URL: POST https://api.opencdp.io/gateway/data-gateway/v1/persons/identify

FieldTypeRequiredDescription
identifierstringYesUnique person ID (your internal user ID). Cannot be empty.
propertiesobjectYesProfile attributes. Use email, first_name / firstName, last_name / lastName, created_at, plus any custom keys.
timestampstringNoISO 8601 timestamp for the identify call.
curl -X POST "https://api.opencdp.io/gateway/data-gateway/v1/persons/identify" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"identifier": "user_123",
"properties": {
"email": "[email protected]",
"firstName": "Ada",
"lastName": "Lovelace",
"created_at": "2026-09-07T12:00:00.000Z",
"plan": "pro"
}
}'

firstName / first_name and lastName / last_name are equivalent. Conviso maps both onto the person profile.

Track​

URL: POST https://api.opencdp.io/gateway/data-gateway/v1/persons/track

FieldTypeRequiredDescription
identifierstringYesMust match the identifier used in identify.
eventNamestringYesEvent name. Case-sensitive. Cannot be empty.
propertiesobjectNoJSON object of event metadata. Nested objects and arrays are allowed.
curl -X POST "https://api.opencdp.io/gateway/data-gateway/v1/persons/track" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"identifier": "user_123",
"eventName": "purchase_completed",
"properties": {
"order_id": "ord_789",
"amount": 99.99,
"currency": "USD",
"items": [
{ "product_id": "prod_123", "name": "Pro plan", "quantity": 1 }
],
"payment_method": "card"
}
}'

Properties are optional. This is also valid:

{
"identifier": "user_123",
"eventName": "app_opened"
}

What a successful track looks like​

{
"success": true,
"message": "Track event recorded",
"data": {}
}

The body is empty on purpose. Tracking is fire-and-forget: Conviso queues the event and returns immediately. Open Events a few seconds later to confirm it landed.

Errors​

StatusWhenWhat to do
401 Missing Authorization HeaderNo Authorization headerAdd the header
401 Invalid TokenKey is wrong, revoked, or for another workspaceCopy a key from Settings → Workspace → API keys
400 identifier is required / identifier cannot be emptyMissing or empty identifierSend a non-empty string
400 eventName is required / eventName cannot be emptyMissing or empty eventNameSend a non-empty string
400 properties must be an objectproperties is an array or a primitiveSend a JSON object
200 Request payload discarded: exceeds 20kb limitBody larger than 20 KBShrink properties. This still returns HTTP 200 — the event is not stored.
500 Failed to process track requestServer errorRetry; check that identify/track still work with curl

⚠️ Payload size:
Identify and track bodies are limited to 20 KB. Oversized requests are discarded. Check the message field even on HTTP 200.

Track without identify​

If you track an identifier that has never been identified, Conviso still accepts the event. A person profile is created from that identifier, but it will have no email or name until you identify. Always identify first so the person is searchable and you can message them.

Full Direct API reference (including device registration and transactional send): Direct API.


Server SDKs​

Use a server SDK when the event happens in your backend—checkouts, billing webhooks, account changes, or anything you already trust on the server.

Node.js​

Install:

npm install @codematic.io/cdp-node

Identify and track:

import { CDPClient } from '@codematic.io/cdp-node';

const client = new CDPClient({
cdpApiKey: process.env.CDP_API_KEY,
});

await client.identify('user_123', {
email: '[email protected]',
firstName: 'Ada',
lastName: 'Lovelace',
created_at: new Date().toISOString(),
plan: 'pro',
});

await client.track('user_123', 'purchase_completed', {
order_id: 'ord_789',
amount: 99.99,
currency: 'USD',
items: [
{ product_id: 'prod_123', name: 'Pro plan', price: 99.99, quantity: 1 },
],
payment_method: 'card',
});

Signature:

async track(
identifier: string,
eventName: string,
properties?: Record<string, any>
): Promise<void>
  • identifier and eventName cannot be empty — the SDK throws before it calls the API
  • properties is optional
  • Wrap calls in try/catch. For page views and other non-critical tracking, do not block the response:
client
.track(user.id, 'page_viewed', { path: req.path })
.catch((err) => console.error('Track failed', err));

Checkout example:

app.post('/api/checkout', async (req, res) => {
const { userId, items, total } = req.body;
const order = await processCheckout(items);

await client.track(userId, 'purchase_completed', {
order_id: order.id,
amount: total,
currency: 'USD',
item_count: items.length,
payment_method: 'stripe',
});

res.json({ success: true, orderId: order.id });
});

Python​

Install:

pip install cdp-python-sdk
from cdp_client.client import CDPClient
from cdp_client.models import CDPConfig

config = CDPConfig(cdp_api_key="YOUR_API_KEY")
client = CDPClient(config)

await client.identify("user_123", {
"email": "[email protected]",
"first_name": "Ada",
"last_name": "Lovelace",
"created_at": "2026-09-07T12:00:00.000Z",
"plan": "pro",
})

await client.track(
"user_123",
"purchase_completed",
{
"order_id": "ord_789",
"amount": 99.99,
"currency": "USD",
"item_count": 1,
},
)

await client.close()
ParameterTypeDescription
user_idstrSame identifier as identify
event_namestrEvent name
propertiesDict[str, Any]Event metadata

Mobile SDKs​

Use a mobile SDK when the event happens on a device—button taps, screen views, in-app purchases, or app lifecycle. Mobile SDKs also handle push, in-app messaging, offline queuing, and automatic screen tracking.

If you are integrating a mobile app for the first time, start with the Mobile E2E guide. Compare capabilities across platforms in the Mobile feature matrix.

On every mobile SDK:

  1. Initialise before any other call — if you skip this, track fails silently
  2. Identify with a non-email user ID
  3. Track the product action
  4. Enable debug: true while testing and watch identify/track POST logs

Flutter​

await OpenCDPSDK.initialize(
config: OpenCDPConfig(
cdpApiKey: 'YOUR_API_KEY',
debug: true,
autoTrackScreens: true,
),
);

await OpenCDPSDK.instance.identify(
identifier: 'user_123',
properties: {
'email': '[email protected]',
'firstName': 'Ada',
'plan': 'pro',
'created_at': DateTime.now().toIso8601String(),
},
);

await OpenCDPSDK.instance.track(
eventName: 'purchase_completed',
properties: {
'order_id': 'ord_789',
'amount': 99.99,
'currency': 'USD',
},
);

Manual screen tracking (for in-app page targeting — this does not appear as a row in Events):

await OpenCDPSDK.instance.trackScreenView(
title: 'Product Details',
properties: {
'product_id': '123',
'category': 'Electronics',
},
);

Android​

val config = OpenCDPConfig(
cdpApiKey = "YOUR_API_KEY",
debug = true,
autoTrackScreens = true,
trackApplicationLifecycleEvents = true,
)
OpenCDP.initialize(this, config)

OpenCDP.identify(
identifier = "user_123",
properties = mapOf(
"email" to "[email protected]",
"plan" to "pro",
),
)

OpenCDP.track(
eventName = "purchase_completed",
properties = mapOf(
"order_id" to "ord_789",
"amount" to 99.99,
"currency" to "USD",
),
)

iOS​

let config = OpenCDPConfig(
cdpApiKey: "YOUR_API_KEY",
debug: true,
autoTrackScreens: true,
trackApplicationLifecycleEvents: true
)
OpenCDP.shared.initialize(config: config)

OpenCDP.shared.identify(
identifier: "user_123",
properties: [
"email": "[email protected]",
"plan": "pro",
]
)

OpenCDP.shared.track(
eventName: "purchase_completed",
properties: [
"order_id": "ord_789",
"amount": 99.9,
"currency": "USD",
]
)

React Native​

Expo Go is not supported. Use an Expo development build or a bare React Native app.

import { OpenCDPSDK } from '@codematic/opencdp-react-native';

await OpenCDPSDK.initialize({
config: {
cdpApiKey: 'YOUR_API_KEY',
debug: true,
autoTrackDeviceAttributes: true,
},
});

await OpenCDPSDK.instance.identify({
identifier: 'user_123',
properties: {
email: '[email protected]',
plan: 'pro',
},
});

await OpenCDPSDK.instance.track({
eventName: 'purchase_completed',
properties: {
order_id: 'ord_789',
amount: 99.99,
currency: 'USD',
},
});

PostHog webhook​

If you already capture product analytics in PostHog, stream those events into Conviso without adding another client SDK.

Setup​

  1. Log in to PostHog
  2. Go to Data pipeline → Destinations (or Webhooks)
  3. Create a Webhook destination
  4. Set the URL to https://api.opencdp.io/gateway/data-gateway/v1/posthog
  5. Add header Authorization = your Conviso API key
  6. Trigger on all events, or filter to the events you care about
  7. Save and enable

How PostHog events map​

PostHog eventWhat Conviso does
$identifyCreates or updates the person (properties.id, or distinct_id if id is missing)
$register_deviceRegisters a device for push. Requires device_id, platform, fcm_token
Any other eventStored as a tracked event with that event name

Minimum payload:

{
"event": {
"event": "purchase_completed",
"distinct_id": "user_123",
"uuid": "018f...",
"timestamp": "2026-09-07T12:00:00Z",
"properties": {
"id": "user_123",
"amount": 99.99,
"currency": "USD"
}
}
}

PostHog metadata is nested on the tracked event so you can still inspect it in the Activities drawer:

{
"amount": 99.99,
"currency": "USD",
"posthog": {
"uuid": "018f...",
"distinct_id": "user_123",
"url": "https://...",
"timestamp": "2026-09-07T12:00:00Z"
}
}

⚠️ Loop prevention:
Events whose identifier contains the word posthog (case-insensitive) are ignored. This stops internal PostHog system users from creating noise in your workspace.

If events do not appear: confirm the identifier is not posthog…, the event object is present, and the API key is in Authorization.

Full setup: PostHog webhook integration.


Event names and properties​

Naming events​

Event names are case-sensitive. Purchase_Completed and purchase_completed are two different event types, two different Events-table rows, and two different campaign triggers.

Use a consistent convention across every source:

  • Prefer lowercase snake_case: purchase_completed, cart_abandoned, onboarding_step_viewed
  • Name the action that happened, not the page or campaign
  • Keep names stable — renaming creates a new event type; historical data stays on the old name
  • Use the same name from web, iOS, Android, and your backend

Good examples:

  • user_signed_up
  • checkout_started
  • purchase_completed
  • subscription_cancelled
  • video_watched

Avoid:

  • Generic names like click or event
  • Names that include dates or campaign IDs (purchase_summer_2026)
  • The same action tracked under different names from different platforms (Purchase on iOS and purchase_completed on the server)

Event properties​

Properties are the details attached to a single occurrence. They do not update the person’s profile.

  • To change who someone is (plan, email, name) → identify
  • To describe what they just did (amount, order_id, page) → track properties
{
"identifier": "user_123",
"eventName": "purchase_completed",
"properties": {
"order_id": "ord_789",
"amount": 99.99,
"currency": "USD",
"items": [
{ "product_id": "prod_123", "name": "Pro plan", "quantity": 1 }
],
"payment_method": "card"
}
}

Best practices:

  • Store numbers as numbers (99.99, not "99.99") and booleans as booleans (true, not "true")
  • Use ISO 8601 timestamps for dates (2026-09-07T12:00:00.000Z)
  • Nest objects when it helps — you can reference nested keys with dot notation in messages and segment filters
  • Keep the payload under 20 KB
  • Do not put passwords, tokens, payment card numbers, or other secrets in properties

Person attributes vs event properties:

Person attributesEvent properties
Sent withidentifytrack
Stored onThe person profileThat one event occurrence
Available inEvery message ({{ person.* }})Event-triggered messages and event conditions ({{ event.* }})
Exampleplan, email, created_atamount, order_id, page

See Attributes for profile data.

Screen views and lifecycle events​

Mobile SDKs can automatically record screen views and app lifecycle.

Screen views (screen_view / trackScreenView) catalogue screens for in-app messaging page targeting. They do not appear as standard events in the Events list, and they do not trigger campaigns.

A small set of mobile lifecycle names is ignored so they do not flood your workspace:

  • App_inactive
  • App_backgrounded
  • App_hidden
  • App_resumed
  • App_foregrounded

Track the product actions you care about explicitly (purchase_completed, onboarding_completed) rather than relying on lifecycle noise.

For automatic screen tracking:


Finding events in Conviso​

This is how you get events after they have been sent. There is no public “list events” endpoint on the Data Gateway — you inspect them in the workspace.

Events list​

  1. Open the workspace
  2. Click Events in the left navigation

Each row is an event type (not a single occurrence):

ColumnMeaning
Event nameThe eventName you sent
Last trackedWhen the most recent occurrence arrived (relative time if within 90 days)
OccurrencesHow many times this event has been recorded in the workspace

You can:

  • Search by name (the filter is applied as you type)
  • Sort by last tracked
  • Click a name to open the event detail

If the list is empty, nothing has been tracked in this workspace yet. Complete Do this first with curl, then refresh.

Event detail​

The header shows:

  • The event name
  • How many people have performed it (“12 people have performed this event”)

Below that:

  • Used in N segments — click to see which segments reference this event, or Create a segment from this event
  • Used in N campaigns — click to see which campaigns are triggered by this event, or Create a campaign from this event

Use those shortcuts when you are ready to automate. They pre-fill the event name so you do not retype it.

Activities (individual occurrences)​

On the event detail page, Activities lists recent tracked events:

ColumnMeaning
PersonName, email, or external ID. Click through to the profile
TimestampWhen the occurrence happened

Click a row to open the Event Details drawer:

  • Event name
  • Person (with a link to their profile)
  • Timestamp
  • Full properties JSON — copy with the copy button

Lookback starts at 7 days. If the occurrence you just sent is older than that window, or you need more history, widen lookback to 30, 90, or 180 days. Pagination walks older pages within the selected window.

On a person’s profile​

This is the fastest way to debug a single user.

  1. Go to People
  2. Search by external ID (user_123), email, or name
  3. Open the profile
  4. Open Activity

You should see:

  • The identify (profile created/updated)
  • Each track with its event name
  • Messages sent, campaign interactions, and segment changes

If the person exists but the event does not, the track used a different identifier than identify.

See People and their profiles.

What “event not found” usually means​

Work through this in order:

  1. Did track return 200 with "Track event recorded"?
  2. Did you wait a few seconds and refresh Events?
  3. Are you in the same workspace as the API key?
  4. Is the name exactly what you sent (purchase_completed vs Purchase Completed)?
  5. Open People for user_123 — if the person is missing, identify did not land
  6. If the person exists but Activities is empty, the track identifier does not match

Using events in segments​

Data-driven segments include or exclude people based on behaviour.

Create a segment from an event​

  1. Open Events → purchase_completed (or your event)
  2. Click Create a segment from this event
  3. Name the segment (for example, Purchased in last 30 days)
  4. The event condition is pre-selected as Has been performed
  5. Expand the condition to set at least N times and a timeframe
  6. Optionally add event-attribute filters (amount greater than 100)
  7. Save

Event condition options​

OptionWhat it does
Has been performedPeople who did this event
Has not been performedPeople who did not do this event in the chosen timeframe
CountAt least n times (default 1)
Timeframe: everAny time in history
Timeframe: within the lastLast n days
Timeframe: betweenInclusive date range
Event attributesFilter on properties of the event

Event-attribute operators:

  • Equals / not equals
  • Contains
  • Greater than / less than
  • Exists / does not exist

You can require all or any of the attribute filters to match.

Examples​

SegmentCondition
Recent buyerspurchase_completed performed at least 1 time within the last 30 days
High-value buyerspurchase_completed performed where amount greater than 100
At risklogged_in has not been performed within the last 30 days
Power usersfeature_used performed at least 10 times ever, and person plan equals pro

You can combine event conditions with person attributes using AND / OR groups. See Segments for group logic.


Using events in campaigns​

Campaigns can start when an event is received. Use this when you want to react immediately—abandoned carts, completed purchases, form submissions.

Create a campaign from an event​

  1. Open Events → your event
  2. Click Create a campaign from this event
    • Or go to Campaigns → Create campaign, then in the workflow click Start here → Event
  3. Select the event name
  4. Optionally add filters:
    • Event attribute — e.g. amount greater than 50
    • Person attribute — e.g. plan equals pro
    • Segment — e.g. person is in Newsletter subscribers
  5. Set frequency:
    • On every occurrence — enter the campaign every time the event fires
    • One time — enter only once, ever
    • Once within a period — at most once per hour/day/week/month, etc.
  6. Build the rest of the workflow (delay, email, push, in-app, branch)
  7. Set a goal if you want to measure success (often “perform an event”)

Personalise with event data​

Event-triggered messages can use both the person profile and the event that fired the campaign.

Hi {{ person.first_name }},
Your order {{ event.order_id }} for {{ event.amount }} {{ event.currency }} is confirmed.

Nested properties use dot notation. Arrays are zero-indexed:

{{ event.items.0.name }}
{{ event.address.city }}

Safe defaults (avoids blank output if a key is missing):

{{ event.amount | default: " " }}
{{ event.items | json }}
NamespaceAvailable inExample
{{ person.* }}Every message{{ person.first_name }}, {{ person.plan }}
{{ event.* }}Event-triggered messages only{{ event.order_id }}, {{ event.amount }}

The message editor’s event-attribute picker is filled from recent occurrences of that event type. If a property does not appear in the picker, send a test event that includes the key, wait a minute, then reopen the editor.

Campaign goals​

A campaign goal can be:

  • Perform an event — e.g. purchase_completed within 7 days of a delivery
  • Enter a segment
  • Leave a segment

Pick the conversion window (value + unit) and whether the clock starts when the message is sent, opened, or clicked.

Learn more in Create a campaign and Overview & setup.


Using events in broadcasts and transactionals​

Broadcast goals​

A broadcast is a one-time send. You can still define success as:

  • Performing a specific event
  • Entering a segment
  • Exiting a segment

Example: send a legal-update email, then measure success as terms_accepted.

Broadcasts are not triggered by events. They are sent by you, immediately or on a schedule. Events measure what happened afterwards.

Transactionals​

Transactional messages are sent by API when something happens in your product. They are not the same as tracked events, but they often go together:

  1. Track password_reset_requested
  2. Send a transactional email with the reset link
  3. Track password_reset_completed when they finish

Use events for analytics, segments, and campaign triggers. Use transactionals for the message that must go out immediately to one person.


Using events in journeys and analytics​

Events also power measurement:

  • Journey analysis — build funnels from event and segment steps to see where people drop off
  • Event analytics cards — in Journeys analytics, pin an event to a card and watch volume, unique people, and period-over-period change
  • Workspace dashboard — overall activity; see Workspace dashboard

To pin an event analytics card:

  1. Go to Journeys (analytics / event analytics)
  2. Create a card and choose the event type
  3. Open the card to see counts, unique people, and trends

Use analytics to decide which events are worth automating. A noisy event that fires on every page load is a poor campaign trigger; a clean checkout_started or trial_ended event is a strong one.


In-app messaging, push, and events​

Tracked events and messaging delivery are related but separate:

  • Tracked events (track) describe product behaviour
  • Push / in-app delivery metrics (delivered, opened, impression, click, dismiss) are recorded by the mobile SDKs when messages are shown

For in-app messages, the SDK syncs eligible messages for the current screen, then reports impression, click, and dismiss. Screen names must match what you configure in the in-app editor.

You can still use a tracked event as the campaign trigger that queues an in-app or push message—for example, track cart_abandoned, then show an in-app banner the next time the SDK syncs on the cart screen.

See:


Start with a small, stable set. Identify the person before or with the first event so history is not orphaned.

Event nameWhen to sendUseful properties
user_signed_upAccount createdsource, plan, method
user_logged_inSuccessful loginmethod
onboarding_step_completedA step in onboarding finishedstep, step_index
onboarding_completedOnboarding finishedduration_seconds
page_viewedMeaningful web page (not every navigation)page, referrer
checkout_startedCheckout beguncart_value, item_count
cart_abandonedCheckout started but not completedcart_value, item_count
purchase_completedPayment succeededorder_id, amount, currency, items
subscription_startedPaid plan startedplan, amount, interval
subscription_cancelledPlan cancelledplan, reason
feature_usedA key feature was usedfeature_name
support_ticket_createdCustomer contacted supportchannel, topic

Copy-paste purchase event (Node.js):

await client.track(user.id, 'purchase_completed', {
order_id: order.id,
amount: order.total,
currency: 'USD',
item_count: order.items.length,
items: order.items.map((item) => ({
product_id: item.id,
name: item.name,
price: item.price,
quantity: item.quantity,
})),
payment_method: order.paymentMethod,
});

Limits and behaviour​

TopicBehaviour
Payload sizeIdentify and track bodies must be ≤ 20 KB. Larger payloads are discarded (HTTP 200 with a discard message).
ProcessingTrack is asynchronous. Dashboard appearance can lag by a few seconds.
Event namesCase-sensitive. New name = new event type.
IdentifierString; required; must be consistent. Mobile: do not use an email.
screen_viewCatalogued for in-app targeting; not listed under Events; does not trigger campaigns.
Lifecycle names listed aboveAccepted with 200, then dropped. They never appear in Events.
PostHog identifiers containing posthogIgnored.
PropertiesJSON object. Nested objects/arrays allowed. Native types recommended.

Troubleshooting​

SymptomLikely causeFix
401 on curlMissing or wrong API keyCopy the key from Settings → Workspace → API keys. No Bearer prefix unless the key includes it.
400 identifier / eventNameEmpty or missing fieldSend non-empty strings.
HTTP 200 but event never appearsPayload over 20 KB, ignored lifecycle name, screen_view, or PostHog loop filterRead the response message. Shrink the body. Use a real product event name.
Event type appears, but Activities is emptyLookback window is 7 days and you are looking at older data, or you are on the wrong event nameWiden lookback. Search Events for the exact name.
Person exists, event does nottrack used a different identifier than identifyLog both values. They must be the identical string.
Person missing entirelyidentify never ran, or ran with a different key/workspaceIdentify first, same API key, then track.
Properties missing in the drawerYou did not send properties, or sent them as a stringSend a JSON object. Re-track and click the newest row.
Property missing in the campaign editorNo recent occurrence included that keySend a test event with the key, wait, reopen the editor.
Campaign did not enterFrequency is one-time and they already entered, or a filter did not matchCheck frequency and event-attribute filters. Confirm the event is on the person’s Activity tab.
Two Events rows for one actioniOS sends Purchase and the server sends purchase_completedStandardise the name in every SDK.
Mobile track does nothingSDK not initialised, or debug off so failures are silentInitialise before runApp / onCreate. Set debug: true.

Still stuck? Re-run the first curl test with a unique identifier (user_debug_001). If curl works and the SDK does not, the bug is in initialisation or the identifier you pass in app code.

More: Direct API troubleshooting, PostHog troubleshooting, Node.js error handling.


Best practices​

  1. Identify first, track second — events attach to people
  2. Use one identifier everywhere — the same ID for identify, track, register device, campaigns, and transactionals
  3. Keep event names stable and boring — purchase_completed ages better than bought_pro_plan_v3
  4. Put state on the profile, actions on the event — plan: "pro" belongs in identify; amount: 99.99 belongs in track
  5. Send native types — numbers, booleans, and ISO dates, not stringified values
  6. Track fewer, better events — a dozen meaningful actions beat hundreds of low-value clicks
  7. Do not send secrets — no passwords, tokens, card numbers, or government IDs
  8. Test with a known person — one test ID, from SDK logs → Events → person profile, before you automate
  9. Confirm properties in the drawer before you build a campaign that uses {{ event.amount }}

Product​

Integrations​


🎉 That’s it!
Send one identify and one track with curl, confirm they appear under Events and on the person’s Activity tab, then instrument the same calls in your app. After that, every new event name shows up automatically and is ready for segments, campaigns, and personalisation.