pixellint

Blog · LinkedIn CAPI

LinkedIn conversionHappenedAt is milliseconds. A ten-digit Meta clock fails the 90-day check.

LinkedIn conversionHappenedAt is milliseconds. A ten-digit Meta clock fails the 90-day check. The schema says epoch milliseconds, and if your source records seconds you insert 000 at the end. Graph event_time without that suffix is 1970.

The Streaming Conversion Events schema types conversionHappenedAt as a long. The example is 1590739275000. Validation says it must be a valid millisecond epoch and must be within the past 90 days. The conversions API error table maps that miss to HTTP 400, type INVALID_CONVERSION_TIME_FIELD_VALUE, message Conversion time should be within 90 days. That is a production miss, not a lint preference.

Meta event_time is Unix seconds. Date.now() divided by 1000 is the Meta helper. Send 1740000000 as conversionHappenedAt and LinkedIn reads 1740000000 milliseconds, which is January 1970. That is more than 90 days ago. The request can include a perfect SHA256_EMAIL and still fail the clock. The Insight Tag does not rescue a server event that already failed validation.

The hop is conversionEvents, not Graph

Stream to POST https://api.linkedin.com/rest/conversionEvents. Required headers include Authorization, Content-Type, Linkedin-Version as YYYYMM, and X-Restli-Protocol-Version: 2.0.0. Scopes are rw_conversions and r_ads. The user needs an ad account role other than VIEWER: ACCOUNT_BILLING_ADMIN, ACCOUNT_MANAGER, CAMPAIGN_MANAGER, or CREATIVE_MANAGER. A Meta system user token on this host is a 401, not a clock bug.

You need a conversion rule before you stream. POST /conversions with conversionMethod CONVERSIONS_API, an account URN, a name, and a type such as PURCHASE or LEAD. The rule id becomes urn:lla:llaPartnerConversion:{id}. Associate campaigns with Campaign Conversions or autoAssociationType. Events only attribute to associated campaigns. A valid clock on an unassociated rule is a reporting hole, not INVALID_CONVERSION_TIME_FIELD_VALUE.

A successful single event returns 201 Created. 422 with /conversionHappenedAt field is required means you omitted the field. 400 INVALID_CONVERSION_TIME_FIELD_VALUE is the 90-day miss. Batch create uses X-RestLi-Method: BATCH_CREATE and elements[]. Up to 5000 events per batch. Rate limits on the live page: 600 requests per minute and 500,000 requests per day per member access token. One bad row in a batch can fail the payload. Fix the clock, then resubmit.

Append 000 if the source is seconds

LinkedIn's own note is the whole adapter: if your source records conversion timestamp in seconds, insert 000 at the end to transform it to milliseconds. That is string concatenation of three zeros, or multiply by 1000. Date.now() is already milliseconds. Math.floor(Date.now() / 1000) is the Meta helper. Do not share that helper with LinkedIn.

Count the digits before you POST. Thirteen digits is milliseconds in this decade. Ten digits is seconds. Twelve digits is a truncated miss. A future timestamp fails the past-90-days rule the same way 1970 does. The sample 1590739275000 is May 2020 in milliseconds. Copying that sample into production in 2025 is also outside the window. Use the event's real UTC instant, converted at the edge.

conversionValue is optional. currencyCode is ISO, amount is a decimal string such as "50.0". That is not Meta custom_data.value as a number, and it is not Reddit metadata.value as a double copied blindly into a string field you forgot to quote. eventId is optional and used for deduplication. Reuse the Insight Tag event id when you fire both pipes. A new uuid on the server and a different id in the browser is two conversions or a drop, depending on LinkedIn's join, not a clock pass.

userIds is not user_data.em

user.userIds is a list of {idType, idValue}. Supported idType values include SHA256_EMAIL, LINKEDIN_FIRST_PARTY_ADS_TRACKING_UUID, ACXIOM_ID, PLAINTEXT_IP_ADDRESS, SHA256_IP_ADDRESS, and GOOGLE_AID. SHA256_EMAIL is lowercase, trimmed, SHA-256 hex, max 64 characters. The first-party cookie / click id is li_fat_id after you enable enhanced conversion tracking, sent as LINKEDIN_FIRST_PARTY_ADS_TRACKING_UUID. It is not fbc and it is not fbclid.

