A recent update to the Firebase SDK included the following information:
The Google Analytics SDKs for Android and for iOS now support integration with server-side tag manager. Review the implementation steps in the guide to server-side tagging for mobile apps.
This was a very welcome addition to server-side Google Tag Manager. For long, we’ve been waiting for app contexts to catch up with the web when it comes to taking control of the data flows using server-side tagging.
In this article, I’ll show you how to set everything up. I’m using an iOS app as the example.
In any case, I do recommend you check out the official implementation guide – it’s comprehensive and has instructions for an Android installation, too.
Prerequisites
This article assumes the following:
- You have an iOS app and you have access to its codebase (d’oh).
- That app is running the latest version of the Firebase SDK and the Firebase Analytics SDK.
- You have connected the app to Google Analytics 4 for analytics measurement purposes.
- You have a server-side Google Tag Manager container up and running.
The changes you need to make in order to stream your app data to server-side Google Tag Manager aren’t too complicated.
However, you do need to edit a few different places, so it’s important to make sure you have all the required bits and pieces available, and it’s important to follow this guide meticulously.
Edit the app Info.plist
The Info.plist file is usually stored in your project root. You can edit it with a text editor, or you can use Xcode to make the required changes.
Above, circled in red, are the new items you need to add to Info.plist. Make sure you get the bundle identifiers and types configured correctly.
Update URL identifier so that it has your app’s bundle identifier as its value. If you don’t know the app’s bundle ID, the easiest way to find it is to go to Google Analytics 4 and open the data stream settings for your app property.
Edit the first item under URL Schemes, too. Its value should be tagmanager.sgtm.c.<bundle ID>
. So in my example app, it should be:
tagmanager.sgtm.c.simo.iOSTest
If you want to edit the Info.plist file in your favorite code editor, the new information is added to the top <dict>
element of the file:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UIApplicationSceneManifest</key>
...
<!-- THESE ARE THE NEW ENTRIES YOU NEED TO ADD -->
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>None</string>
<key>CFBundleURLName</key>
<string>simo.<BUNDLE ID></string>
<key>CFBundleURLSchemes</key>
<array>
<string>tagmanager.sgtm.c.<BUNDLE ID></string>
</array>
</dict>
</array>
<key>GOOGLE_ANALYTICS_SGTM_UPLOAD_ENABLED</key>
<true/>
</dict>
</plist>
In the code above, make sure you update the two <BUNDLE ID>
references to your app’s correct bundle identifier.
Enable server-side Google Tag Manager preview mode
Previewing app requests in server-side Google Tag Manager is a bit different from the web, again.
Instead of visiting a URL so that preview cookies are dropped, you need to scan a QR code that opens the app on your device with the information required for preview mode to work.
To allow the app to be opened with the QR code link, you need to add some code to a lifecycle method. I’m using UIScene
in my app, so I’m adding the code to SceneDelegate.swift like this. Adjust your app code as necessary:
import UIKit
import FirebaseAnalytics
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
...
// THIS IS THE NEW CODE:
if let urlContext = connectionOptions.urlContexts.first {
let url = urlContext.url
Analytics.handleOpen(url)
}
...
To actually preview the app (once everything is up and running), you need to go to Preview mode in server-side Google Tag Manager, click the little overflow menu in the top right corner, and select Send requests from an app.
Once you click that, you need to add your app’s bundle identifier again, and then scan the QR code with your device that has the app. This should open a browser, where a pop-up should appear asking you to open the app in question. Once you open the app through this prompt, any requests to your GA4 property will be dispatched to server-side GTM Preview mode.
Just note that you still need to complete the rest of the guide steps before Preview mode works properly!
Just one more thing to do before you can test things.
Enable rerouting to server-side GTM in your GA4 property settings
This final step is, in my view, the biggest nuisance. For server-side tagging to work, you must have a functioning GA4 property.
This means that it’s impossible to do a completely isolated setup, where you only use the Firebase Analytics SDK as the dispatch mechanism. You must have GA4 enabled, because that’s where the Firebase Analytics SDK gets its configuration signals from.
In the GA4 property you’ve linked to your app, browse to Admin -> Data collection and modification -> Data streams. Here, select the data stream for the app you’re configuring.
In the data stream Settings, click Configure SDK settings, and here click Configure server-side Tag Manager.
In the next screen, toggle the Send data to a server-side Tag Manager container, and then fill in your Server container URL.
You can then choose whether to reroute app measurement data for Debug devices only, or for a percentage of traffic (25%, 50%, 75%, or 100%).
Needless to say, start with debug devices, and then ramp up slowly once you’ve verified that everything works.
Once you make this change, the Firebase Analytics SDK will receive the new configuration from GA4, and your app should start sending data to your server-side tagging endpoint.
It might take some time for the changes to propagate! If you don’t see any data flowing to server-side Google Tag Manager, be patient. If you’re using Xcode, you can try cleaning the build folder and then rebuilding the app.
Add a GA4 (App) Client in server-side Google Tag Manager
One more thing you need to do is add a Client in server-side Google Tag Manager for GA4 (App) requests. There’s a built-in Client for that purpose, and it doesn’t require any configuration.
Save the Client once you’ve created it.
Test that it works
Now, you can open your app by scanning the QR code found in the server-side Google Tag Manager Preview mode (see above for instructions).
If all works as intended, you should start seeing app-measurement
requests in Preview mode when your app dispatches Firebase Analytics events.
The GA4 (App) Client automatically handles them and parses them for the constituent events.
If you’re using just Xcode to test your app, you should first enable Firebase logging in your product scheme:
After that, you can run the app and look for references to your server-side tagging endpoint:
You can also look for corresponding request rows in your server-side tagging logs (such as Logs Explorer if you’re using the Google Cloud Platform).
If you don’t see any requests in server-side GTM Preview mode (or logs), and if you don’t see references to your server-side endpoint in Xcode logs, chances are that the change hasn’t propagated yet to your Firebase SDK configuration, so be patient and try in an hour or so again.
It could also be that you messed something up in the implementation, so go over the steps of this blog again to verify it works.
What is actually sent to SGTM
So now let’s get to the interesting part. There are those /app-measurement
requests, but what do they actually comprise?
Well, the request is a POST request with the parameters sent in the POST body as a serialized protobuf. If you’re using Preview mode, it handily deserializes the body:
The body contains a lot of metadata about the request, the device, and the app. It also contains a bundle of events dispatched from the app. These events are parsed by the Client into event data objects, and by selecting the events in Preview mode, you can jump into the Event Data tab to analyze what information is available to your tags.
For example, here’s what the Event Data object for a regular screen_view event contains:
Name | Value |
app_id | “simo.iOSTest” |
app_version | “1.0” |
debug | 1 |
event_name | “screen_view” |
event_origin | “auto” |
firebase_id | “dVx1jRJTpUF9vaDeNMcX4E” |
ip_override | “83.245.131.16” |
language | “en-us” |
realtime | 1 |
screen_class | “ViewController” |
screen_id | -5154847429042976000 |
x-ga-app_instance_id | “6B335AC6C28A40FB973E0AFE7C263E8F” |
x-ga-app_store | “manual_install” |
x-ga-app_version_major | 1 |
x-ga-bundle_sequential_index | 410 |
x-ga-consent_diagnostics | “19911” |
x-ga-device_model | “iPhone17,1” |
x-ga-dma | true |
x-ga-dma_cps | “ahmopsy” |
x-ga-gcs | “G1–“ |
x-ga-gmp_app_id | “1:169715865774:ios:d6fff60d58876bcf52f53f” |
x-ga-mp2-user_properties | { first_open_after_install: 1, first_open_time: 1729580400000, x-ga-npa: 1, session_id: 1729594430, session_number: 2, lifetime_user_engagement: 1106914, session_user_engagement: 337102 } |
x-ga-os_version | “18.0.1” |
x-ga-platform | “ios” |
x-ga-previous_timestamp_millis | 1729597698174 |
x-ga-protocol_version | 1 |
x-ga-redacted_fields | [“google_signals”] |
x-ga-system_properties | { config_version: 1729578240626990, delivery_index: 296, end_timestamp_millis: 1729597798448, experiment_ids: [42820019], gmp_version: 110400, previous_bundle_end_timestamp_millis: 1729597778336, previous_bundle_start_timestamp_millis: 1729597778336, start_timestamp_millis: 1729597798448, upload_timestamp_millis: 1729597798513, uploading_gmp_version: 110400 } |
x-ga-time_zone_offset_minutes | 180 |
x-ga-timestamp_millis | 1729597798448 |
x-ga-vendor_device_id | “93B6B18F-E544-426C-AE5E-966016A078EA” |
x-sst-system_properties | { gcsub: “region1”, user_attributes: { first_open_after_install: {type: 2, set_timestamp_millis: 1729579520757}, first_open_time: {type: 2, set_timestamp_millis: 1729579520757}, x-ga-npa: {type: 2, set_timestamp_millis: 1729579536066}, session_id: {type: 2, set_timestamp_millis: 1729594430024}, session_number: {type: 2, set_timestamp_millis: 1729594431129}, lifetime_user_engagement: {type: 2, set_timestamp_millis: 1729597778568}, session_user_engagement: {type: 2, set_timestamp_millis: 1729597778569} }, type_map: { screen_id: 2, screen_class: 1, realtime: 2, debug: 2, event_origin: 1 } } |
This is fascinating stuff! Firebase Analytics has always been something of a black box, and trying to decipher what is dispatched with those requests has required the installation of special tools and proxies to get to the bottom of it.
With server-side GTM rerouting, you still need to do some additional configuration, but at least this time you can actually utilize the debug streams efficiently.
What next?
You’re now collecting Firebase measurement requests to server-side Google Tag Manager. Great!
One obvious thing you’ll need to do is create a Google Analytics 4 tag for those Firebase Analytics events. Assuming you want to collect the data to GA4 – but nothing’s forcing you to do this! You could just as well use Firebase Analytics as the incoming stream and then dispatch the data to some completely different location, bypassing Google Analytics 4 entirely!
The Google Analytics 4 tag in SGTM is the same one you would use for web hits. The tag automatically takes the app-measurement
payload and converts it to GA4 format.
It parses the event data object before compiling the outgoing protobuf payload – the cool thing is that this means you can use Transformations to modify what the GA4 tag actually “sees”.
For example, in this request, I’ve used an Augment Event Transformation to change the event name to test-event and to add a new test-param event parameter:
You can also see how the request is not dispatched to the “regular” GA4 collection endpoint /collect
, but instead it’s sent to the app-measurement.com
endpoint the app would normally communicate with.
This is why the request body is serialized back into protobuf format.
It’s a bit of a shame that GA4 is built like this, with totally distinct app and web streams. They could have built a consolidated endpoint that collects data from either source. This separated approach means that collection is even more of a black box, especially considering how much metadata is sent to GA4 servers with app requests.
So what about Facebook Conversions API? What about Piwik PRO? What about Amplitude? These are solutions that have dedicated templates built for GA4 events.
Well, they definitely won’t work as smoothly out of the box with Firebase measurement data.
You’ll need to do a lot more enriching in the app code to make sure that the tags get all the data they need. After all, many of the solutions rely on first-party cookies (not available in apps) or data generated by Google Tag (not available in apps).
Summary
I sincerely hope that there will be more blog posts and guides released by the wonderful measurement community for working with Firebase measurement data in server-side Google Tag Manager.
For example, the Facebook Conversions API is a hot topic, and for it to work with app measurement data, a Campaign ID parameter needs to be collected as an additional parameter at the very least. This ID links the app session with the deep link or app-to-app click from the Facebook app.
Now, I think there are many good things about the Firebase SDK setup. It’s wonderful we finally have transparency to what the Firebase measurement stream actually looks like. Similarly, seeing how that stream is compiled into an Event Data object by the GA4 (App) Client in server-side Google Tag Manager speaks volumes.
This is a great opportunity for reverse engineers to better understand how the mythical world of Firebase app measurement works!
My biggest gripe, and it does grind my gears, is that you still need a GA4 property to configure the dispatch with. This is very different from web measurement, where you could just use a dummy Measurement ID to dispatch events to server-side Google Tag Manager.
I would have loved if setting up the rerouting was as simple as adding a configuration to the app codebase! This would have been similar to using server_container_url
with a Google Tag.
A link with Google Analytics 4 is thus required, and you need to keep that link alive even if you don’t ever dispatch any hits from server-side Google Tag Manager to GA4.
That gripe aside, and ignoring the general aches and pains of debugging mobile apps, the dispatch mechanism does work like a charm.
Please let me know in the comments if you have any questions or additional input about collecting Google Analytics 4 app hits in server-side Google Tag Manager via the Firebase Analytics SDK (phew – that was a mouthful!).
14 Responses
In some regions, our app fails to load configuration settings for sGTM host migration from GA4. As a result, the sGTM event isn’t being sent. Is there a way to bypass the configuration loading and hardcode the sGTM host within the app to ensure consistent event delivery in these regions?
Hi
I have done all the setup which you mentioned above, but at my end it is not working. I am getting Received SSL challenge for host. Host: https://app-analytics-services-att.com/a in xcode console. Also preview mode is not working. Please help me for this. I am using Firebase 11.6.0.
Hi
Make sure that your SGTM server is running correctly. From the looks of it, it is not. You need to make sure that SGTM responds in that endpoint before you setup the measurement.
Thanks a lot for the article, Simo!
I was able to set it up and route the events via SGTM to GA4.
Two questions:
1.) Did you manage to activate the preview mode with an XCode project? I could not find a way to scan the QR with the XCode simulator.
2.) Did you manage to forward the GA4 app events into multiple GA4 properties? Although I configured multiple tags in SGTM it only routes the events to one GA4 property I’ve created (and which differs from the one that I used for the initial app setup; the original one does not receive any events anymore after SGTM was set up).
Hi
1) I don’t use the XCode emulator – it’s much easier and more accurate to just plug a device in to the Mac with the USB cable and use that for testing. But AFAIK, it’s not possible to use the emulator to scan QR codes.
2) To the extent where I saw the outgoing requests being dispatched from SGTM – yes. I didn’t do a thorough test to see if they actually make it through processing or into BigQuery tables.
Thanks a lot, Simo!
I used the USB cable approach. I entered the bundle ID as “App ID” in the preview mode form and scanned the QR code. The browser on my mobile then asks me to confirm that the website tries to open another app, which I did, but then I keep ending up with “An error occurred. The app could not be opened.” Any idea what I might have missed?
I still did not manage to route one incoming stream into different GA4 properties by just providing different app IDs in the GA4 tags in serverside GTM. 🙁 Would be awesome if you or someone else could give that a try and let me know if you somehow managed to make it work.
Hi Simo,
Thanks for sharing this article! This has been a much-awaited feature from the Firebase team.
Regarding Facebook CAPI, you mentioned CampaignID—I think you meant “fbclid,” which should be sent to Meta for mobile apps.
Wouldn’t it be relatively straightforward to parse this value (if it’s available with every ad click) upon app open and send it as a custom parameter? This could then be utilized in sGTM to activate App CAPI.
if (!fbc) fbc = eventData.fbc;
If this works as expected, wouldn’t it essentially eliminate the need for MMPs altogether?
Hi
No, I’m specifically talking about campaign_ids: https://developers.facebook.com/docs/marketing-api/conversions-api/app-events – fbclid is a feature of the Facebook *website* – as far as I know, the Facebook *app* or deep links from other sites to the app don’t use the “fbclid”.
Hi Simo,
Great resource as always !
Would you think that this setup (both in Plist + Stream) is overidding client-side data collection ?
To put this in other words, is it possible to do dual setup having Firebase -> GA4 alongside Firebase -> sGTM -> GA4 ?
Hi!
I don’t think it’s possible due to how Firebase SDK works, but perhaps Google will at some point add support for multi-streaming Firebase data.
Thanks Simo for the great write up as always.
With the SS GTM solution, would an app developer be able to update Firebase event tracking without requiring a full new app release (making end users update etc) to the app store?
Hi
No. Server-side tagging has no impact on how the app collects events in the first place. If you need to change how the app does tracking, you need to change the Firebase Analytics calls in the codebase, and that requires an update for the end users.
This is a very detailed article. Thanks Simo for taking time to write one, as always. 🙂
Thank you, Jyot!