TikTok Events API Attribution: Why Numbers Don't Match

digital advertising
TikTok and Meta platform logos representing server-side conversion tracking and ad attribution setup
TikTok attributes on ttclid inside a 7-day click window by default. Miss either one and the conversion disappears.

If your TikTok ads look like they're losing money while your bank account says otherwise, start here: TikTok's Events API defaults to a 7-day click-through and 1-day view-through window, and it matches conversions primarily on ttclid. If your sales cycle runs longer than a week, or you never stored the click ID when the visitor landed, TikTok has no way to connect the sale back to the ad. The conversion happened. TikTok just doesn't know it belongs to them.

We see this constantly on accounts that come to us after a "TikTok doesn't work for us" verdict. In most cases TikTok worked fine. The measurement didn't. Below is what's actually happening and the setup we deploy to fix it.


The Default Window Is Shorter Than You Think

Meta conditioned everyone to think in 7-day-click/1-day-view as a floor, and Google Ads gives you 30, 60, even 90-day windows on conversion actions. So most advertisers never check TikTok's setting, assume parity, and get quietly burned.

TikTok Pixel and Web Events API advertisers are defaulted to 7-day click-through and 1-day view-through. At the ad group level you can choose 1, 7, 14, or 28 days for click-through, and Off, 1 day, or 7 days for view-through, per TikTok's own ad group attribution settings documentation. That's it — 28 days is the ceiling.

Here's why that matters more on TikTok than anywhere else. TikTok is a discovery platform, not an intent platform. Nobody opens TikTok to buy your thing. They get interrupted by it, keep scrolling, and come back days later when they're ready. The gap between the click and the purchase is structurally longer on TikTok than on Google Search, where the person typed the problem into the box thirty seconds before converting.

So TikTok — the channel with the longest natural consideration lag — ships the shortest default window. Every conversion that lands on day nine is real revenue that your reporting attributes to "Direct" or "Organic."

The fix is boring and takes two minutes: pull the median time from first touch to closed deal out of your CRM, and set the ad group click-through window one bracket above it. For a DFW home services client we onboarded this spring, median lag was eleven days. They'd been running on the 7-day default for eight months. Moving to 28-day click recovered a large share of conversions that had been invisible the entire time — same spend, same ads, nothing about the campaigns changed. We hadn't improved anything yet. We'd just stopped throwing away the data.


ttclid Is the Whole Ballgame

TikTok's click ID, ttclid, is appended to your landing page URL when someone taps an ad. When you send a conversion through the Pixel or the Events API with that click ID attached, TikTok matches it deterministically back to the click. No click ID, and TikTok falls back to probabilistic matching on whatever else you sent — hashed email, phone, IP, user agent — which is materially less accurate.

The failure mode is almost never "we forgot to send ttclid." It's that ttclid was never persisted long enough to still exist when the conversion fired.

Think about the actual journey. Someone taps your ad on Tuesday, lands with ?ttclid=... in the URL, browses, leaves. On Saturday they come back — typing your domain directly, or through a branded search, or from an email — and buy. That second session has no ttclid in the URL. If you only ever read ttclid from the query string at conversion time, you have nothing, and that sale matches probabilistically at best.

What we deploy instead:

  • Read ttclid on first page load, server-side or in an edge function that runs before client JavaScript, not in a tag that might be blocked.
  • Write it to a first-party cookie with a 30-day expiry. TikTok won't match a ttclid to the original click beyond roughly 30 days anyway, so longer retention buys you nothing.
  • Persist it to your database against the lead or session record the moment you have an identifier — a form fill, an account creation, an email capture. Cookies get cleared. Your database doesn't.
  • Attach it at conversion time from storage, never from the current URL.

That last bullet is the one that separates setups that work from setups that look correct in a tag debugger and still under-report by 30%.


The Deduplication Rules Nobody Reads

Once you're sending events from both the Pixel and the Events API, you have a new problem: TikTok needs to know that the browser-side Purchase and the server-side Purchase are the same sale, not two sales.

