A lead-gen form closes in your CRM. The handler posts the conversion to LinkedIn's Conversions API with userInfo filled in and gets 422.
The conversion rule id is valid. The timestamp looks fine if you copied a Meta payload. The failure is an object you never sent.
{
"conversion": "urn:lla:llaPartnerConversion:104012",
"conversionHappenedAt": 1770000000000,
"user": {
"userInfo": {
"firstName": "mike",
"lastName": "smith"
}
},
"eventId": "abc12345"
}
LinkedIn documents user.userIds as required on every event. Matching on userInfo, lead, or externalIds does not replace it. When you are not sending hashed identifiers, send an empty list:
"user": {
"userIds": [],
"userInfo": {
"firstName": "mike",
"lastName": "smith"
}
}
Meta's Conversions API lets you omit whole blocks when another identity path is present. LinkedIn does not. That difference alone explains a lot of 422s after a CAPI port.
Milliseconds on the clock, URN on the rule
Two other copy-paste traps sit beside the missing array.
conversionHappenedAt is an epoch timestamp in milliseconds, thirteen digits. Meta's event_time is seconds, ten digits. Dividing Date.now() by 1000 and flooring it, which is the right fix on Meta, lands your LinkedIn event in 1970.
"conversionHappenedAt": 1770000000
LinkedIn also rejects events older than ninety days. A seconds value fails twice: wrong unit and outside the window.
The conversion field is not a numeric action id from Campaign Manager. It is a URN:
"conversion": "urn:lla:llaPartnerConversion:104012"
The rule must be created with conversionMethod set to CONVERSIONS_API. A pixel-only rule id in that slot is another 422.
Hash the email in userIds, not in userInfo
When you do populate userIds, each entry pairs idType with idValue. For email, idType is SHA256_EMAIL and idValue is the hex digest, not the address.
"userIds": [
{
"idType": "SHA256_EMAIL",
"idValue": "shopper@example.com"
}
]
LinkedIn matches on the digest. A raw address in idValue is both a privacy leak and an unmatchable identifier. Trim, lowercase, SHA-256, then send the hex with the documented idType.
userInfo fields like firstName are separate, lower-confidence signals. They do not replace a hashed email in userIds when that is what you have.
Value and currency travel together
conversionValue.amount is a decimal string, such as "50.0", not a JSON number with a currency symbol. If you send an amount, send conversionValue.currencyCode as a three-letter ISO code in the same object.
"conversionValue": {
"amount": "50.0"
}
Amount without currencyCode is incomplete on LinkedIn's side even when the HTTP client accepts the JSON.
How you catch it before 422
I maintain Pixellint, an open source linter for pixels and conversion API payloads. The LinkedIn Conversions API pack (vendor/linkedin-conversions-api) contracts single events and batched elements against Microsoft's published reference. On the missing userIds payload above:
$ pixellint validate json @linkedin-lead.json
rulepack: vendor/linkedin-conversions-api (vendor: linkedin)
error vendor.linkedin-conversions-api.body.user.userIds.missing
`user.userIds` is absent. LinkedIn returns a 422 when this
field is missing. Send it as an empty list if you match on
`lead`, `externalIds`, or `userInfo` instead.
fix: Add `user.userIds`. If you match on something else, send
it as an empty list: `"userIds": []`.
docs: https://learn.microsoft.com/en-us/linkedin/marketing/integrations/ads-reporting/conversions-api
A seconds timestamp prints vendor.linkedin-conversions-api.body.conversionHappenedAt.invalid. A raw email prints vendor.linkedin-conversions-api.body.unhashed_email. Amount without currency prints vendor.linkedin-conversions-api.body.value_needs_both_fields.
cargo install pixellint
# or: npm install pixellint
Paste a payload into the browser playground if you do not want a local install. Nothing you paste leaves the browser.
Where to go next
The LinkedIn Conversions API pack page lists every parameter the pack checks, with links back to Microsoft's docs. For cross-vendor identity rules (what to hash, what to leave plain, click ids versus cookies), read Identity for ads. LinkedIn wants milliseconds while Meta and Pinterest want seconds; the event time guide maps those units side by side so you are not guessing from one vendor's sample.
Server-side conversion wiring (dedup ids, batch shapes, when to fire from the order server) lives in Conversions API payloads. The docs index links the rest of the vendor packs if you are linting more than one endpoint in CI.
The short version
Send user.userIds on every LinkedIn conversion event. Use [] when you match on lead or userInfo. conversionHappenedAt is Date.now() in milliseconds, not Meta's seconds. conversion is a urn:lla:llaPartnerConversion:ID. Hash email into userIds with SHA256_EMAIL. Pair conversionValue.amount with currencyCode.
Pixellint is independent of LinkedIn. The rule ids above cite Microsoft's Conversions API docs because that is where the requirements live, not because this is an official tool.
Top comments (0)