FCM Delivery Monitoring: Know What Sent, Delivered, and Opened Actually Mean
A practical guide to Firebase Cloud Messaging observability that separates send acceptance, aggregated delivery, app processing, and user interaction instead of treating one success response as proof of delivery.
Table of Contents11 sections

A developer workstation where caches, SDKs, and build artifacts accumulate.
A successful Verifying Firebase Cloud Messaging Delivery Cloud Messaging send response does not prove that a user received a notification. It proves a much narrower event: FCM accepted the send request and returned a message name. Production monitoring becomes misleading when that acceptance is labeled delivered, then compared with notification opens as if nothing happened between those two states.
For the client-side delivery checklist, see verifying Firebase Cloud Messaging delivery before deciding which server-side signal belongs in your dashboard.
The useful model is an evidence chain:
business event → send attempt → FCM acceptance → transport outcome → app processing when observable → notification interaction
Each stage answers a different question. Some can be observed per message inside your own system. Some are available only as aggregated provider metrics. Some depend on the Android payload type and app state. Treating those signals as interchangeable is how teams end up with dashboards that look healthy while users still report missing Diagnosing Duplicate Firebase Notifications In.
This guide builds an operational model for FCM delivery monitoring that separates those signals, shows where client acknowledgements are useful, and explains what to log when a notification incident happens.
Start by Defining What Delivered Means
Before adding another dashboard, define the states your system is allowed to claim.
| State | Evidence | What it proves |
|---|---|---|
| created | Business event exists | Your product decided a notification should be sent |
| send_attempted | Backend dispatch log | Your sender tried to call FCM |
| accepted_by_fcm | Successful FCM send response | FCM accepted the request |
| rejected_by_fcm | FCM error response | The provider rejected that attempt |
| transport_delivered | FCM reporting/Data API signal | Provider-side delivery outcome, generally as aggregated evidence |
| app_processed | Your app acknowledgement or telemetry | App code actually handled the event on paths where you can observe it |
| opened | Notification interaction event | The user interacted with the notification |
The first important correction is naming. Do not store a successful projects.messages.send response as delivered. Call it accepted_by_fcm, provider_accepted, or another name that matches the evidence.
That small vocabulary change improves incident response immediately. If sends are accepted but downstream delivery falls, you investigate transport, device state, payload behavior, token health, or client handling. If FCM rejects sends, you investigate credentials, tokens, payloads, quotas, or sender logic instead.
FCM Already Provides Delivery Evidence, but Not the Same Evidence as Your App
Firebase provides several delivery-reporting surfaces. The Firebase console exposes messaging reports, and the FCM Data API provides aggregated Android delivery information such as accepted messages and outcome percentages. Firebase also supports messaging data export for deeper analysis.
These signals are valuable because they reveal provider and Android transport trends that your backend cannot infer from a send response alone. They can help answer questions such as whether accepted-message volume suddenly diverged from delivered outcomes, whether messages are being delayed rather than dropped, whether the problem is broad or limited to a segment represented by an Analytics label, and whether a release or messaging change correlated with a transport regression.
But provider reporting and application telemetry should not be collapsed into one field. Aggregated delivery data is excellent for fleet-level diagnosis. Your own event IDs are better for tracing a specific business event through systems you control. A practical dashboard therefore keeps provider health and application event lifecycle as related but separate views.
Why onMessageReceived Is Not a Universal Delivery Receipt
A common monitoring design says: When Android receives an FCM message, call our acknowledgement API from onMessageReceived(). That can be useful, but only if you understand which message paths actually invoke your code.
Android handling differs by payload type and app state. Notification messages received while the app is in the background can be handled by the system tray instead of passing through onMessageReceived(). Data messages give the application more direct handling control. Messages that combine notification and data fields also behave differently between foreground and background states.
That means an absent onMessageReceived() acknowledgement does not automatically mean FCM failed to deliver the notification. If your product requires application-level processing evidence for every relevant event, design the payload and handling strategy around that requirement rather than retrofitting a callback counter onto notification messages and calling it delivery rate.
For a deeper client-side delivery checklist, see verifying Firebase Cloud Messaging delivery.
Use Stable Business Event IDs, Not Only FCM Message Names
Provider message identifiers are useful for transport correlation, but your system should also have an identifier for the logical event being communicated.
Imagine an order-status event with ID order_4821_shipped. A queue worker times out, retries, and sends the same logical event twice. FCM can legitimately accept both requests and return different provider message names. From the provider perspective, those are two sends. From the product perspective, they are one event accidentally dispatched twice.
A useful notification record therefore separates business event IDs, notification identifiers, send attempts, provider message names, recipient references, and payload types.
Do not store sensitive notification payloads indiscriminately just because debugging is easier with raw data. Persist the minimum metadata needed for correlation, and apply your normal retention and access controls. This distinction also makes duplicate-notification incidents much easier to diagnose. If the same business event has two send attempts, investigate sender idempotency before changing Android rendering code. RayLabs guide to duplicate FCM notifications on Android covers the client and sender duplication paths in more detail.
Model Acknowledgements as Application Evidence
When app code does receive and process a message, an acknowledgement can provide stronger application-specific evidence than a provider send response.
For example, a data-message workflow may record received event IDs, app versions, device timestamps, processing results, and acknowledgement timestamps. The server should make that acknowledgement endpoint idempotent. Mobile connectivity is unreliable, so the client may retry an acknowledgement after the server already stored it. A duplicate acknowledgement should update or no-op safely rather than create a second lifecycle event.
Also decide whether the acknowledgement is operationally important enough to retry. An immediate HTTP request from every message handler adds network work and can itself fail. For critical application processing, you may persist a small local receipt and upload it with retry semantics. For non-critical analytics, provider reporting or batched telemetry may be sufficient.
The key is to label the signal correctly: an app acknowledgement proves your code processed an observable path. It is not a universal substitute for FCM delivery reporting.
Separate Delivery From Display and Engagement
Even transport delivery and app processing do not prove that the user saw the notification. A notification can reach the device while notification permission is denied, a channel is disabled, presentation logic suppresses it, or the application intentionally handles the event silently. Likewise, a displayed notification is not the same as an opened notification.
For production analysis, keep at least three conceptual layers separate:
- Delivery: Did the provider or device transport successfully handle the message?
- Presentation or processing: Did the operating system or application take the expected local action?
- Engagement: Did the user open or otherwise interact with the notification?
A low open rate with healthy delivery is a product or relevance question before it is an infrastructure incident. A sharp delivery decline with stable send acceptance is an operational signal. A healthy provider delivery trend with broken deep links is a client behavior problem.
Build the Dashboard Around Ratios That Have Clear Denominators
Notification percentages are easy to misread when the denominator changes silently. Useful metrics include:
| Metric | Example denominator | Diagnostic value |
|---|---|---|
| FCM rejection rate | send attempts | Sender/token/provider request health |
| Provider acceptance rate | send attempts | Whether requests are reaching FCM successfully |
| Transport outcome rate | accepted messages represented by provider reporting | Delivery trend at the provider/Android transport layer |
| App-processing acknowledgement rate | eligible messages expected to invoke app processing | Client processing health for that specific path |
| Open rate | delivered or displayed population, depending on analytics definition | User engagement |
| Duplicate business-event send rate | unique business events | Sender idempotency problems |
Never compare two percentages until you know whether they measure the same population. Provider reports can also be delayed or aggregated, so they should not be presented as a real-time per-message truth source when they are not one.
Treat Token Errors as Lifecycle Signals
FCM registration tokens are not permanent identities. A production sender needs to distinguish retryable failures from signals that a token should no longer be targeted. The monitoring system should therefore expose token-health outcomes alongside send outcomes. If invalid or unregistered targets spike after an app migration, reinstall pattern, environment mistake, or backend change, the problem may look like a delivery outage even though the provider is correctly rejecting stale destinations.
Do not hide these failures inside a generic send-failed counter. Preserve enough error classification to answer whether the problem is authentication, payload validation, token lifecycle, throttling, or a transient provider condition.
Use an Incident Decision Tree
When someone reports that push notifications are not arriving, investigate in evidence order rather than jumping directly into Android code:
- Was the business event created? If not, the notification system is not yet involved.
- Was a send attempt recorded? If not, inspect queueing, scheduling, or sender logic.
- Did FCM accept the request? If not, classify the provider error.
- What do delivery reports or FCM Data API trends show? Look for delayed or dropped transport outcomes.
- What payload type was sent? This determines which Android callbacks and system-rendered paths are possible.
- For app-handled paths, did client telemetry appear? Compare by app version and relevant device segment.
- Was the notification displayed or opened? Inspect permission, channel, rendering, deep-link, and engagement behavior separately.
This sequence turns vague complaints into a bounded investigation.
Verify the Monitoring System Before You Trust It
Observability code can fail too. Test the evidence pipeline deliberately before an incident occurs. Run scenarios covering valid foreground data messages, background notification messages, invalid or unregistered tokens, temporary device offline states, unavailable acknowledgement endpoints, duplicate queue executions, and user notification opens.
Running this matrix beforehand ensures your first production outage tests FCM rather than testing the dashboard built to diagnose FCM.
Practical Takeaway
Reliable FCM monitoring is not a single delivered column. It is a chain of evidence with explicit boundaries. Record why a notification exists, record each send attempt, treat a successful FCM response as provider acceptance rather than user delivery, use Firebase delivery reporting and the FCM Data API for provider trends, add client acknowledgements only where the Android message path can actually provide meaningful processing evidence, and track display and opens separately from transport.
The goal is not to manufacture perfect certainty from a distributed mobile system. The goal is to know which part of the path your evidence actually proves. Once those states are explicit, notification incidents stop being vague reports of FCM not working and become structured investigations with a clear next place to look.
Continue Exploring
You Might Also Like

Mastering List to String Conversion in Mobile Development
An in-depth guide on handling list to string conversion, managing Android lifecycles, and avoiding memory leaks during state transformation.

Android Date and Time: Model Instants, Local Dates, and Time Zones Correctly
Learn how to model and format date and time in Android with Kotlin by separating absolute instants, local calendar values, time zones, localization, and testable presentation logic.

Fix Duplicate FCM Notifications on Android
A practical Android debugging guide for duplicate Firebase Cloud Messaging notifications, covering notification vs data payloads, sender duplication, stable event IDs, and local idempotency.