FlowPilot already tracks flow analytics for you and sends them to the FlowPilot backend, which powers placement analytics and experiment results. The analytics callback is an extra tap on that same stream: it hands you every event the SDK records so you can forward selected events into Amplitude, Mixpanel, Segment, or your own pipeline.
The callback is additive. It does not replace FlowPilot’s own ingestion. Both happen for every event.
Set the callback
public func setAnalyticsCallback(_ callback: @escaping AnalyticsCallback)
// AnalyticsCallback = (AnalyticsEvent) -> Void
Set it once at startup, right after FlowPilot.configure(...):
FlowPilot.shared?.setAnalyticsCallback { event in
print("FlowPilot event:", event.eventName)
}
The callback is captured into each flow session when the session is created. Set it before you present or create a session, otherwise events from that session will not reach your callback.
The event
Each call gives you an AnalyticsEvent. The useful fields:
| Field | Type | Notes |
|---|
eventName | String | The event type, for example flow_complete. (Encoded on the wire as event_type.) |
flowId | String | The flow that produced the event. |
flowVersionId | String | The exact published version. |
placementId | String? | The placement that resolved the flow. |
experimentId | String? | Set when the user is in an experiment. |
variantId / variantName | String? | The assigned variant, when in an experiment. |
screenId / screenName | String? | The current screen, for screen and interaction events. |
screenIndex | Int? | 0-indexed screen position. |
elementId / elementType / interactionType | String? | Set for element_interaction events. |
revenue / currency | Double? / String? | Set for conversion events. |
properties | [String: AnyCodable]? | Custom properties (for example product_id on a conversion). |
userId / sessionId | String | Stable user id and the current session. |
timestamp | Date | When the event fired. |
Most fields are optional and only present for the event types that carry them. For the complete field list and per-event meaning, see Event taxonomy.
Event names
The SDK fires these automatic event names (from AutomaticEventType):
| Event name | When |
|---|
flow_start | A flow begins. |
screen_view | A screen appears. |
screen_exit | A screen is left (carries time on screen). |
flow_complete | The flow completes (a closeFlow from completion). |
flow_exit | The flow is dismissed before completing. |
experiment_exposure | The user is assigned to an experiment variant. |
element_interaction | The user drives an action chain (tap, toggle, and so on). |
Two more event names appear in the stream but are not part of AutomaticEventType:
conversion is emitted when you call trackConversion. It carries revenue, currency, and any properties you pass, and it reaches your callback like any other event.
resolve_no_flow is a backend-side event recorded when a placement resolves to no flow. The iOS SDK does not emit it, so do not expect it in this callback.
These names match the backend’s event taxonomy exactly, so the names you forward line up with what FlowPilot reports. The Swift eventName property maps to the event_type field on the wire.
Example: forward to your analytics
Forward completions and conversions, with flow and experiment context attached:
FlowPilot.shared?.setAnalyticsCallback { event in
switch event.eventName {
case "flow_complete":
Analytics.track("Onboarding Completed", properties: [
"flow_id": event.flowId,
"variant": event.variantName ?? "control"
])
case "conversion":
Analytics.track("Purchase", properties: [
"revenue": event.revenue ?? 0,
"currency": event.currency ?? "USD",
"flow_id": event.flowId,
"experiment_id": event.experimentId ?? ""
])
default:
break // ignore the rest
}
}
Notes
- It runs in-process. The callback is invoked synchronously while the SDK records the event. Keep it fast and non-blocking. If you need to do heavy work, hand the event to your own queue and return.
- It is additive, not a replacement. FlowPilot still sends every event to its backend. Your callback is an extra copy for your own tools.
- Most fields are optional. Guard against
nil. A field like revenue is only set on conversion events; screenId is only set on screen-scoped events.
Common mistakes
- Setting the callback after presenting. The session captures the callback at creation time. Set it before
presentPlacement or createSession, ideally right after configure.
- Assuming it replaces FlowPilot ingestion. It does not. FlowPilot tracks independently. If you only wanted to disable FlowPilot’s own tracking, that is a separate concern, not this callback.
- Double-counting purchases. If you call
trackConversion and also forward the conversion event to a provider that already tracks the purchase elsewhere, you can count it twice. Pick one source of truth per metric.
- Heavy work in the callback. Logging, disk writes, or network calls inline will slow the flow. Offload them.
Related pages