LW IT Solutions
« Blog Overview /Digital Marketing/Tutorials / Tutorial: Implementing the TikTok Events API for...

Tutorial: Implementing the TikTok Events API for Online Shops

Tutorial: Implementing the TikTok Events API for Online Shops
Contents
  1. Step 1: Generating the Event ID Once
  2. Step 2: The Browser Event
  3. Step 3: Capturing the Identifiers the Server Cannot See
  4. Step 4: The Server Request
  5. Step 5: Verifying That Deduplication Works
  6. Staying Inside the Guardrails
  7. Sources

A TikTok pixel on its own reports fewer purchases than a shop actually processes. Ad blockers, tracking protection in the browser, and visitors who close the tab before the confirmation page finishes rendering all remove events that did happen. Sending the same purchases a second time from the server closes that gap — and creates a new problem, because TikTok now receives two reports of one purchase.

The mechanism that resolves it is a shared event_id: one value generated per purchase and sent unchanged along both paths. The following walkthrough builds that architecture end to end for a shop — the browser event, the identifiers the server cannot see by itself, the server request, and the verification that deduplication is actually working.

Two-lane flow: one event_id generated per purchase, sent by the browser TikTok pixel and by the server Events API, merging in Events Manager where the matching id makes the purchase count once
The pixel supplies the browser context, the server supplies the reliability – the shared event_id keeps them one conversion.

Step 1: Generating the Event ID Once

The event_id has to be generated in one place and reused, which rules out generating it independently on each side — two random values describe two separate conversions as far as TikTok is concerned. For a shop, the order identifier is the natural source: it already exists at the moment of purchase, it is stable, and it is available to both the confirmation page and the backend.

A prefix keeps event types apart, since a single order can produce more than one tracked event. An event_id of purchase-10432 for the purchase and checkout-10432 for the checkout start keeps each pair matched to its own counterpart. As a side effect, a visitor refreshing the confirmation page produces the same event_id again and is deduplicated rather than counted twice.

Step 2: The Browser Event

In the TikTok pixel, the identifier travels in the third argument of ttq.track, separate from the event properties:

ttq.track('CompletePayment', {
  contents: [
    { content_id: 'SKU-8891', content_type: 'product', quantity: 1, price: 149.00 }
  ],
  value: 149.00,
  currency: 'PLN'
}, {
  event_id: 'purchase-10432'
});

The event name must match on both sides as well. TikTok pairs the two reports on the combination of event name and event_id, so CompletePayment in the browser and Purchase on the server are two different events that will never deduplicate against each other, no matter how carefully the identifier is matched.

Step 3: Capturing the Identifiers the Server Cannot See

Two values exist only in the browser. ttclid arrives as a URL parameter when a visitor lands from a TikTok ad, and _ttp is the first-party cookie the pixel sets. Both improve attribution on the server request, and both have to be persisted at landing time — the confirmation page is usually several navigations later, by which time the URL parameter is long gone:

// On landing: persist the click id for the rest of the visit
const ttclid = new URLSearchParams(window.location.search).get('ttclid');
if (ttclid) {
  document.cookie = 'shop_ttclid=' + encodeURIComponent(ttclid) +
                    ';max-age=2592000;path=/;secure;samesite=Lax';
}

At checkout, both values are read out of the cookies and stored on the order record, so the backend has them when it builds the server event. The visitor’s IP address and user agent come from the original HTTP request and belong in the same record — a request sent later from a server has neither of its own.

Step 4: The Server Request

The Events API takes a POST to the event/track/ endpoint of the TikTok Business API, authenticated with an access token from TikTok Events Manager and addressed to a pixel through event_source_id. Identifying fields are normalised and then SHA-256 hashed; nothing identifying leaves in plain text:

{
  "event_source": "web",
  "event_source_id": "PIXEL_CODE",
  "data": [{
    "event": "CompletePayment",
    "event_time": 1789000000,
    "event_id": "purchase-10432",
    "user": {
      "email": "1f3a...",
      "phone": "9c22...",
      "external_id": "7b41...",
      "ttclid": "E.C.P.xxxxx",
      "ttp": "2ABCDe...",
      "ip": "203.0.113.7",
      "user_agent": "Mozilla/5.0 ..."
    },
    "properties": {
      "contents": [
        { "content_id": "SKU-8891", "content_type": "product", "quantity": 1, "price": 149.00 }
      ],
      "currency": "PLN",
      "value": 149.00
    },
    "page": { "url": "https://shop.example.com/thank-you" }
  }]
}

Normalisation before hashing decides whether the identifiers match anything: email trimmed and lowercased, phone reduced to digits in international E.164 form. The same value hashed from Anna@Example.com  and from anna@example.com produces two unrelated digests, and only one of them can match. event_time is a Unix timestamp in seconds, and the request belongs immediately after order confirmation rather than in a nightly batch. TikTok rejects events that arrive too late, and a batch job discovers that only once the window has already closed.

Step 5: Verifying That Deduplication Works

The test_event_code field, taken from the Test Events tab in Events Manager, routes a request into the test view without touching reporting — enough to confirm that the payload is accepted and that the hashed fields are recognised.

Deduplication itself only shows up in live data. In Events Manager, an event receiving both channels reports its browser and server counts separately, and the difference between their sum and the deduplicated total is the evidence that pairing is working. A total that equals the sum of both channels means nothing is being matched — usually a mismatched event name, or an event_id generated separately on each side. Comparing the deduplicated count against actual orders in the shop backend is the check that matters; TikTok’s own numbers cannot reveal a purchase that neither channel reported.

Staying Inside the Guardrails

Both channels belong behind the same advertising-consent signal. A server-side request is not exempt from consent because it originates in a backend: it carries the same customer data, and the lawful basis for sending it is the one collected in the browser. That means the consent state has to be stored with the order and checked before the request is built. Raw email addresses and phone numbers exist only long enough to be normalised and hashed, and the access token belongs in a secrets store rather than in application code, since it authorises writing conversions into the ad account.

Lukas Wojcik

Lukas Wojcik

Systems architect and technology enthusiast specializing in scalable tracking solutions, GMP Stack (GA4 & GTM), and robust backend architectures. Advocate for clean code and privacy-first design.

Get in Touch

Briefly describe your project or inquiry for a tailored response. This site is protected by reCAPTCHA.

Write a comment

The email address is not published. Required fields are marked with an asterisk.

ALL ARTICLES & CATEGORIES

CCTV

Follow this category by RSS

Cloud & AI

Follow this category by RSS

Data Privacy

All 13 articles in this category Follow this category by RSS

Digital Analytics

All 50 articles in this category Follow this category by RSS

Digital Marketing

All 30 articles in this category Follow this category by RSS

IT & Networks

All 16 articles in this category Follow this category by RSS

Music Production

Follow this category by RSS

Raspberry PI

Follow this category by RSS

Smart Home

All 18 articles in this category Follow this category by RSS

Web Development

Follow this category by RSS

WordPress Plugins & Tricks

Follow this category by RSS