Custom Events
Generates: Events (Log Records)
Track business-level interactions, feature usage, and user milestones with named events and arbitrary attributes.
Already using an analytics service?
If you're using Mixpanel, Amplitude, Firebase Analytics, Segment, or similar — you can bridge those calls to Pulse.shared.trackEvent(...) alongside your existing tracking. This means zero additional instrumentation effort: every business event you already track automatically flows into Pulse too.
These events are the raw signal that Pulse uses to derive Interactions — user journey flows that are automatically composed, scored with Apdex, and surfaced in the dashboard to show what matters to your users.
Basic Usage
Pulse.shared.trackEvent(
name: "purchase_completed",
observedTimeStampInMs: Int64(Date().timeIntervalSince1970 * 1000),
params: [
"item.id": "sku_123",
"item.name": "Premium Plan",
"order.total": 49.99,
"currency": "USD",
]
)
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
name | String | Yes | Event name. Use snake_case or dot.notation consistently across your app |
observedTimeStampInMs | Int64 | Yes | Event timestamp in epoch milliseconds |
params | [String: Any?] | No | Arbitrary attributes (string, number, bool, or arrays of each) |
Timestamp Helper
let now = Int64(Date().timeIntervalSince1970 * 1000)
Common Event Patterns
Feature Usage
Pulse.shared.trackEvent(
name: "feature.used",
observedTimeStampInMs: Int64(Date().timeIntervalSince1970 * 1000),
params: [
"feature.name": "dark_mode",
"feature.enabled": true,
]
)
Onboarding Steps
Pulse.shared.trackEvent(
name: "onboarding.step_completed",
observedTimeStampInMs: Int64(Date().timeIntervalSince1970 * 1000),
params: [
"step.index": 2,
"step.name": "profile_setup",
]
)
Search
Pulse.shared.trackEvent(
name: "search.performed",
observedTimeStampInMs: Int64(Date().timeIntervalSince1970 * 1000),
params: [
"search.query": query,
"search.result_count": results.count,
"search.filter": activeFilter,
]
)
E-commerce
// Add to cart
Pulse.shared.trackEvent(
name: "cart.item_added",
observedTimeStampInMs: Int64(Date().timeIntervalSince1970 * 1000),
params: [
"item.id": product.id,
"item.name": product.name,
"item.price": product.price,
"item.category": product.category,
]
)
// Checkout completed
Pulse.shared.trackEvent(
name: "checkout.completed",
observedTimeStampInMs: Int64(Date().timeIntervalSince1970 * 1000),
params: [
"order.id": orderId,
"order.total": total,
"order.currency": "USD",
"payment.method": "apple_pay",
]
)
Generated Telemetry
Type: Log Record (Event)
Body: Event name
pulse.type: custom_event
Attributes
| Attribute | Description | Always Present |
|---|---|---|
pulse.type | Always "custom_event" | ✅ Yes |
event.name | The event name you provided | ✅ Yes |
session.id | Current session identifier | ✅ Yes |
screen.name | Active UIViewController | ⚠️ If available |
| (your params) | All key/value pairs in params | ⚠️ As provided |
Sample Payload
{
"body": "purchase_completed",
"attributes": {
"pulse.type": "custom_event",
"event.name": "purchase_completed",
"item.id": "sku_123",
"item.name": "Premium Plan",
"order.total": 49.99,
"currency": "USD",
"session.id": "f40364c92b85ec0c19c35a65be42b97f",
"screen.name": "CheckoutViewController"
}
}
Best Practices
- Use consistent naming: Pick a convention (
snake_caseordot.notation) and apply it everywhere. Event names are case-sensitive in the Pulse dashboard. - Keep names semantic:
purchase_completedis better thanbutton_tapped. Name the business action, not the UI interaction. - Avoid high-cardinality keys: Don't put raw user-generated text (search queries, user names) directly into attribute keys — use values instead.
- Avoid
nullvalues: Omit keys whose values are not known rather than setting them tonil.