Blog · TikTok and Meta
TikTok Events API vs Meta CAPI splits on event_source, and on which clock the path still uses.
TikTok Events API vs Meta CAPI is a Graph Purchase pointed at business-api.tiktok.com. Meta posts event_name, event_time as ten-digit Unix seconds, action_source, and user_data to the Graph events edge. The pixel id is in the path. website requires event_source_url of the page. Purchase requires value and currency. Dedup is event_name plus event_id plus that pixel id, and the first arrival inside about 48 hours keeps the value.
TikTok Events API 2.0 posts one JSON body to https://business-api.tiktok.com/open_api/v1.3/event/track/ with Access-Token. event_source is required: web, app, offline, or crm. event_source_id has to match that choice: a Pixel Code, a TikTok App ID, an Offline Event Set ID, or a CRM Event Set ID. data is an array, up to 1,000 events, and more than 1,000 rejects the entire request. event_time on each event is Unix seconds in UTC. The sample is 1687758765. A Meta helper that floors Date.now() to seconds can fill that integer. It cannot fill the envelope around it.
Events API 1.0 still posts to /open_api/v1.3/pixel/track/ with pixel_code at the root and timestamp as ISO 8601, such as 2020-12-14T09:49:27Z. If timestamp is omitted, TikTok stamps the time it received the event. An epoch number in that ISO field is the same class of miss: the event is accepted and then treated as arrival, so yesterday's purchase trains today's campaign. Events API for Web now calls /event/track/ the recommended endpoint. The clocks did not merge. Point the Meta seconds integer at 1.0 and you have picked the path that will not keep it.
website does not become web by renaming the host
Meta action_source website is TikTok event_source web. They are siblings of different objects. Putting action_source on a 2.0 body does not set event_source. Putting a Pixel Code on event_source app can still parse and still be the wrong id. App through 2.0 is allowlist-only. Posting event_source app without that permission is not a self-serve upgrade. If you are not on the allowlist, keep event_source web for the pixel you actually have.
Web requires page.url. That is the browser page, not the API host, the same rule as Meta event_source_url, under a different key. App requires an app object. CRM requires lead, including lead_id exported from TikTok, and CRM events may only be custom events. Offline events may only be standard events. Copying Meta Purchase onto an offline set does not move the enum. 2.0 samples use Purchase. Browser ttq.track and 1.0 examples still use CompletePayment. The same event_id with two event strings will not dedupe.
user.email and user.phone are SHA-256 after TikTok's normalize. ip and user_agent stay plaintext. Hashing the IP because a Meta helper hashed em makes the event unmatchable on both vendors, and it is the same mistake. ttclid is web only. ttp is the _ttp cookie the pixel writes. You extract it and attach it. The server will not invent it. fbc is fb.1, a millisecond landing time, and an fbclid. It is not ttclid, and it is not ttp.
Dedup is event_source_id plus event_id plus event
TikTok uses event_source_id, event_id, and event to deduplicate the same event from one channel or across channels, browser pixel and Events API included. event_id is required when event_source is web and both pipes fire, and when event_source is crm and you also upload a CSV. The id may be hashed or unhashed. It has to be identical. Two UUIDs, one from GTM and one from the worker, are two purchases.
TikTok's Event Deduplication writeup, as Adobe's extension still quotes it, says duplicates within five minutes are merged and a duplicate received within 48 hours is removed. Meta's window is also about 48 hours, and the first arrival keeps its value. Do not assume TikTok replaces the value the way Google's matching transactionId can. Send one id, one event string, one clock.
A batch of 1,001 events fails the whole request. A Meta batch that fails because one event_time is older than seven days is a different blast radius with the same lesson: one bad row can drop the rest. Keep the order instant. Floor it to seconds for Meta and for TikTok 2.0. Serialize ISO only on the 1.0 pixel/track path, if that path is still what you post.
One purchase, two TikTok envelopes
The Meta seconds integer matches 2.0 event_time. It does not match 1.0 timestamp. CompletePayment and Purchase do not dedupe against each other.
1.0: pixel_code, event CompletePayment, timestamp "2026-09-27T18:04:00Z"
2.0: event_source web, event_source_id <pixel code>, data[].event Purchase, data[].event_time 1759010400
Meta: event_name Purchase, event_time 1759010400, action_source website
HTTP 200 with a TikTok code other than 0 is not success. Meta's Graph error on a stale event_time is a failed batch. A TikTok code 0 on a 1.0 body whose timestamp was an integer can still be the arrival-time stamp. Read the code and the clock you sent, not only the status line.
The browser pixel is ttq on analytics.tiktok.com, sdkid as the pixel id, lib ttq. Two inits are two PageViews, the same way two Meta base codes are two PageViews. Store ttclid from the landing URL the way you store fbclid. Without it, matching leans on hashed email and on IP and user agent.
Pixellint is not affiliated with TikTok or Meta. The 2.0 contract is the track guide. The 1.0 contract is still the pixel/track body the older pack checks, including ISO timestamp. Validate the envelope you actually POST. A Graph-valid Purchase is not that envelope.
The token, the page, and the event string have to be chosen once
2.0 authenticates with an Access-Token header and Content-Type application/json. Meta puts access_token on the Graph query, and the pixel id in the path. A worker that copies the Graph URL shape onto business-api.tiktok.com will send the token to the wrong place and still omit event_source. test_event_code is a Meta field. It does not divert a TikTok event to a test tab. A TikTok code other than 0 is the error, including when HTTP status is 200.
Pick the event string from the 2.0 list you are actually allowed to send, then put that same string on ttq.track. If the pixel still fires CompletePayment and the server fires Purchase, event_id cannot collapse them. Offline may only use standard events. CRM may only use custom events, and it needs lead.lead_id from TikTok. Web needs page.url of the browser page. A Graph event_source_url copied into a 1.0 context.page.url is the old envelope.
Generate the fixture from the serializer, not from the sample in the 2.0 doc with the clock edited by hand. pixellint validate json on the 1.0 shape checks pixel_code and ISO timestamp. The 2.0 shape is a different body. Run the check that matches the path you POST. The playground accepts either paste. The pack you select has to match the path.
One order, two edges
Keep the order id, the UTC instant in milliseconds, the normalized email, the landing URL, ttclid, and ttp on your session. The Meta edge writes event_name Purchase, event_time as ten-digit seconds, action_source website, event_source_url, user_data.em, and fbc built from fbclid. The TikTok 2.0 edge writes event_source web, event_source_id as the Pixel Code, data[].event set to the string you also passed to ttq, event_time as those same seconds, user.email, plaintext ip and user_agent, ttclid, and ttp.
Do not send fbc on the TikTok body and call it ttclid. Do not send ttclid inside Meta user_data.fbc. The shapes are different, and a hashed click id matches neither graph. Retries reuse the event id and the original seconds. A new UUID on a timeout is a second purchase on both vendors.
The one-endpoint writeup is the 2.0 host and the event_source enum. This comparison is the Meta body that looks finished and still is not that host. Lint both fixtures. Do not publish a conversion lift number TikTok's help page does not print.
What does not transfer
- action_source website becomes event_source web, plus event_source_id as the Pixel Code.
- event_time in seconds can fill 2.0 event_time. On 1.0 /pixel/track/ the field is timestamp as ISO 8601.
- CompletePayment on ttq and Purchase on a 2.0 sample are different event strings. Dedup needs one string.
- fbc and fbclid do not fill ttclid or ttp.
- Hash email and phone. Leave ip and user_agent in the clear.
- App on 2.0 is allowlist. Offline is standard events only. CRM is custom events plus lead_id.
- More than 1,000 events rejects the whole 2.0 request. A non-zero TikTok code is the error, even on HTTP 200.
Sources
Contract pages
The dated argument is above. These pages are the field lists.