PLAINTEXT_IP_ADDRESS is IPv4 in the clear. LinkedIn hashes with salt before matching. SHA256_IP_ADDRESS is the hex digest if you hash yourself, IPv4 only. userInfo is plaintext firstName and lastName, optional companyName, title, countryCode. If you include userInfo it must contain both firstName and lastName. If you identify via lead, externalIds, or userInfo without a userId, send userIds as []. Omitting userIds entirely is 422. Copying Meta user_data without that array is the usual 422.

externalIds is a list with maximum size 1 today. Send it with a standard identifier first so LinkedIn can store the mapping for a year across ad accounts in the same Business Manager. Later events can send just the externalId. lead is a leadGenFormResponse URN. None of these fields replace conversionHappenedAt. A perfect match key on a 1970 clock still fails the 90-day check.

The body people will send first

A Meta CAPI purchase with event_time in seconds. This is not a LinkedIn conversion event.

{
  "data": [{
    "event_name": "Purchase",
    "event_time": 1740000000,
    "action_source": "website",
    "user_data": { "em": ["64ec88ca..."] }
  }]
}

That object is a legal Meta CAPI event if the hash is right. Posted to /rest/conversionEvents it is the wrong wrapper, the wrong event name key, no conversion URN, no userIds, and a ten-digit clock. LinkedIn will not map event_time to conversionHappenedAt. The 90-day validator never sees a millisecond timestamp it can accept.

The LinkedIn shape is a conversion URN, conversionHappenedAt as thirteen digits within 90 days, user.userIds with at least one documented idType or an empty list plus another valid identifier, and optional conversionValue.amount as a string. Headers carry Linkedin-Version. None of that is Graph.

Names that do not translate

The 90-day window is not Meta's seven days

Meta CAPI wants event_time within seven days and not more than about ten minutes in the future. LinkedIn wants conversionHappenedAt within 90 days. A CRM lead from last month can be legal on LinkedIn and stale on Meta. A seconds clock that looks recent on Meta is 1970 on LinkedIn. One internal UTC instant. Two conversions at the edge. Two windows.

Post-click and view-through windows on the conversion rule are a different 90. Default postClickAttributionWindowSize is 30, default viewThroughAttributionWindowSize is 7, with allowed values including 1, 7, 30, 90, and 365 for listed types such as PURCHASE and LEAD. That rule window does not replace the event timestamp validator. You can have a 90-day attribution window and still fail INVALID_CONVERSION_TIME_FIELD_VALUE because the millisecond clock was ten digits.

Insight Tag plus CAPI is still two pipes. The tag sees the browser. The POST sees the CRM. Dedup on eventId. Capture li_fat_id on the landing URL when enhanced conversion tracking is on, and copy it through forms that leave the landing host. Chrome cookies, Safari ITP, and ad blockers still punch holes in the tag. The server clock still has to be milliseconds.

If your source records conversion timestamp in seconds, please insert 000 at the end to transform it to milliseconds. conversionHappenedAt must be a valid timestamp representing the number of milliseconds since epoch time. The timestamp must be within the past 90 days.

LinkedIn Conversions API schema

What to do

Keep one internal event: name, UTC instant, order or lead id, hashed email, landing URL, click references. At the edge, emit the LinkedIn envelope. Convert the clock there: milliseconds, append 000 if you only stored seconds. Persist eventId as a different column from Meta event_id. Persist the conversion URN. Do not POST Graph JSON to api.linkedin.com.

Create the CONVERSIONS_API rule. Associate campaigns. Send userIds, even if the list is empty because you matched on userInfo. Lint the body you POST. The LinkedIn pack is vendor/linkedin-conversions-api. The Meta pack is vendor/meta-conversions-api. One fixture cannot serve both.

The contract page for the field list is the Conversions API schema. This post is the market fact: conversionHappenedAt is milliseconds, and a ten-digit Meta clock fails the 90-day check. Pixellint is not affiliated with LinkedIn or Microsoft. Passing a linter means the artifact matches the published envelope. It does not mean Campaign Manager attributed the conversion.

Checklist

Sources

Contract pages

The dated argument is above. These pages are the field lists.

Open the LinkedIn CAPI pack Docs