pixellint

CAPI · PMs · marketers · engineers

The browser never saw the purchase

Ad blockers, ITP, and checkouts that finish on another origin mean the thank-you pixel is optional. The order record is not. A conversion API is how you send that record to the ads platform without waiting for a tag.

The POST is the product

A conversion API is an HTTPS POST with a JSON body, an access token or API secret, an event name, a time, and identifiers: click id, cookie, hashed email, IP, user-agent. Meta CAPI posts to the Graph API events edge and requires access_token. TikTok Events API posts pixel_code plus event to business-api.tiktok.com. GA4 Measurement Protocol requires api_secret and exactly one of measurement_id (G- form) or firebase_app_id.

It is still a measurement artifact. Pixellint validates the body the same way it validates a pixel URL: required fields, formats, and the traps the vendor documented. It does not invent a shared schema. There is not one.

The clocks are incompatible on purpose

Meta CAPI event_time is Unix seconds, exactly 10 digits. Date.now() is 13 digits (milliseconds). A 13-digit value lands tens of thousands of years in the future, outside Meta's window (event_time can be up to 7 days old). vendor.meta-conversions-api.body.event_time.invalid is that miss. Pinterest CAPI uses the same seconds clock. Reddit CAPI v3 event_at is the inverse: exactly 13 digits. LinkedIn conversionHappenedAt is 13 digits. GA4 timestamp_micros is 16 digits (microseconds). TikTok Events API timestamp is ISO 8601; an epoch number is stamped as arrival time.

Copying one helper across vendors is how one pipe attributes and the next one files the purchase in 1970 or in the year 56000. Convert at the edge. The field name is not a hint you can trust across companies.

Identity is hashed on some fields and forbidden on others

Meta user_data.em, ph, fn, ln, ge, db, ct, st, zp, country, and external_id must be SHA-256 hex. Uppercase hex is allowed: Meta lowercases the INPUT, not the digest. The same over-hashing trap exists on Snap and Pinterest. client_ip_address and client_user_agent MUST NOT be hashed; vendor.meta-conversions-api.body.hashed_plaintext_field is that shape. TikTok hashes context.user.email, phone_number, and external_id, and wants context.ip and context.user_agent in the clear.

Reddit CAPI v3 is the odd one: email and phone MAY be raw or hashed. That is not permission to send a raw address to Meta. LinkedIn requires user.userIds even when the list is empty and matching rides on lead or userInfo instead.

A 200 does not mean the event exists

PostHog documents that a capture missing event or distinct_id still returns HTTP 200 and is not ingested. Segment track returns 200 without checking that the call had an event name. GA4 Measurement Protocol returns 204 on the collect endpoint; that is not schema validation. Meta often returns 200 with per-event errors in the JSON, or accepts a batch and reports later.

Validate the body before you ship. Required fields, timestamp units, hashed versus raw, ISO 4217 currency. That is what the rulepacks encode from vendor docs. Status code is liveness.

What it does not do

It does not replace a click id you never stored. It does not hash an email you never collected. It does not fix a pixel that fires Purchase on the landing page. It does not turn a data-center IP into a shopper. Garbage in, matched garbage out.

It also does not unify the names. Meta Conversions API, TikTok Events API, Snap Conversion API, Pinterest Conversions API, Reddit Conversions API, LinkedIn Conversions API, X conversion API, Google Ads enhanced conversions and UploadClickConversions, GA4 Measurement Protocol: same idea, incompatible payloads. TikTok Events 2.0 at /open_api/v1.3/event/track/ is a different envelope from the pixel track pack. Reddit v2 ISO event_at is a different shape from v3.

Wrong clock, right status

The usual first bug is Date.now() copied into Meta event_time. The POST succeeds. Events Manager stays empty or the event never attributes. Count the digits before you debug the token.

// Wrong: 13 digits. Meta and Pinterest want seconds (10).
{"event_name":"Purchase","event_time":1770000000000}

// Right for Meta / Pinterest CAPI
{"event_name":"Purchase","event_time":1770000000}

// Right for Reddit CAPI v3 (milliseconds) and LinkedIn (conversionHappenedAt)
{"event_at":1770000000000}

// Right for GA4 MP (microseconds, 16 digits)
{"timestamp_micros":1770000000000000}

// Right for TikTok Events API (ISO 8601). An epoch here is arrival time.
{"timestamp":"2026-08-21T18:04:00Z"}

Check the artifact

Paste the pixel URL or JSON body into the playground. Same engine as pixellint validate. Nothing leaves the tab.