LinkedIn's Conversions API is harder to set up than Meta's or Google's. OAuth tokens, strict URN formatting, and a professional identity graph that matches differently. This guide covers every layer — authentication, event mapping, deduplication, and the li_fat_id persistence problem that kills B2B match rates.
The LinkedIn Conversions API (CAPI) is a direct connection between your server and LinkedIn's ad platform. Instead of tracking conversions through a browser pixel, your server sends the data directly to LinkedIn after a purchase or lead event fires.
This matters because browser tracking is unreliable. Ad blockers, Apple's Intelligent Tracking Prevention (ITP), and iOS privacy changes all strip data from client-side pixels. When signals disappear, LinkedIn's algorithm can't optimize. ROAS looks wrong. Retargeting audiences shrink.
The CAPI moves conversion data off the browser entirely. LinkedIn gets the data regardless of what the visitor's device or browser does.
Meta's Conversions API matches on a consumer identity graph — personal emails, phone numbers, location. Google Enhanced Conversions matches on search intent and click IDs. LinkedIn matches on a professional identity graph: work emails, job titles, company names, and its own click ID called li_fat_id. The matching logic is fundamentally different, which is why LinkedIn CAPI requires different data preparation.
The LinkedIn Insight Tag is a browser-based pixel. It captures page views, session data, and early-funnel behavior. The CAPI captures confirmed conversions from your server. Running both together is the recommended hybrid model.
The Insight Tag handles top-of-funnel visibility. The CAPI guarantees transaction data arrives — even when the browser pixel gets blocked. When both fire for the same event, LinkedIn deduplicates them automatically.
eventId → LinkedIn keeps 1 conversion, discards the server-side duplicate.This is where LinkedIn CAPI gets harder than Meta or Google. LinkedIn requires OAuth 2.0 authentication for every API call. There is no simple static API key you can drop into a config file and move on.
You have two paths depending on your situation:
If you manage your own ad account, you can generate a non-expiring access token directly inside LinkedIn Campaign Manager. Go to Signals Manager → Conversions → Settings → Generate Access Token. This token works immediately and does not require building an OAuth app.
The token must carry two specific OAuth scopes: rw_conversions (read-write conversions) and r_ads (read ads). The user who generates the token must also have an explicit role on the ad account — Account Manager, Campaign Manager, or Billing Admin. A token with missing scopes returns a 403 error and will silently drop all your data.
Agencies managing multiple client accounts need to build a proper OAuth application. This requires registering through the LinkedIn Developer Portal, applying for the Conversions API product, verifying your company page, and implementing a 3-legged OAuth flow. This process takes longer and requires LinkedIn approval.
Every API call must include these headers exactly. Missing any one of them causes a hard rejection:
Authorization: Bearer YOUR_ACCESS_TOKEN Content-Type: application/json LinkedIn-Version: 202405 X-Restli-Protocol-Version: 2.0.0LinkedIn uses a fixed set of conversion event types. You must map WooCommerce actions to these names exactly. The wrong name means data is accepted but never attributed to a campaign.
| WooCommerce Action | LinkedIn Event | Notes |
|---|---|---|
| Form submission / contact | Lead | Primary B2B event. Maps to any lead gen form on WordPress. |
| Account registration | Sign Up | New user account created in WooCommerce. |
| Item added to cart | Add To Cart | Recommended for ecommerce; helps train the algorithm. |
| Payment confirmed | Purchase | Fire from woocommerce_payment_complete hook — not the thank you page. |
Below is the full Purchase payload structure for the LinkedIn CAPI endpoint at /rest/conversionEvents:
{ "conversion": "urn:lla:llaPartnerConversion:CONVERSION_RULE_ID", "conversionHappenedAt": 1700000000000, // UNIX ms — NOT seconds "eventId": "wc_order_5291", // Must match Insight Tag exactly "conversionValue": { "currencyCode": "USD", // ISO 4217 "amount": "149.00" // String, not float }, "user": { "userIds": [{ "idType": "SHA256_EMAIL", "idValue": "SHA256_HASH_OF_LOWERCASED_EMAIL" }, { "idType": "LINKEDIN_FIRST_PARTY_ADS_TRACKING_UUID", "idValue": "VALUE_OF_li_fat_id" }], "userInfo": { "firstName": "John", // Plain text, no hashing "lastName": "Smith", "companyName": "Acme Corp", "title": "Marketing Director", "countryCode": "US" } } }LinkedIn requires conversionHappenedAt in milliseconds, not seconds. PHP's time() returns seconds. You must multiply by 1000. Sending seconds causes a 400 INVALID_CONVERSION_TIME_FIELD_VALUE error that is easy to miss in logs.
LinkedIn matches server events to user profiles using identifying data you include in the payload. The more identifiers you send, the higher your match rate. But not all identifiers are equal — and one in particular is critical for B2B attribution.
li_fat_id value from the URL parameter or first-party cookie. This is deterministic — if present, attribution is near-certain. See the persistence problem below.When someone clicks a LinkedIn ad, LinkedIn appends li_fat_id to the destination URL. This is the strongest match key you have. The problem: browser-based cookies get deleted by ITP within 7 days. B2B sales cycles last weeks or months.
If you rely on a browser cookie to preserve li_fat_id, it will be gone long before the WooCommerce purchase fires. Your server payload arrives with no deterministic identifier — only hashed email — and match rates collapse.
When a visitor lands from a LinkedIn ad, capture li_fat_id from the URL immediately. Store it in a server-side, HTTP-only cookie or in the WooCommerce session/user meta. When the purchase event fires — weeks later, if needed — retrieve it from storage and include it in the API payload. This is what separates a 4/10 match score from an 8+/10 match score.
Running the same li_fat_id problem with Meta? The same persistence architecture applies to Facebook's Conversions API. Our fix guide covers both platforms side by side.
Meta CAPI Dedup Fix →Four main approaches exist. Your choice depends on traffic volume, team capacity, and how much control you need over the payload.
Plugins like PixelYourSite and Pixel Manager for WooCommerce support LinkedIn CAPI to varying degrees. They handle OAuth token storage and basic event mapping automatically. The tradeoff: you can't control the exact payload structure, and they may not support li_fat_id persistence correctly. Plugin updates also lag behind LinkedIn's API deprecation cycles.
Deploy a Google Tag Manager Server container on a custom subdomain like metrics.yourstore.com. The web container captures WooCommerce dataLayer events. The server container formats the payload and fires the LinkedIn CAPI call. This approach keeps tracking logic off your WordPress server and gives you full payload control through a visual interface.
Stape.io has a fixed monthly cost and a visual payload log — you can inspect historical events without writing SQL. Google Cloud Run is cheaper at scale but requires query tools to debug. For most WordPress stores, Stape is faster to set up. Full comparison: Stape vs Google Cloud for GTM →
Map it to metrics.yourstore.com. Requests from the browser are treated as same-origin — ITP cannot block them. This is also how the li_fat_id cookie survives longer than 7 days.
Find it in the GTM Community Template Gallery. Add your access token and the Conversion Rule URN from LinkedIn Campaign Manager → Signals Manager.
WooCommerce and LinkedIn use different field names. Use GTM variables to transform them: order_id → eventId, total → conversionValue.amount, billing_email → hashed SHA256_EMAIL.
SHA-256 hashing must happen in the web container — before data leaves the browser. Raw PII should never appear in server logs or transit between containers.
Capture li_fat_id on first page load and store it server-side. Include it in the GTM dataLayer push on the thank you page so the server container can pick it up.
For teams that want full control without middleware, hook into woocommerce_payment_complete directly. This hook fires when an order transitions from pending to processing — server-side, with no dependency on the browser.
add_action( 'woocommerce_payment_complete', function( $order_id ) {
$order = wc_get_order( $order_id );
$email = strtolower( trim( $order->get_billing_email() ) );
$hashed_email = hash( 'sha256', $email );// Retrieve persisted li_fat_id from user/session meta $li_fat_id = get_post_meta( $order_id, '_li_fat_id', true );$payload = [ 'conversion' => 'urn:lla:llaPartnerConversion:YOUR_RULE_ID', 'conversionHappenedAt' => time() * 1000, // ms! 'eventId' => 'wc_order_' . $order_id, 'conversionValue' => [ 'currencyCode' => $order->get_currency(), 'amount' => (string) $order->get_total()
], 'user' => [ 'userIds' => [
[ 'idType' => 'SHA256_EMAIL', 'idValue' => $hashed_email ],
[ 'idType' => 'LINKEDIN_FIRST_PARTY_ADS_TRACKING_UUID', 'idValue' => $li_fat_id ]
]
]
];wp_remote_post( 'https://api.linkedin.com/rest/conversionEvents', [ 'headers' => [ 'Authorization' => 'Bearer ' . YOUR_TOKEN, 'Content-Type' => 'application/json', 'LinkedIn-Version' => '202405', 'X-Restli-Protocol-Version' => '2.0.0',
], 'body' => json_encode( $payload ),
]);
} );The woocommerce_thankyou hook fires on the browser-rendered thank you page. Page caches, off-site gateways, and mobile users closing early can all prevent it from executing. Use woocommerce_payment_complete instead — it fires server-side, guaranteed, once payment clears.
Need this built correctly? Our tracking integrity service covers full CAPI implementation — authentication, payload mapping, li_fat_id persistence, and match quality validation.
View Tracking Services →When both the Insight Tag and the CAPI fire for the same event, LinkedIn uses the eventId field to merge them. Both payloads must carry the exact same string. One character difference means two conversions get recorded instead of one.
LinkedIn's dedup logic has one important quirk: when it finds a duplicate, it keeps the browser-side Insight Tag event and discards the server-side CAPI event. This is the opposite of Meta's behavior (Meta prefers the server signal). A working LinkedIn setup will show more conversions attributed to the Insight Tag than the CAPI in your dashboard — that is normal and correct.
| Attribution Type | Default Window | Max Configurable |
|---|---|---|
| Post-click | 30 days | 90 days (standard) / 365 days (Purchase) |
| View-through | 7 days | 30 days |
| Deduplication window | 48 hours | Fixed |
For B2B lead gen, the extended 365-day Purchase window is critical. A prospect who clicked your LinkedIn ad in Q1 and signed a contract in Q4 can still be attributed — if you've preserved the li_fat_id and sent the offline conversion payload within the window.
LinkedIn CAPI errors fall into two types. Hard rejections come back as HTTP error codes — the payload never entered the system. Silent failures return 201 Created but the event never attributes to a campaign.
| HTTP Code | Error | Cause and Fix |
|---|---|---|
| 400 | INVALID_CONVERSION_TIME_FIELD_VALUE | Timestamp is in seconds, not milliseconds. Multiply by 1000. |
| 400 | INVALID_USER_IDENTIFICATION_FIELD_VALUE | No valid identifier in the payload. You need at least one of: SHA256_EMAIL, li_fat_id, or firstName+lastName. Empty strings cause this. |
| 401 | EMPTY_ACCESS_TOKEN | Authorization header missing or malformed. Check the Bearer token format. |
| 403 | USER_NOT_AUTHORIZED | Token missing rw_conversions scope, or the user doesn't have Campaign Manager role on the ad account. |
| 422 | Unprocessable Entity | The conversion rule URN is wrong, deleted, or the attribution window type is unsupported for that event. Verify the URN in Campaign Manager. |
| 201 | Unmatched (silent failure) | Payload accepted but identity didn't resolve. Check email normalization and li_fat_id capture. Look for "Unmatched" in Campaign Manager. |
| 500/504 | Server Error / Timeout | LinkedIn-side disruption. Implement exponential backoff retry logic in your PHP or GTM setup. |
Matched means LinkedIn tied the event to a real member profile and searched for ad interactions. Unmatched means the payload arrived correctly but identity resolution failed. The data is useless for attribution or algorithm optimization.
A successful 201 response does not mean a successful attribution. Always check the matched/unmatched breakdown in Campaign Manager, not just your server logs.
Most LinkedIn CAPI documentation assumes a single-event, single-session conversion. B2B doesn't work that way. A prospect clicks a LinkedIn ad, downloads a whitepaper, enters your CRM, and closes a deal four months later. The CAPI must handle both scenarios from the same WordPress installation.
For lead gen forms, fire the Lead event from the WooCommerce/WordPress backend when the form submits. Capture li_fat_id at the same time and store it with the contact record in your CRM. For the eventual purchase, retrieve the stored li_fat_id and include it in the CAPI payload — even if that payload fires months later.
When deals close offline — phone calls, in-person meetings, CRM stage changes — there is no browser session to track. The CAPI supports asynchronous uploads with up to a 365-day lookback for Purchase events. When a deal closes in your CRM, trigger a server-side API call with the historical li_fat_id or email from the original lead record. LinkedIn attributes the offline revenue back to the ad campaign that started the relationship.
Server-side tracking does not bypass consent requirements. Sending hashed email or li_fat_id still constitutes processing personal data under GDPR. Your Consent Management Platform must gate both the Insight Tag and the CAPI independently.
If a user declines advertising cookies, the woocommerce_payment_complete hook must be suppressed or fire without any PII — no email, no li_fat_id, no name. The consent state captured at checkout must be passed server-side and checked before the API call executes. This is your legal obligation, not LinkedIn's.
If you send a second Purchase event with the same eventId as the original order, LinkedIn treats it as a duplicate and discards it. You lose the upsell revenue data. Use a unique suffix for each additional transaction: wc_order_5291_bump_1.
LinkedIn scores your integration with an Event Match Quality (EMQ) score from 1 to 10. This measures how well your identifiers resolve to real LinkedIn member profiles.
LinkedIn doesn't process CAPI events instantly. Allow up to 24 hours for identity resolution, plus another 48 hours for attribution modeling to update Campaign Manager reports. Always use a 72-hour trailing window when comparing WooCommerce order counts to LinkedIn conversion totals. Real-time reconciliation will always show a gap — this is expected.
Need this implemented and validated? We build the full LinkedIn CAPI stack — OAuth setup, GTM server container, li_fat_id persistence, PII hashing, and EMQ validation — as part of our PPC technical ownership service.
Get in Touch →