This article is part of the weekly learning pathways we’re releasing leading up to Firebase Summit. See the full pathway and register for the summit here.
When you send a push notification to your users, things can go wrong in many places: the network might be down, the device might have performance issues or the send request might be invalid. And each platform has its own complexity so the challenges can keep adding up. All of this makes it difficult to know whether your notifications actually got delivered to users.
To get a better understanding of whether your notifications were delivered and where things might have gone wrong, you need one central place to collect all information and then run an analysis. This is where BigQuery can help. BigQuery, Google's solution for building data warehouses, gives you the ability to run queries and build custom dashboards that provide deep insights and actionable information.
With the Firebase Cloud Messaging Android SDK, you can log notification delivery data and export it to BigQuery. We recently expanded the same support for iOS and the web. In this article, we will walk you through the process of setting this up so you can better understand your delivery rate and identify ways to troubleshoot or improve it.
First, you need to enable BigQuery for your Firebase project. You can do this by navigating to the Firebase console > Project Settings > Integrations, and then click Link on the BigQuery card. It's worth noting that, by default, this integration uses the BigQuery sandbox. This lets you explore BigQuery capabilities at no cost, so you can evaluate if it fits your use case. After you link your Firebase project to BigQuery, Firebase will export your data to BigQuery. The initial propagation of data might take up to 48 hours to complete.
Once the data is successfully exported, you should be able to see a data table created under your project’s firebase_messaging tab in the BigQuery console.
data
firebase_messaging
To collect Notification Delivery data, you need to enable logging on the client side. In the next step, we're going to set up the iOS client and start logging some data to BigQuery.
With the Firebase Messaging iOS SDK 8.6.0 or higher, you can now enable notification delivery logging in your app that exports to BigQuery by calling a new API we recently added. You will need to log alert and background notifications separately (this is only required for Apple's platforms).
Alert notifications and Notification Service Extension
To log alert notifications delivery, you first need to add a Notification Service Extension to your project in Xcode. The Notification Service Extension lets you customize the content of notifications before they become visible to users. Apple’s developer documentation has more details about how to add a service extension to your project.
After adding the Notification Service Extension to your project, find the NotificationService didReceiveNotificationRequest:withContentHandler: method inside the service extension target. This is where you will add code to enable logging.
NotificationService didReceiveNotificationRequest:withContentHandler:
In Firebase 8.6.0, We have introduced a new method to the Firebase Messaging API: the delivery data export API FIRMessagingExtensionHelper exportDeliveryMetricsToBigQueryWithMessageInfo:. Logging is disabled by default, so you will need to explicitly enable it by calling this API inside the Notification Service Extension.
FIRMessagingExtensionHelper exportDeliveryMetricsToBigQueryWithMessageInfo:
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { self.contentHandler = contentHandler bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) if let bestAttemptContent = bestAttemptContent { // Modify the notification content here... bestAttemptContent.title = "\(bestAttemptContent.title) 👩🏻💻" // Log Delivery signals and export to BigQuery. Messaging.serviceExtension() .exportDeliveryMetricsToBigQuery(withMessageInfo: request.content.userInfo) // Add image, call this last to finish with the content handler. Messaging.serviceExtension() .populateNotificationContent(bestAttemptContent, withContentHandler: contentHandler) } }
Note that, if you use the Firebase Cloud Messaging SDK to add an image to your notification, you need to make sure to call the deliver data export API before calling Messaging.serviceExtension().populateNotificationContent(_:withContentHandler), as shown in the code snippet above.
Messaging.serviceExtension().populateNotificationContent(_:withContentHandler)
The Apple system only passes the notification to the service extension if the notification payload contains the key "mutable-content" :1, so make sure you specify this key if you use the HTTP v1 API. If you use the Firebase console to send notifications, "mutable-content" :1 will be set automatically.
"mutable-content" :1
Background notifications
Background notifications are hidden messages that allow your app to wake up and update data in the background. To enable delivery data export for these kinds of notifications, you will need to implement the application:didReceiveRemoteNotification:fetchCompletionHandler: method and call exportDeliveryMetricsToBigQueryWithMessageInfo: method as shown below:
application:didReceiveRemoteNotification:fetchCompletionHandler:
exportDeliveryMetricsToBigQueryWithMessageInfo:
// For background notifications, call the API inside the UIApplicationDelegate method: func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) { Messaging.extensionHelper().exportDeliveryMetricsToBigQuery(withMessageInfo:userInfo) }
Make sure to include "content-available":1 to specify it’s a background notification when you use the HTTP v1 API to send the notification.
"content-available":1
Notification Delivery Logging is newly available on the web for Firebase JS SDK versions 9.0.0 and newer. To enable delivery data export, all you need to do is to enable the flag on a service worker as shown below. This is handled similarly to Android.
// userConsent holds the decision of the user to give big query export consent. const userConsent = ...; const messaging = getMessagingInSw(app); experimentalSetDeliveryMetricsExportedToBigQueryEnabled(messaging, userConsent)
Notification Delivery Logging has been supported on Android since Firebase SDKs version 20.1.0 or higher. On Android, you can call the delivery data logging API to enable data export for both display and data messages like this:
FirebaseMessaging.getInstance().setDeliveryMetricsExportToBigQuery(true)
By default, Notification Delivery Logging is disabled on all three platforms, and will only be activated once you call the API to enable it for the particular platform your app is running on. You can also deactivate the BigQuery export any time by unlinking your project in the Firebase console.
Results on BigQuery
Once you have successfully logged some data you will see this data in the BigQuery console. Keep in mind that the data is regularly pushed from Firebase to BigQuery and the daily propagation might take some time (up to a couple of hours, depending on the amount of data) to complete.
BigQuery data also includes customized analytics labels that help you to create custom queries. Here’s a preview what type of data you can get from the iOS platform:
With the delivery data on BigQuery, you can now run custom queries or build a customized dashboard based on your needs. It provides detailed information about your campaign, including message identifier, message type, SDK platform and timestamp so you can get a lot of useful information helping you identify if the message was sent as expected. You can also segment the data based on your custom analytics labels. And if you are interested in getting additional aggregate information about message delivery on Android, check out this Medium post to find out how to use FCM Aggregation API for advanced data analysis.
View the full learning pathway on FCM message delivery for additional codelabs, videos and articles. And don’t forget to register for Firebase Summit and join us on November 10th to learn how Firebase can help you accelerate your app development, release with confidence and scale with ease!
Apps and games have evolved rapidly in recent years, and user expectations for high performing apps have increased right alongside them. Today’s users don’t just demand speed and performance — they reward it. A 2019 study found that retail sites saw 8% more conversions when they reduced their mobile site load times by one-tenth of a second. And travel sites boosted conversions by just over 10%.
Pinpointing an app’s performance issues can be challenging, especially when the culprit is slow network requests from your dependencies or even your own server.
This is where network analysis from Firebase Performance Monitoring can help. Firebase Performance Monitoring helps you understand your app’s performance from the user’s perspective in near real time. You can analyze the performance of each module of your app by monitoring response times, success rates, and payload sizes of your most critical network requests.
Let’s look at how we were able to spot performance pitfalls in BingoBlast - Firebase's very own demo app.
Out-of-the-box, Firebase Performance Monitoring measures each network request that is sent from your app. To surface the most important trends from the vast number of URLs, Firebase automatically aggregates data for similar network requests to representative URL patterns. Furthermore, this aggregation removes all PII (Personal Identifiable Information) such as home address, username, and password so that developers don't need to worry about leaking user information.
Firebase displays all URL patterns (including custom URL patterns) and their aggregated data in the Network requests subtab of the traces table, which is the lower half of the Performance dashboard.
BingoBlast Auto Aggregate Data.
By just integrating Firebase Performance Monitoring into your app, you can quickly see the slowest network requests your app is making and how that performance has changed over time.
For BingoBlast, our traces table shows that some network requests have substantial slowdowns in their response time over the past several days, signaling that there might already be issues needing our attention.
Although Firebase does a great job at automatically generating URL patterns, at times the specific pattern you're interested in might be hidden under an automated pattern. In those situations, you can create custom URL patterns to monitor specific URL patterns that Firebase isn't capturing with its derived automatic URL pattern matching.
Custom URL patterns let you specify the patterns that will take precedence over the automatic URL patterns. With custom URL patterns, you can:
A custom URL pattern consists of the hostname followed by path segments. Subdomains and path segments can be replaced with a * to represent matching with any string.
*
If a request's URL matches more than one custom URL pattern, Firebase Performance Monitoring maps the request to the most specific custom URL pattern based on left-to-right specificity. See the documentation for the full details and syntax available.
For example, suppose you configure two custom URL patterns:
example.com/books/* example.com/*/dog
A request to example.com/books/dog will match against example.com/books/* because book is more specific than *.
example.com/books/dog
example.com/books/*
book
To show how custom URL patterns can be helpful, let's look closer at the data from BingoBlast. Let's say that we're worried that an important configuration API request (api.redhotlabs.com/1.2/config.get) might be causing issues in BingoBlast. But, we're unable to find it in our list of automatic network URL patterns. In this case, Firebase Performance Monitoring has aggregated the configuration API request along with a few other API requests into the api.redhotlabs.com/1.2/* URL pattern.
api.redhotlabs.com/1.2/config.get
api.redhotlabs.com/1.2/*
To get better insight into any performance issues this API call might be causing, let's use a custom URL pattern for this specific request.
To do this, we just click the Create custom URL pattern button in the traces table, then enter api.redhotlabs.com/1.2/config.get into the dialog. After the new pattern is created, the traces table will start displaying the new custom URL pattern based on new data.
Creating a new custom URL pattern.
Since this is an important API request and we want to track our improvements to it over time, we can add metrics (like response time) for this new custom URL pattern to our metrics board at the top of the Performance dashboard page. These metrics cards are a great way to provide a quick overview of your most important metrics.
Pin your most important metrics to the top of your dashboard.
With api.redhotlabs.com/1.2/config.get extracted as its own URL pattern, it's easier to monitor for any unwanted changes in the performance of these requests. We can then take action, like removing the request from the app's critical path or recommending improvements for the backend implementation.
Newly added api.redhotlabs.com/1.2/config.get custom URL pattern.
On the other hand, we sometimes want to group related URLs into a single URL pattern. In BingoBlast, we have included a library that plays a short video. However, we noticed that our traces table is showing many separate URL patterns to different googlevideo.com subdomains.
googlevideo.com
Overly precise automated URL patterns for googlevideo.com subdomains.
Since we're more concerned about the overall performance of the video requests, as opposed to which specific subdomain they're from, we can create a custom URL pattern *.googlevideo.com/** to aggregate all these URLs into one pattern. This makes it easy to understand the performance for the video and, as a bonus, makes our traces table more tidy!
*.googlevideo.com/**
Newly aggregated data for the custom URL pattern for googlevideo.com subdomains.
Firebase Performance Monitoring provides a wealth of data on how your users experience your app. By leveraging Firebase’s out-of-the-box automatic URL patterns and tailoring your dashboard with custom URL patterns you’re most interested in, you can easily pinpoint slow performance areas in your apps and quickly boost your app’s responsiveness.
View the full learning pathway for additional codelabs, videos and articles on creating fast and stable apps. And don’t forget to register for Firebase Summit, happening November 10th to learn how Firebase can help you accelerate your app development, release with confidence, and scale with ease!
Earlier this year at Google I/O, we announced Firebase App Check, Firebase's new platform for protecting your Firebase APIs from abuse. Not only can App Check protect hosted APIs (such as Cloud Storage for Firebase, Firebase Realtime Database, and others), it can also be used to protect your own backend resources, whether they are run in a managed environment such as Cloud Run or hosted on your own infrastructure.
To prevent abuse, your public APIs should verify that the calling application is authorized to make requests, regardless of whether a user credential is present or not. Imagine you run a backend which provides the API for a free mobile app; your app might be funded with ads, so you should ensure that all requests originate from your app—and not someone else's app!
To protect your backend with App Check, your apps should send an App Check token with every request. Apps built with Firebase SDKs and with App Check functionalities properly configured will automatically obtain and refresh App Check tokens for you. They will also automatically send those tokens along with every request to supported Firebase services such as Cloud Storage for Firebase, Cloud Functions for Firebase, and Firebase Realtime Database. These services will also automatically verify those tokens for you.
On the other hand, if you run your services on your own infrastructure, you are responsible for making sure that:
In Node.js backends running in trusted environments, such as Cloud Run, Cloud Functions, or your own server, it is common practice to use middleware modules to integrate cross-cutting concerns like this. Here's a code snippet that defines an Express.js middleware layer that verifies the App Check token using the Firebase Admin SDK:
const express = require('express'); const firebaseAdmin = require('firebase-admin'); const app = express(); firebaseAdmin.initializeApp(); const appCheckVerification = async (req, res, next) => { const appCheckClaims = await verifyAppCheckToken(req.header('X-Firebase-AppCheck')); if (!appCheckClaims) { res.status(401); return next('Unauthorized'); } next(); }; const verifyAppCheckToken = async (appCheckToken) => { if (!appCheckToken) { return null; } try { return firebaseAdmin.appCheck().verifyToken(appCheckToken); } catch (err) { return null; } }; app.get('/yourApiEndpoint', [appCheckVerification], (req, res) => { // Handle request. });
For more details, check out our documentation.
App Check tokens are implemented as JSON Web Tokens (JWT) as specified by RFC 7519. This means they are signed JSON objects. To assert that an App Check token is legitimate, you must perform the following steps:
https://firebaseappcheck.googleapis.com/v1beta/jwks
RS256
JWT
The following example performs the necessary steps in Ruby using the jwt gem as a Rack middleware layer. Many languages have similar JSON Object Signing and Encryption (JOSE) libraries that you can use for this purpose.
require 'json' require 'jwt' require 'net/http' require 'uri' class AppCheckVerification def initialize(app, options = {}) @app = app @project_number = options[:project_number] end def call(env) app_id = verify(env['HTTP_X_FIREBASE_APPCHECK']) return [401, { 'Content-Type' => 'text/plain' }, ['Unauthenticated']] unless app_id env['firebase.app'] = app_id @app.call(env) end def verify(token) return unless token # 1. Obtain the Firebase App Check Public Keys # Note: It is not recommended to hard code these keys as they rotate, # but you should cache them for up to 6 hours. uri = URI('https://firebaseappcheck.googleapis.com/v1beta/jwks') jwks = JSON(Net::HTTP.get(uri)) # 2. Verify the signature on the App Check token payload, header = JWT.decode(token, nil, true, jwks: jwks, algorithms: 'RS256') # 3. Ensure the token's header uses the algorithm RS256 return unless header['alg'] == 'RS256' # 4. Ensure the token's header has type JWT return unless header['typ'] == 'JWT' # 5. Ensure the token is issued by App Check return unless payload['iss'] == "https://firebaseappcheck.googleapis.com/#{@project_number}" # 6. Ensure the token is not expired return unless payload['exp'] > Time.new.to_i # 7. Ensure the token's audience matches your project return unless payload['aud'].include? "projects/#{@project_number}" # 8. The token's subject will be the app ID, you may optionally filter against # an allow list payload['sub'] rescue end end class Application def call(env) [200, { 'Content-Type' => 'text/plain' }, ["Hello app #{env['firebase.app']}"]] end end use AppCheckVerification, project_number: 1234567890 run Application.new
If your application uses content delivery networks (CDNs) to cache content closer to your users, you can use App Check to filter out abusive traffic at the edge. Since the Firebase Admin SDK's App Check functionalities are currently only available in Node.js and not all CDN providers support the Node.js runtime, you may need to verify App Check tokens in another runtime supported by the CDN. For this use case, you can adapt the following example for CloudFlare workers:
import { JWK, JWS } from "node-jose"; // Specify your project number to ensure only your apps make requests to your CDN const PROJECT_NUMBER = 1234567890; addEventListener("fetch", event => { event.respondWith(handleRequest(event.request)) }); async function handleRequest(request) { const appCheckToken = request.headers.get('X-Firebase-AppCheck'); const appId = await verifyAppCheckToken(appCheckToken); if (!appId) { return new Response("Unauthorized", { status: 401 }); } return new Response(`Hello app ${appId}`, { headers: { "content-type": "text/plain" } }); } async function verifyAppCheckToken(encodedToken) { if (!encodedToken) { return null; } // 1. Obtain the Firebase App Check Public Keys // Note: It is not recommended to hard code these keys as they rotate, // but you should cache them for up to 6 hours. const jwks = await fetch("https://firebaseappcheck.googleapis.com/v1beta/jwks", { headers: { "content-type": "application/json;charset=UTF-8", } }); // 2. Verify the signature on the App Check token const keystore = await JWK.asKeyStore(await jwks.json()); const token = await JWS.createVerify(keystore).verify(encodedToken); // 3. Ensure the token's header uses the algorithm RS256 if (token.header["alg"] !== "RS256") { return null; } // 4. Ensure the token's header has type JWT if (token.header["typ"] !== "JWT") { return null; } const payload = JSON.parse(token.payload.toString()); // 5. Ensure the token is issued by App Check if (payload["iss"] !== `https://firebaseappcheck.googleapis.com/${PROJECT_NUMBER}`) { return null; } // 6. Ensure the token is not expired if (Date.now() > payload["exp"] * 1000) { return null; } // 7. Ensure the token's audience matches your project if (!payload["aud"].includes(`projects/${PROJECT_NUMBER}`)) { return null; } // 8. The token's subject will be the app ID, you may optionally filter against // an allow list return payload["sub"]; }
Apigee is Google Cloud's comprehensive API management platform for your APIs. In Apigee, you can easily implement a policy for your API Proxy that checks for the presence and validity of Firebase App Check tokens for all your incoming requests.
In the following example, we will check for the presence of the Firebase App Check token in the request header X-Firebase-AppCheck, ensure that it is valid, and verify that it was issued by the correct project.
X-Firebase-AppCheck
First, in your API Proxy, add a Verify JWT policy; you can enter any Display Name.
Similar to the examples we have seen so far, you will need to perform all of the following steps in this policy:
request.headers.X-Firebase-AppCheck
<Source>
<Algorithm>
<Audience>
projects/{project_number}
{project_number}
<Issuer>
https://firebaseappcheck.googleapis.com/{project_number}
<Subject>
Following these steps, your configuration should look like the following:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <VerifyJWT continueOnError="false" enabled="true" name="Firebase-App-Check-Token-Verification"> <DisplayName>Firebase App Check Token Verification</DisplayName> <Algorithm>RS256</Algorithm> <Source>request.headers.X-Firebase-AppCheck</Source> <PublicKey> <JWKS uri="https://firebaseappcheck.googleapis.com/v1beta/jwks"/> </PublicKey> <!-- Be sure to use your real project number in <Issuer> and <Audience>. --> <Issuer>https://firebaseappcheck.googleapis.com/123456789</Issuer> <Audience>projects/123456789</Audience> <!-- You can also optionally check that the Subject matches your app's App Id. --> <Subject><!-- Insert your app's App ID here. --></Subject> </VerifyJWT>
Finally, add this policy to your Proxy Endpoint's pre-flow, and save this configuration as a new revision. Once you re-deploy the proxy at this revision, any request that arrives at the proxy must have a valid Firebase App Check token in the X-Firebase-AppCheck header, or the request will be rejected.
Securing your app and your resources is critical. Using Firebase Authentication and Firebase Security Rules helps protect access to user data, and using Firebase App Check helps mitigate fraud and secure access to your backend resources—whether those are Firebase resources or your own. View the full learning pathway on protecting your app from abuse for additional resources.
And don’t forget to register for Firebase Summit and join us on November 10th to learn how Firebase can help you accelerate your app development, release with confidence and scale with ease!
The journey of building and releasing an app can sometimes feel like scaling a mountain – both can be filled with hurdles and require a lot of hard work. At Firebase, our goal is to make this journey easier by providing you with tools and resources you need to build and grow apps users love.
That is why every year, we bring the community together at Firebase Summit to share exciting new product updates, answer your burning questions, and provide hands-on training so you can get the most out of our platform. This year won’t be any different, and we are excited to announce that Firebase Summit will be returning as a virtual event on November 10th, 2021 at 9:30am PST. We also have a few exciting activities leading up to Firebase Summit, so read on for what to expect.
Hands-on Learning Experiences
To help you get ready for Firebase Summit, and deepen your knowledge about our products and services, we’ll release a new set of learning pathways every week leading up to the main event. These learning pathways will consist of easy-to-follow codelabs, articles, interactive videos, and quizzes. After completing a pathway, you’ll also have the opportunity to earn a shiny new Google developer badge. The first weekly pathway will launch on October 13th, so check out the event website for more details.
Community Talks
One of the best ways to learn about Firebase is from other developers. On November 3rd, we’ll host community talks where you will get the opportunity to hear from Google Developer Experts on topics like how to build enterprise scale apps with Firebase, implement authorization models, and so much more. Mark your calendars and visit the event website where we’ll highlight these sessions.
Live Keynote, On-demand Technical Sessions, and More
The pathways and community talks lead up to Firebase Summit on November 10th, which will kick off with a jam-packed keynote at 9:30 am PST. Join us to learn how Firebase can help you accelerate app development, run your app with confidence, and scale your business with ease. After the keynote, you can ask questions in the chat and have them answered live by the Firebase team during #AskFirebase. We will also have new on-demand technical sessions on our latest announcements, and demos that you can view at your convenience.
We’ll be sharing more details about Firebase Summit 2021 in the coming weeks so stay tuned. In the meantime, register for the event, subscribe to the Firebase YouTube channel, and follow us on Twitter to join the conversation. #FirebaseSummit
As an app developer, you probably send hundreds of app notifications to users nudging them to try a new game level, complete a purchase, read a new article, or so on. But have you ever wondered what impact those notifications are having on user behavior? Are they actually improving the metrics you care about?
Firebase Cloud Messaging enables you to deliver and receive messages and notifications on Android, iOS, and the web at no cost. Measuring the impact of these notifications is an important but somewhat difficult task to accomplish. Cloud Messaging has always used Google Analytics to count sent notifications, and now, there’s a new way to see other user events related to an open notification across sessions or a longer period of time. Using these events will not only provide more information about the notifications you’re sending to users, they will also enable you to better measure the impact of sending notifications to users.
With analytics labels in Cloud Messaging, you have the ability to pick and apply analytics labels to messages, which Google Analytics can use to track all events related to your notifications beyond just counting sent ones. What is an analytics label? The label is a text string that you can add to any message. For example, let’s say you wanted to see all of the opened notifications for users who signed up in January. You can now do that by attaching a “january” label to your notifications when sending.
You can attach a label for any notification sent via the Firebase Cloud Messaging API as well as for a messaging campaign in Firebase Console. When you set up a Cloud Messaging campaign in the Notifications composer, you can use the dropdown menu in step 4 for Conversion events to choose an analytics label.
This will attach an analytics label to all messages sent as part of this campaign.
After some time has passed, you can get insight into the impact of those messages. Cloud Messaging in Firebase Console has a Reports tab with a graph showing funnel analysis - the number of sends, received, impressions, and opened notifications for a given timeframe. Filtering using an analytics label shows the information for just that label.
Aside from being able to see the impact of labels in Reports, you can use them to perform other specific Google Analytics analyses. For example, perhaps you want to compare how your game play time during winter holidays performed against summertime; you can use comparisons to evaluate metrics like engagement time for those two different messaging campaigns. You can also use the cohort exploration feature to compare user activity, and in fact, you can use the labels as event parameters on the messages for any Google Analytics analyses!
Viewing User Engagement card with label comparisons applied.
Cohort analysis using labeled notification campaign segments.
So there we have it! Using analytics labels in Cloud Messaging, we are able to leverage the full power of Google Analytics to measure the impact of messages sent via the API and Firebase Console!