pixellint

Analytics · engineers

Amplitude wants milliseconds, not seconds

The Meta helper you copied divides by 1000. Amplitude is the vendor where that helper is wrong in the opposite direction. Copying CAPI clients across analytics APIs is a clock bug generator.

HTTP V2 envelope

POST amplitude.com /2/httpapi. api_key is required (project key, keep it server-side). events[] is required and must hold at least one event. Each event needs event_type. Pixellint: vendor.amplitude.body.api_key.missing, .events.missing, .event_type.missing. An empty events array is vendor.amplitude.body.events.empty. This is not GA4 MP and not Segment. There is no writeKey and no measurement_id.

insert_id is how Amplitude dedupes retries. Mint it once per event, persist it, replay the same body. A 500 is not permission to mint a new insert_id. That is the same idea as Meta event_id, a different field name, a different vendor.

{
  "api_key": "AMPLITUDE_API_KEY",
  "events": [{
    "event_type": "Purchase Completed",
    "user_id": "user-12345",
    "device_id": "device-12345",
    "time": 1770000000000,
    "insert_id": "order-T12345",
    "event_properties": { "revenue": 19.99, "currency": "USD" }
  }]
}

time is 13 digits

Amplitude time is milliseconds since epoch. Date.now() is already the right unit. A 10-digit Unix seconds value is January 1970 as milliseconds. Pixellint: vendor.amplitude.body.time.invalid. LinkedIn conversionHappenedAt is the same millisecond clock. Meta event_time is seconds. GA4 timestamp_micros is 16 digits. PostHog and TikTok want ISO 8601. One helper, five bugs.

Do not send an ISO string in the numeric field. Parse it. Do not multiply Date.now() by 1000 because GA4 needed microseconds. That lands millennia ahead. Amplitude will store it. Your charts will not.

pixellint validate json @amplitude.json --rulepack vendor/amplitude

user_id or device_id, five characters minimum

One of user_id or device_id is required on every event: vendor.amplitude.body.event_needs_an_identifier. Amplitude documents both as strings of 5 characters or more, and silently drops shorter ones unless min_id_length is set. Pixellint: vendor.amplitude.body.user_id.invalid and .device_id.invalid. user_id=42 is a legal JSON value and a dropped identity.

Do not rotate device_id per event. That is a new person every hit. Do not put the email in user_id if you also send it hashed to ads platforms under a different canonical id. Pick one product id and map it.

Revenue is its own shape

A generic event with a value property may not show in revenue LTV. Amplitude revenue events use revenue, revenueType, and (when you use them) the revenue API the docs name. Currency as a display symbol is not an amount. Shipping a Purchase Completed with no revenue field is how finance and product disagree by 100%.

Identify (user properties) is not an event. Setting plan=pro on the user and never sending subscribed will confuse anyone who lives in a funnel chart. The pack validates HTTP V2 event uploads, not the Identify API.

Do not share a clock helper with Meta

If your shared instrumentation layer has toTimestamp() that divides by 1000, Amplitude is the regression. Gate it with pixellint validate json in CI on a golden Purchase Completed fixture that uses Date.now()-shaped 13-digit time, and a failing fixture that uses 10-digit seconds. The HTTP 200 from Amplitude is not that gate.

Pixellint is not affiliated with Amplitude. The pack cites the HTTP V2 API. When the docs change the digit count, change the pack, not a wiki sentence.

Check the artifact

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