Your data stack

Unifying data: making your tools agree on one user

Unifying data means lining up records from different tools so they describe the same user or campaign. You join at the least detailed level both sides share. User-level sources join on your internal user ID; ad spend only goes down to campaign and day.

By Ansh Agrawal4 min readUpdated

Unifying data can mean a few different things, so start with what you probably already have:

  • a Data warehouseA separate database built for analysis rather than for running the product. It is where every source lands together, which is what makes it the number you trust when two tools disagree.Glossary
  • a Product analyticsThe analysis of what users do inside your product. The test is whether you can point at a row of data and name the user who did it.Glossary tool
  • a CRM, maybe Hubspot
  • an email marketing tool like Klaviyo
  • a handful of other things

And if you're running ads, you have ad data too.

Most Founders I speak with have every one of these tools installed and none of them lining up.

Where does unifying data happen?

The warehouse is where the unifying actually happens.

Product analytics, backend database, CRM, email tool and ad platforms all feed into the warehouse, the source of truth, where every source is lined up on the same user or campaign. Optionally, reverse ETL syncs unified data back into your tools, like CRM fields into Mixpanel, priced per record synced.

At what level should you join data from different tools?

Join at the least detailed level both sides share. The level you unify on can be different, and this is the part worth getting right.

Most of your data is user level. A signup, an email open, a lead score, a purchase: all of it attaches to a person.

Ad spend doesn't. Google Ads gives you spend per campaign per day, and it can't tell you which of those dollars went to which user.

So you connect your data at the least detailed level both sides share. User level data stays user level. Ad spend only goes down to campaign and day, so that's where you join it.

DataLevel it comes atWhat you join it on
Signups, email opens, lead scores, purchasesUserYour internal user ID
Ad spend from Google Ads or MetaCampaign per dayCampaign ID and day

In practice: take a campaign's spend for a day, count the users who came in on it that day (through UTMs or MMPA mobile measurement partner: the service that attributes an app install to the campaign that caused it, which UTM parameters cannot do because they do not survive the app store.Glossary setup) and how many activated or stuck around, and you know what you paid per activated user.

Product data is user level, since each sign-up, activation and purchase belongs to a person, while ad spend comes per campaign per day, because Google Ads can't say which dollar went to which user. You join them at the level both share, campaign and day, counting the users each campaign brought in that day and how many activated, which gives you cost per activated user.

What you're building is one user journey across every source, so you can answer things like:

  • if we spend this much on this campaign, how does this campaign perform on activation and RetentionThe share of users who come back. Bounded retention counts people who returned on exactly that day; unbounded counts that day or any day after, and the two give very different numbers.Glossary?
  • if one user's lead score is 8 and someone else's is 4, what actions in the product got them there?

Should you sync unified data back into your tools?

A lot of the time these data points can also be synced back into other tools, to widen what those tools can do for you. You might want a lot of your Hubspot data in Mixpanel, for example, so you can answer these questions inside Mixpanel rather than writing SQL against the warehouse every time.

Whether you sync into your tools or query in the warehouse is a product decision you have to make, and both sides cost something. Syncing means a reverse ETL tool priced per record synced, and the fields have to be kept in step as your schema changes. Querying in the warehouse costs you nothing extra in tooling, but every question goes through someone who can write SQL, so you're paying in that person's time and in how long people wait for answers.

In an ideal setup you have a warehouse with all your data unified and proper models built on top of it. Models are the cleaned-up tables your team actually queries, covered in Data modelling.

How do you make your ad platform data match your product analytics data?

People really struggle with this one, and it's very simple.

Every tracking template (the extra text your ad platform adds to the end of the link) should have a utm_campaign parameter, the tag that tells your analytics tool which campaign a click came from (covered in MMP and UTMTags added to the end of a link that record where a click came from. Enough on their own for a web-only product, and useless the moment an app install sits in the path.Glossary). That parameter should contain your campaign ID.

You don't type the ID in by hand. Every platform has a macro, a placeholder it swaps for the real value at click time:

  • In Google Ads, set a Final URL Suffix at the account level with utm_campaign={campaignid}.
  • In Meta, use the URL parameters field with utm_campaign={{campaign.id}}.

Both get replaced with the real ID the moment someone clicks. Google lists {campaignid} among its ValueTrack parameters, and Meta lists {{campaign.id}} in its dynamic URL parameters.

Set it once at the account level and every campaign you launch after that is tagged correctly, including the ones you forget about.

From Google Ads you know how much you spent on that campaign on that day. From your product analytics tool you know how many users came through that campaign, and what they did next. Now you can build out the entire FunnelThe steps a user goes through in order. Every product is one, and so is every feature inside it. Counting who reaches each step is what shrinks a whole-product problem to a single step.Glossary.

The linking happens on campaign ID, so the values have to match exactly. If they don't, it's not going to work.

This is what people get wrong. Say you send the campaign name as your utm_campaign value instead, it goes out uppercase, and the name on the platform is written differently. Now the same campaign has two different labels, and you'll never be able to map them together.

Matching on campaign name fails: utm_campaign says SUMMER_SALE and the ad platform says Summer-Sale, so a name typed twice never joins. Matching on campaign ID works: both say 12345, filled in by a macro and set once at account level, with utm_campaign={campaignid} in Google Ads and utm_campaign={{campaign.id}} in Meta.

So make sure the value in your utm_campaign and the ID in your ad platform are exactly the same. Once the IDs match, you can put spend next to activation and retention for every campaign you run.

Similarly, you can match on Ad group, Ad ID, keywords, etc.

Common questions

How do you unify data from different analytics tools?

Land everything in a warehouse and join on the level both sides actually share. User-level sources join on your internal user ID; ad platforms only report per campaign per day, so that's the grain you can join them at.

Why doesn't my ad platform data match my product analytics?

Because they count different things at different grains. Ad platforms report spend and clicks per campaign per day and can't say which dollar reached which user, so the two only reconcile at campaign level, not per person.

Where should you unify your data?

In the data warehouse, not the analytics tool. Land your backend, product analytics, CRM, email and ad data there, then join them.

What should the utm_campaign parameter contain?

The campaign ID, not the campaign name. Fill it with the ad platform's campaign ID macro, set once at the account level, so the value matches the ID in the platform exactly.

Should you sync warehouse data back into tools like Mixpanel?

It's a product decision, and both sides cost something. Syncing needs a reverse ETL tool priced per record synced, with fields kept in step as your schema changes; querying in the warehouse costs nothing extra in tooling, but every question goes through someone who can write SQL.