Skip to main content
Last Updated: July 2, 2026
Audience: Client developers integrating Zixflow SDK (Flutter)
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 push_handlers.dart.

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”)?
Your app reports these three moments back to Zixflow using the SDK. This powers the delivery analytics you see in campaign dashboards — open rates, click rates, and conversion funnels after a push.

The Delivery Lifecycle at a Glance

Each of these SDK calls results in a delivery report event flowing to the Zixflow backend, updating the campaign’s live metrics.

The Push Payload

Zixflow injects two special fields into every push notification’s data payload. Your app uses these to associate the tracking event with the correct campaign delivery. These appear inside message.data (Flutter/Android) or userInfo (iOS native). The SDK’s trackMetric() method requires both to route the event correctly. Example raw data payload received by the app:
Important: action_buttons is a JSON string (not a nested object). Parse it with json.decode() / JSONSerialization before use.

The Three Tracking Events

Each interaction maps to a specific SDK call. The event name, parameters, and platform code are listed for each below.

1. Delivery Confirmed

Event name (sent to Zixflow): Push Notification Delivered
When to fire: The moment the push data payload arrives on the device — inside your onMessage / onMessageReceived / willPresent handler.
SDK method: Zixflow.instance.trackMetric(deliveryID:, deviceToken:, event: MetricEvent.delivered)
What happens: Zixflow updates the campaign delivery record to delivered. No profile event is stored.
Parameters:

2. Notification Opened

Event name (sent to Zixflow): Push Notification Opened
When to fire: When the user taps the notification banner. Fire for both body taps and action button taps.
SDK method: Zixflow.instance.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 Clicked
When 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.instance.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 named track() call — not trackMetric(). There is no MetricEvent enum for clicks.
Properties:

Platform-Specific Integration

Flutter is the reference implementation. The pattern is the same for both platforms once FCM / APNs tokens are obtained.

Step 1 — Register Device Token

Step 2 — Track Delivery (Foreground)

When the app is in the foreground, FCM delivers the message to onMessage. Track delivery immediately.

Step 3 — Track Open (Background / Terminated)

Step 4 — Track Action Button Click (Local Notification Response)

For foreground-received notifications displayed as local notifications, handle taps via flutter_local_notifications:

Action Buttons Format

The action_buttons field in the push payload is a JSON-encoded string containing an array of button objects. You must parse it before use. Payload value (raw string):
Parsed structure:
Rules:
  • Maximum 2 buttons per notification (iOS system limit; Android supports more but keep it consistent)
  • deeplink may be an empty string — handle gracefully, don’t navigate to a blank URL
  • Button index is 0-based — button at index 0 is leftmost/first
  • On iOS, button labels are pre-registered at app init due to OS constraints. Only the deeplinks are dynamic from the payload. Use generic labels in the pre-registration (“Action 1”, “Action 2”) and rely on the action_name in the tracking event for analytics.
iOS pre-registration example (required):

Parsing action_buttons

Building the Buttons on the Notification (Foreground Path)

onDidReceiveNotificationResponse’s response.actionId (e.g. "ACTION_0") is what you parse back into an index for tracking.

Handling the Tap (End-to-End)

Important on Android (Flutter): pressing an action button does not automatically dismiss the notification the way tapping the notification body does — see Sticky Notifications below.

Sticky Notifications

Field: sticky (boolean, sent in the data.* payload as the string "true" / "false") “Sticky” means the notification survives passive dismissal — the user swiping it away, or hitting “Clear all” — the kind of behavior used for downloads-in-progress, active calls, or ongoing music playback. It does not mean the notification is permanently stuck: tapping the notification body or an action button always removes it, exactly like a normal notification. This is Android-only — there is no iOS/APNs equivalent (iOS notifications can always be swiped away, with no API to change that), so all iOS code paths below are a deliberate no-op for this field.
This requires two separate Android notification flags, not one — and a third piece of manual cleanup for action buttons.
  • setAutoCancel(true) must always be on, regardless of sticky — this is what makes tapping the notification body remove it.
  • setOngoing(true), driven by sticky, is what actually blocks swipe and “Clear all”.
  • Action buttons need an explicit cancel call in your action handler. Android does not auto-dismiss a notification when an action button is pressed.
Unlike native Android, Flutter’s Zixflow SDK does not intercept messages with its own background service — your app’s FirebaseMessaging.onMessage / onBackgroundMessage handler is what builds the local notification, so this flag must be read and applied in your own app code.
Every Zixflow push notification may carry a deeplink_url at the top level and per-button deeplinks in action_buttons. Your app is responsible for routing these to the right in-app screen, falling back to an external browser/app when the link isn’t one of your own screens. Priority order for navigation on tap:
  1. If an action button was tapped → use action_buttons[index].deeplink
  2. If body was tapped → use deeplink_url
  3. If neither is set → open app to default screen
Resolution order your routing code should follow, regardless of platform:
  1. Try to resolve the link to one of your app’s own screens (e.g. a custom yourapp:// scheme, or https://yourapp.com/... paths you own).
  2. If it doesn’t match anything you own, fall back to opening it externally (browser, another installed app, mailto:, etc.).
  3. If the URL is empty/missing, do nothing (or navigate to a default/home screen).

Decision Flowchart

Use this when deciding which SDK call to make for any notification interaction:

Fallback Behaviour (No Zixflow-Delivery-ID)

If the push notification was sent by a non-Zixflow source (e.g., a direct FCM API call, a test tool, or a third-party system), Zixflow-Delivery-ID and Zixflow-Delivery-Token will be absent from the payload. In this case, skip trackMetric(). Push notification event names (Push Notification Delivered, Push Notification Opened, Push Notification Action Clicked) are reserved for Zixflow’s delivery pipeline — they are not stored as user profile events. If you want to track a non-Zixflow push for your own analytics (e.g., to build a custom funnel), use a custom event name instead:

Background Isolate Taps

When the app was terminated and the user taps a local notification, the response may run in a background isolate. Re-initialize Zixflow before tracking:
Wire this as onDidReceiveBackgroundNotificationResponse when initializing FlutterLocalNotificationsPlugin.

Testing Push Tracking

In the dashboard: Messaging → Push Notifications → check Delivered, Opened, and Clicked (action) for your campaign.