Clickstream and Agencies: Building out dual Facebook Pixel support

The Problem

While Astronomer Clickstream integrates with a broad stack of analytics tools to empower you to collect crucial data to derive insights from, sometimes edge-cases we don’t initially support open opportunities to further advance our data tooling.

Recently, a global marketing services customer, using Astronomer Clickstream to track ad efficacy for a large client of theirs, approached us with a problem they were running into. In addition to other tools, our customer uses the Astronomer Facebook Pixel integration to track Facebook Ads conversions. This became problematic, as their client also uses Facebook Pixel outside of Astronomer to track separate audiences from Facebook.

In short, they needed a quick solution to allow for:

  • Full control over the properties of Facebook Pixel without requiring developer resources from their client for updates
  • Performant solution to plug in multiple analytical and data storage tools that can handle their client’s multiple million daily page views
  • Not interfere with any of their client’s data collection

While Astronomer Clickstream solves the first two solutions out-of-the-box, they still needed a way for two separate Facebook Pixels: one loaded by the Astronomer and the other implemented natively by the client, to play nice with each other.

Natively, two separate Facebook Pixels require specific configuration and implementation to send data to the correct account. Without a custom configuration, both pixels would track the same conversion events (e.g. a purchase of a product), causing our customer's FB Pixel to pollute their client's FB pixel account with data they were not expecting..

The Fix

We set out to ensure that our platform would only send events to the Facebook Pixel account owned by our customer, rather than both, in order to maintain clean and sorted data. Luckily, facebook has documentation outlining a remedy to this exact issue, but we have to translate it into functionality our platform can accept without interrupting the data flow for our other customers using the same Facebook Pixel destination.

A typical installation of Facebook Pixel on your website would require manually inserting the Facebook Pixel Tracking Code onto every page you want to track and tying a window.fbq() function to all actions you want to report back to Facebook.

When someone flips on a Facebook Pixel integration in their Astronomer Dashboard, a few things happen to Astronomer's Analytics.js, our web-based application SDK:

  1. Analytics.js is dynamically rebuilt to include the Facebook Pixel functionality available in the open-source Facebook Pixel Integration repository.

  2. Every time Analytics.js is loaded on a page, Facebook Pixel is also initialized with the settings provided to it from the Astronomer Dashboard as is ready to receive events.

  3. Once the actions of a user trigger an Analytics.js event, each event goes through each activated Integration and evaluates what action needs to be taken to forward that data to the correct place.

The Facebook Pixel Integration maps specifically named events to a list of events that Facebook expects to receive. Once Analytics.js sees one of these configured events, it will use Facebook Pixel’s own javascript snippet to push data to Facebook. Let’s dive in to see what happens when Analytics.js loads the Facebook Pixel.

The first thing we need to do is to configure the settings for Facebook Pixel through our UI. This will rebuild the Analytics.js file that we load onto our web application and enable Facebook Pixel tracking on our site.

Specifically, we need to provide three fields:

Pixel ID This is the unique identifier that tells Facebook which account to correlate all received events with.

Standard FB Events Here we can translate the event names that currently exist within our web application to those that Facebook will recognize.This allows us to enable Facebook Pixel with little or no developer requirements.

Send all Events as Single Events This is part of our recent update to fix the issue we are outlining in this article. Enabling this is unnecessary when operating with a single Pixel ID, but when there are multiple Pixel IDs loaded on the page, this setting ensures that any of the Events that map to the Standard FB Events property will only go to the Pixel ID specified in the settings. By default, Standard FB Events that are triggered go to all loaded Pixel IDs.

Inside the Integration code, we can see the defaults settings and properties that are available to our integration. I will have a few snippets embedded in this article, but you can review the entire file from within Astronomer's fork for the Facebook Pixel integration on Github.

var FacebookPixel = module.exports = integration('Facebook Pixel')
  .option('pixelId', '')
  .option('agent', 'seg')
  .option('initWithExistingTraits', false)
  .option('onlyTrackSingle', false)
  .tag('<script src="//">');

lib/index.js, Lines 68-76

On the data storage side, the settings that we saved from within the UI map to the names of the variables available within this integration.

  • Pixel ID = pixelId
  • Standard FB Events = standardEvents
  • Send all Events as Single Events = onlyTrackSingle

Further down in index.js, we call the individual functions that are called to translate the events Analytics.js receives into ones that we can send to Facebook. There are a few that map directly to Analytics.js types (such as .track and .page) and others that map to Facebook Standard Events (such as .productViewed and .orderComplete.)

The original integration code directly triggered the appropriate Facebook Pixel function (loaded on the window as fbq) with differences in the payload format depending if it was an eCommerce event or not.

Here is an example of the what a typical Facebook Pixel event function looked like before our changes:

window.fbq('track', EVENT_NAME, CUSTOM_DATA);

This corresponds to Facebook’s instructions on how to set up this event. However, per the Facebook Multiple Pixel instructions referenced earlier, we need that .page function to change to the following whenever we need to block other Pixels loaded on the page from receiving events:

window.fbq('trackSingle', PIXEL_ID, EVENT_NAME, CUSTOM_DATA);

Note that the addition of the Pixel ID argument to trackSingle forces us to handle this logic a bit more dynamically. Looking at the latest code, you will find that we’ve replaced all window.fbq functions with a superTrack function. Here it is for page events:

superTrack('track', 'PageView');

And we've added a new function at the top of index.js called superTrack that will, on the fly, handle reformatting the fbq calls to the format needed for either track or trackCustom (the function for fbq that handles our Legacy FB Events map).

var fbPixelId = null;
var fbSendAsSingle = false;

var superTrack = function(type, eventName, customData) {
  var sendTrack = function(ty) {
    if (fbSendAsSingle && fbPixelId) {
      if (customData) {
        window.fbq(ty, fbPixelId, eventName, customData);
      } else {
        window.fbq(ty, fbPixelId, eventName);
    } else if (!fbSendAsSingle) {
      if (customData) {
        window.fbq(ty, eventName, customData);
      } else {
        window.fbq(ty, eventName);
  if (fbSendAsSingle) {
    if (type === 'track') {
    if (type === 'trackCustom') {
  } else {
    if (type === 'track') {
    if (type === 'trackCustom') {

lib/index.js, Lines 29-63

With the difference between our new functionality to silo Facebook Pixel events to a single pixel, we have eight different formats to decide between to keep all arguments in the order that Facebook expects.

superTrack’s implementation is a bit more verbose than we'd like, but it's because we're limited to which best practices we can employ whilst providing compatibility with as many different browser versions as possible

When superTrack is called, we pass the type as either track or trackCustom to keep similar patterns to how this file was written before. Using this type, and the variable fbSendAsSingle (which is set appropriately based on the setting for onlyTrackSingle when Analytics.js is initialized), we can determine within the if... else block which one out of the four basic argument formats we need for the event. Once we know which condition is the correct fbq type, we trigger the sendTrack function and we format the arguments to fbq correctly to fill out all of the eight possible conditions.

With it’s default state, this new feature doesn’t change any of the integration’s functionality and we maintain all compatibility with our other client’s existing implementations while preparing every single one to enable and use it in the future.

Subscribe to RSS
Ready to build your data pipelines?

Astronomer is the data engineering platform built by developers for developers. Send data anywhere with automated pipelines built in minutes.