The mechanism is event_id, and TikTok's deduplication rules are specific:

  • You must send the same event name and the same event_id through both channels. If you omit event_id, no deduplication happens and your conversion count inflates.
  • Pixel-to-Pixel and API-to-API duplicates are deduplicated when identical event and event_id pairs arrive within a 48-hour window from the first event.
  • Pixel-to-Events-API merges happen for identical pairs arriving after 5 minutes and within that same 48-hour window.
  • TikTok keeps the first event it received and enriches it with what arrives later — so the first send should be your most complete one where you can control the order.

That 5-minute floor trips people up. If you fire the server event essentially simultaneously with the Pixel, you're inside the merge boundary in a way that behaves differently from what you'd expect coming from Meta's CAPI, where near-simultaneous deduplication is routine. Build a small, deliberate delay into the server-side send, or queue it, rather than racing the browser.

And deduplication is only required where the same event type flows through both channels. If you send Purchase server-side only and ViewContent browser-side only, there's nothing to dedupe. Plenty of accounts we audit are sending everything twice with no event_id at all, reporting inflated numbers, and then wondering why blended ROAS never matches platform ROAS.


Why Your Match Rate Will Never Look Like Meta's

Expect TikTok server-side match rates roughly in the 30-55% range, against the 50-70% that's typical for a well-configured Meta CAPI setup. That gap is not a bug in your implementation. It reflects the identity graph TikTok can work with and the demographics of the audience.

Chasing parity is the wrong goal. The right goal is maximizing the signals you actually control on every event:

  • ttclid — deterministic, by far the strongest, and the one you're most likely to be losing.
  • Hashed email — highest-value secondary identifier, and the one most likely to exist in your CRM already.
  • Hashed phone — do the E.164 formatting before hashing, or match quality drops for no reason.
  • IP address and user agent — free, easy, and frequently omitted from server-side sends because the server passes its own IP instead of the client's.

That last one is the single most common technical bug we find in existing TikTok server-side setups. The event is firing, the payload validates, and the IP belongs to the Cloud Run container or the GTM server instead of the customer. Match quality craters and nothing in the interface tells you why.

If you're already running server-side infrastructure for Meta, this is incremental work, not a new build. Our walkthrough on server-side GTM hosting covers the container side, and the same first-party capture pattern we described for iOS 26 stripping fbclid and gclid applies directly to ttclid.


The Order We Fix Things In

When a new account comes to us reporting a TikTok/CRM gap, we work in this order, because it's roughly descending by recovered conversions per hour of work:

  1. Check the attribution window against real CRM lag. Two minutes of work, frequently the largest single recovery. Do this before touching any code.
  2. Verify ttclid is captured at landing and persisted server-side. Load a live ad link, confirm the parameter is read before any tag fires, and confirm it survives into the database.
  3. Audit for duplicate events and missing event_id. Inflated counts are as damaging as missing ones — they teach the algorithm the wrong thing.
  4. Fix the IP and user agent passthrough on server-side events.
  5. Add hashed email and phone to every conversion event you can.
  6. Reconcile weekly against closed revenue and feed outcomes back in.

Step six is what makes the other five compound. Platform-reported conversions are a proxy. Closed-won revenue in your CRM is the truth, and once you're sending real outcomes back into TikTok rather than form fills, optimization starts pointing at customers instead of leads. That's the same principle behind everything we build on the attribution side, whether it's Meta CAPI or a full Hyros implementation.


A Note on What This Won't Fix

Better measurement doesn't make bad creative profitable. If you fix the window, capture ttclid properly, deduplicate cleanly, and TikTok still reports a CPA that's triple your target, TikTok might genuinely be wrong for that offer.

The point of this work is to make the decision on real data. Most of the accounts we audit have never had that. They killed a channel on numbers that were structurally incomplete, and the honest answer is that nobody knows whether it worked, because it was never measured properly in the first place.

Get the measurement right first. Then decide.


Ready to Find What Your Tracking Is Missing?

We run the same attribution audit across TikTok, Meta, Google, and Hyros for our clients — checking windows, click-ID capture, deduplication, and match quality against actual closed revenue, then fixing what's broken.

Get a Free Automation Audit and we'll show you exactly where your conversions are going missing, and what recovering them is worth.