DEV Community

Aleksander Sekowski
Aleksander Sekowski

Posted on

TikTok Events API Wants ISO 8601. Unix Epoch Gets Stamped as Arrival Time.

A checkout handler posts CompletePayment to the TikTok Events API. The response is 200. Events Manager shows the conversion, but it landed yesterday afternoon, not at 2:14 AM when the order cleared.

The usual suspect is the access token. The usual bug is the clock field name and shape.

timestamp: Math.floor(Date.now() / 1000)
Enter fullscreen mode Exit fullscreen mode

That value is correct on Meta's Conversions API. It is correct on Pinterest. It is wrong on TikTok.

TikTok documents timestamp as an ISO 8601 string, such as 2026-07-26T06:00:00Z. Ten digits of Unix seconds and thirteen digits of milliseconds are not ISO 8601. TikTok accepts the POST anyway and stamps the event with the time it arrived, not the time you meant.

The fix is one line:

timestamp: new Date().toISOString()
Enter fullscreen mode Exit fullscreen mode

If the order happened two hours ago, send that instant in ISO form. Do not send Date.now() and hope TikTok interprets the unit.

Meta seconds, LinkedIn milliseconds, TikTok ISO

Conversion APIs disagree on how to spell "when." Meta wants event_time in seconds. LinkedIn wants conversionHappenedAt in milliseconds. Reddit CAPI v3 wants event_at in milliseconds. TikTok wants a string that starts with YYYY-MM-DD and includes a time component.

A shared helper named event_time or timestamp will be wrong on at least one hop unless you branch by vendor at the edge. The event time units guide maps those four shapes side by side so you are not guessing from one vendor's sample.

CompletePayment is not Purchase

TikTok's server event name is event, not event_name. Standard web names are PascalCase strings like CompletePayment, ViewContent, and AddToCart. Meta's Purchase copied into TikTok's event field is not a purchase here. It is an unknown or custom label unless Events Manager recognizes it.

The browser side uses ttq with its own event names. The loader hits analytics.tiktok.com. The server side hits business-api.tiktok.com on /pixel/track or /pixel/batch. pixel_code on the server is the Pixel ID from Events Manager, the same id ttq loads as sdkid in the page. Two different hosts, one dedup contract.

Hash the email. Do not hash the IP.

TikTok's context.user.email, phone_number, and external_id must be SHA-256 hex digests on the client side before they are sent. context.ip and context.user_agent are plaintext. Hashing those two makes the event unmatchable, the same inversion Meta documents for client_ip_address.

"context": {
  "ip": "203.0.113.42",
  "user_agent": "Mozilla/5.0 ...",
  "user": {
    "email": "shopper@example.com"
  }
}
Enter fullscreen mode Exit fullscreen mode

That raw address in context.user.email fails twice: the field is not a 64-character hex digest, and a separate rule flags the @ as unhashed PII. Normalize first (trim, lowercase), hash, send the hex. The identity guide lists which fields each vendor hashes and which stay plain.

Dedup needs the same event_id the pixel sent

When the same conversion fires in ttq and on the server, TikTok expects the same event_id on both sides. Without it you double-count or Events Manager's dedup view hides the server hit and you cannot tell which pipe attributed.

Generate one id per conversion at checkout. Pass it to ttq in the browser and reuse it in the Events API payload. That id is separate from timestamp: you can get dedup right and still lose attribution if the server timestamp is an epoch integer.

How I catch it before the queue drains

I maintain Pixellint, an open source linter for pixels and conversion API payloads. The TikTok Events API pack (vendor/tiktok-events-api) contracts track and batch bodies against TikTok's parameter docs. On a payload with epoch seconds in timestamp:

$ pixellint validate json @tiktok-events.json
rulepack: vendor/tiktok-events-api (vendor: tiktok)
  error   vendor.tiktok-events-api.body.timestamp.invalid
          TikTok documents the event time as an ISO 8601 timestamp.
          An epoch number is not that format, and TikTok then stamps
          the event with the time it arrived.
    fix:  Send an ISO 8601 timestamp, such as `2026-07-26T06:00:00Z`.
Enter fullscreen mode Exit fullscreen mode

The same pack catches raw email in context.user.email (vendor.tiktok-events-api.body.unhashed_email) and a SHA-256 digest in context.ip (vendor.tiktok-events-api.body.hashed_plaintext_field).

Paste the body into the playground if you do not want a local install. Nothing you paste leaves the browser.

cargo install pixellint
# or: npm install pixellint
pixellint validate json @tiktok-events.json --rulepack vendor/tiktok-events-api
Enter fullscreen mode Exit fullscreen mode

Where to go next

The TikTok Events API pack page is the field table with rule ids and vendor doc links. The TikTok Pixel pack covers the loader on analytics.tiktok.com: required sdkid, expected lib=ttq.

The TikTok Pixel and Events API playbook walks both pipes together: ttq in the page, ISO time on the server, shared event_id, hashed PII in context.user only.

The short version

timestamp is ISO 8601, not Unix seconds and not Date.now(). event is CompletePayment, not Purchase. Hash email, phone, and external_id. Do not hash context.ip or context.user_agent. Send the same event_id ttq sent. Lint the JSON before you drain the queue.

Pixellint is independent of TikTok. The rule ids above cite TikTok's Events API docs because that is where the requirements live, not because this is an official tool.

Top comments (0)