Audience: Client developers integrating Zixflow SDK (React Native)
Purpose: Track push notification lifecycle events — delivery, open, and action clicks — so Zixflow can measure campaign performance accurately. See also: Push Notifications and the reference implementation
src/pushHandlers.ts.
Why Push Tracking Matters
When Zixflow sends a push notification to a user’s device, it records that the notification was sent. But it cannot know on its own:- Did the notification actually arrive on the device?
- Did the user open it (tap the banner)?
- Did the user tap an action button (“Shop Now”, “Remind Me”)?
Two Ways to Handle a Zixflow Push — and Why Tracking Is Required in Both
Every push can be Native or Custom in the dashboard — that choice decides the payload shape (see The Push Payload).- Native: display content lives in FCM
notification/ APNsaps.alert;datahas tracking + routing keys (Zixflow-Delivery-ID,Zixflow-Delivery-Token,deeplink_url,action_buttons,template_id). - Custom: no
notificationblock — the full content is indata.*so your app renders with@notifee/react-native.
The one thing both paths share: tracking is never automatic.
- Path A (Native, app backgrounded/killed):
messaging().onMessageis not invoked for anotification-block push while backgrounded. TrackOpenedfromonNotificationOpenedApp/getInitialNotification. ForDeliveredon this path, rely on Zixflow’s server-side delivery receipt, or see Tracking Delivery When the Device Is Locked. - Path B (Custom): your code runs the moment the push arrives. Track
Deliveredimmediately, then trackOpened/Push Notification Action Clickedfrom notifee tap handlers.
Reference implementation: sdk-examples/react-native ships a runtime “Custom handling” toggle that switches live between path A and path B on the same running app.
A note on@react-native-firebase/messagingand Path A: once the FCM client library is present, it typically installs its ownFirebaseMessagingService. Verify the library’s fallback renderer before assuming Path A gives full fidelity for free (some dropnotification.image).
The Delivery Lifecycle at a Glance
The Push Payload
Zixflow injects two special fields into every push data payload.trackMetric() requires both.
The rest of the payload depends on the rendering mode chosen in the dashboard:
- Native (OS-rendered): display content lives in
notification/aps.alert;datacarries tracking + routing keys only. - Custom (app-rendered): there is no
notificationblock — the full content is indata.
data payload in Custom mode:
data payload in Native mode (tracking + routing only):
Important:Preferaction_buttonsis a JSON string (not a nested object). Parse it withJSON.parsebefore use.
message.notification?.title ?? data.title so a single renderer covers Native (foreground) and Custom.
Complete Key Reference
Demo-only sample-app conventions (not official schema):
data.priority, data.analytics_label, data.ttl_seconds.
Field Support Summary: Native (OS-Rendered) vs. Custom-Handled
Template-Based Custom Rendering (template_id)
Every push sent from a dashboard template includes data.template_id. Map known IDs to a dedicated renderer and fall back to the generic field-driven builder.
Complete Field Mapping Reference (Custom-Handled UI)
One consolidated builder covering every official field from the Complete Key Reference. Combine with the tracking calls from The Three Tracking Events. Matchessdk-examples/react-native.
The Three Tracking Events
Each interaction maps to a specific SDK call. The event name, parameters, and platform code are listed for each below.Event naming — read this first:trackMetric()used to always send a single generic internal event name,Report Delivery Event, for every metric type (delivered/opened/clicked/converted), with the actual status only distinguishable via an internalmetricproperty. The SDK now sends the metric name itself as the event name —Delivered,Opened,Clicked— so each lifecycle stage is directly filterable/reportable by name in analytics and Journeys, with no code change required on your side (you still calltrackMetric({ event: MetricEvent.delivered })exactly as before; only the resulting event name on the backend changed). If you have older dashboards/segments filtering on the literal string"Report Delivery Event", update them to filter on"Delivered"/"Opened"/"Clicked"instead. Both old and new names are still recognized by the backend, so nothing breaks during the transition — but new events will use the short-form names going forward.
1. Delivery Confirmed
Event name (sent to Zixflow):DeliveredWhen to fire: The moment the push data payload arrives on the device — inside your
onMessage handler.SDK method:
Zixflow.trackMetric({ deliveryID, deviceToken, event: MetricEvent.delivered })What happens: Zixflow updates the campaign delivery record to
delivered. No profile event is stored.
Parameters:
Tracking Delivery When the Device Is Locked
A common gap:Delivered isn’t recorded when the push arrives while the phone is locked or the app is backgrounded. This is expected default OS behaviour.
2. Notification Opened
Event name (sent to Zixflow):OpenedWhen to fire: When the user taps the notification banner. Fire for both body taps and action button taps.
SDK method:
Zixflow.trackMetric({ deliveryID, deviceToken, event: MetricEvent.opened })What happens: Zixflow updates the campaign delivery record to
opened. No profile event is stored.
Parameters:
3. Action Button Clicked
Event name (sent to Zixflow):Push Notification Action ClickedWhen to fire: When the user taps a named action button (“Shop Now”, “Try It Free”, etc.). Always fire
trackMetric(opened) first, then fire this event.SDK method:
Zixflow.track({ name: "Push Notification Action Clicked", properties: {...} })What happens: Zixflow records a
clicked delivery report and captures which button was tapped for campaign analytics.
This is a namedProperties:track()call — nottrackMetric(). There is noMetricEventenum for clicks.
Action Buttons Format
Theaction_buttons field is a JSON-encoded string of {name, deeplink} objects. Parse it before use. Buttons never render on the Native path — attach them when you display with notifee.
Payload value (raw string):
- Maximum 2 buttons per notification
deeplinkmay be an empty string — handle gracefully, don’t navigate to a blank URL- Button index is 0-based
- On iOS, button labels are pre-registered at app init — use
categoryId: 'ZX_2BTN'
Parsing action_buttons
Building the Buttons on the Notification (notifee)
notifee.onForegroundEvent / notifee.onBackgroundEvent’s detail.pressAction.id (e.g. "action_0") is parsed back into an index for tracking.
Handling the Tap (End-to-End)
Important on Android (React Native): pressing an action button does not automatically dismiss the notification — see Sticky Notifications below.
New Notification Layouts
Not Zixflow-defined. Add keys such asstyle / progress / timer yourself if you want them. Sample-app convention below. Android-only.
style — notification layout
progress — determinate / playback bar
timer — live countdown chronometer
Example payloads
Sticky Notifications
data.sticky is an official Custom-mode field. Apply notifee ongoing (or native FLAG_NO_CLEAR) in your builder — Native OS rendering does not honor sticky.
Sample-app convention: sticky accepts three values:
This is Android-only and only works on the Custom (app-rendered) path. Notifee has no dedicated prop for
FLAG_NO_CLEAR — use ongoing for "until_click" and accept the “Clear all” limitation for "until_swipe", or drop to native NotificationCompat for that mode.
@react-native-firebase/messaging + notifee is what actually displays the notification in your app code — sticky must be read and applied there.
Deep Link Handling
Every Zixflow push may carry adeeplink_url and per-button deeplinks in action_buttons. Your app is responsible for routing these.
Sample-app priority on tap:
- Action button → that button’s
deeplink - Body tap →
deeplink_url - Neither set → default screen
click_action: FCMandroid.notification.click_actionneeds a matching<intent-filter>. A separateclick_actionkey in customdatais optional — the sample app maps"OPEN_SALE"/"OPEN_DASHBOARD"ahead ofdeeplink_url.
Decision Flowchart
Fallback Behaviour (No Zixflow-Delivery-ID)
If the push notification was sent by a non-Zixflow source,Zixflow-Delivery-ID and Zixflow-Delivery-Token will be absent from the payload.
In this case, skip trackMetric(). Push notification event names (Delivered, Opened, Push Notification Action Clicked) are reserved for Zixflow’s delivery pipeline only when their properties actually carry a Zixflow delivery identifier — the backend checks for Zixflow-Delivery-ID before treating it as a delivery report. If you send a custom event named exactly Delivered, Opened, or Clicked with no delivery identifier, it is stored as a normal profile event.
When a Zixflow-originated push tracking call is correctly matched, its event is not stored as a user profile event.
If you want to track a non-Zixflow push for your own analytics, use a custom event name instead.