pixellint

CAPI · engineers · PMs

One CAPI JSON cannot serve every vendor

Short answer

The first CAPI integration is usually Meta. The second is a copy with the host changed. That is how LinkedIn returns 422, TikTok stamps arrival time, and Pinterest drops the event for a missing event_id.

Validate this snippet Open the rulepack

The clock is the tell

Meta CAPI event_time is Unix seconds, 10 digits. LinkedIn conversionHappenedAt is milliseconds, 13 digits. A seconds value on LinkedIn reads as 1970 and falls outside the 90-day window. TikTok Events API timestamp is ISO 8601. An epoch number there is treated as arrival time, so a backfill looks like it happened just now. Pinterest CAPI event_time is seconds again, like Meta. Reddit CAPI v3 event_at is milliseconds.

Count the digits before you POST. Date.now() is right for LinkedIn and wrong for Meta. Math.floor(Date.now()/1000) is right for Meta and wrong for LinkedIn. toISOString() is right for TikTok and a type error on Meta. There is no shared helper that is correct for all four without a branch.

Required fields do not translate

LinkedIn requires user.userIds even when you match on userInfo or lead. The field is often an empty array. Omit it and the API returns 422. Meta does not have that field. Copying a Meta body to LinkedIn drops userIds because it was never in the template.

Pinterest CAPI requires event_id. Meta treats event_id as how you dedup against the pixel; people ship Meta without it and still see purchases. The same omission on Pinterest is invalid. action_source casing differs (Pinterest lowercase web). Event names differ (Purchase versus CompletePayment versus a LinkedIn URN). A shared enum is a mapping table, not a JSON key.

{
  "event_name": "Purchase",
  "event_time": 1770000000,
  "user_data": { "em": ["64ec88ca..."] }
}

Identity hashing is per vendor

Meta wants SHA-256 hex on em and ph, and forbids hashing IP and user-agent. Reddit CAPI v3 allows email raw or hashed. That is not permission to send raw email to Meta. LinkedIn wants SHA256_EMAIL inside userIds idValue. TikTok hashes context.user.email and wants IP in the clear.

A warehouse column hashed_email is useful. A warehouse column user_data copied to every vendor is how you send Meta-shaped nested objects into a vendor that wanted a flat list. Convert at the adapter. Validate the outbound body, not the warehouse row.

HTTP 200 is still not the contract

Several APIs return 200 or 204 for payloads they will not attribute. Schema validation is a separate step. pixellint validate json --rulepack vendor/meta-conversions-api on the Meta body, vendor/linkedin-conversions-api on the LinkedIn body, vendor/tiktok-events-api on the TikTok body. One command per hop.

Test Events, LinkedIn diagnostics, and TikTok Events Manager are per-vendor UIs. Passing Meta Test Events does not mean the LinkedIn URN ingested. Empty Test Events still means you are not live on Meta even if GTM fired.

The adapter is the product

Keep one internal event: name, UTC instant, order id, hashed email, click ids, value, currency. At the edge, emit the vendor envelope. Persist Meta event_id, LinkedIn conversion URN, TikTok event_id as separate columns so retries replay the same ids.

Pixellint is not affiliated with Meta, LinkedIn, TikTok, or Pinterest. The rulepacks are the published contracts. A shared CAPI JSON is a bug, not a shortcut.

pixellint validate json @meta.json --rulepack vendor/meta-conversions-api
pixellint validate json @li.json --rulepack vendor/linkedin-conversions-api
pixellint validate json @tt.json --rulepack vendor/tiktok-events-api

Check the artifact

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