LinkedIn conversion tracking, Insight Tag or Conversions API
Both setups step by step, with working code for sending events and for keeping a conversion from counting twice.
The LinkedIn Insight Tag is JavaScript on your site that records visits and conversions from the visitor's browser. The Conversions API sends conversions from your server, Salesforce or HubSpot, including ones that happen offline, like a qualified lead or a closed deal. Run both on the same events with a shared event ID so LinkedIn counts each conversion once, and attach every conversion rule to the ad sets it should measure.
Insight Tag or Conversions API, the short answer
Start with the Insight Tag. LinkedIn's conversion tracking page says it's free, and website retargeting runs on the same tag anyway. Add the Conversions API, often shortened to CAPI, when the conversion you care about happens after the form, like a lead your sales team qualifies or a deal signed on the third call. The tag never sees those, because they happen in your sales tools long after the visitor left the site.
| Insight Tag | Conversions API | |
|---|---|---|
| Data source | JavaScript in the visitor's browser | Your server, Salesforce, HubSpot or a partner tool |
| Tracks | Page loads and on-site actions, such as a button click or a form submit | Anything you record, online or offline, such as a qualified lead or a closed deal |
| Who builds it | Whoever edits the site or the tag manager | A partner integration, or a developer: 2 to 4 weeks on average for a direct build |
| How it matches people | LinkedIn cookies and the li_fat_id click ID, plus hashed emails with enhanced matching | Emails hashed with SHA-256, the li_fat_id click ID, IP address, name and company |
| Longest conversion window | 90 days | 365 days for lead, purchase and a few other types |
| Data retention | Direct identifiers removed within 7 days, the rest deleted within 180 days | Deleted after 180 days, with only aggregate reporting kept |
| Role in deduplication | Its event is kept | Its copy is dropped when the event IDs match |
Sources, checked 2026-11-07: LinkedIn's Insight Tag FAQs, Conversions API overview and conversion window page, plus Microsoft Learn's Conversions API schema and deduplication guide.
LinkedIn's case for adding the API comes from its own beta. Across 829 campaigns run from July 1 to August 26, 2023, it reported 31% more attributed conversions and a 20% lower cost per action (LinkedIn Ads growth page). The page says nothing about method beyond a footnote with the sample and the dates.
Set up the Insight Tag
You need 1 tag for the whole site, and every Insight Tag conversion you create later reads from it, so put it in the template every page shares.
Install it by hand or with Google Tag Manager
In Campaign Manager, go to Measure > Signals manager > Insight Tag (LinkedIn Help). You can install it yourself, send it to a developer or use a tag manager. By hand, paste the code right before the closing </body> tag in your global footer. In Google Tag Manager, LinkedIn's Insight Tag 2.0 template asks for the partner ID shown with your tag.
Paste the code exactly as Campaign Manager gives it, with straight quotation marks. Some editors swap in curly ones on paste, and the tag then never verifies. Keep it off pages that collect or show sensitive data: pages that offer health or financial products to consumers, or where people manage medical appointments or financial accounts.
This is LinkedIn's base code with 1 line added on top, the event ID used later for deduplication. Use the partner ID from your own account.
<script>
// A new ID per page view, rendered by your server before the tag loads.
window._linkedin_event_id = "{{ eventId }}";
</script>
<script type="text/javascript">
_linkedin_partner_id = "12345";
window._linkedin_data_partner_ids = window._linkedin_data_partner_ids || [];
window._linkedin_data_partner_ids.push(_linkedin_partner_id);
</script>
<script type="text/javascript">
(function(l) {
if (!l){window.lintrk = function(a,b){window.lintrk.q.push([a,b])};
window.lintrk.q=[]}
var s = document.getElementsByTagName("script")[0];
var b = document.createElement("script");
b.type = "text/javascript";b.async = true;
b.src = "https://snap.licdn.com/li.lms-analytics/insight.min.js";
s.parentNode.insertBefore(b, s);})(window.lintrk);
</script>
<noscript>
<img height="1" width="1" style="display:none;" alt="" src="https://px.ads.linkedin.com/collect/?pid=12345&fmt=gif" />
</noscript>Check it's firing
LinkedIn checks the tag once a page that carries it gets a visit, which takes anywhere from a few minutes to 24 hours. The status sits under Measure > Signals manager > Insight Tag (LinkedIn Help). Active means LinkedIn got a signal in the past 7 days. No recent activity means the last 1 is older than that, and Unverified means the tag has never sent anything.
For a quicker check, open your browser's developer tools, filter the Network tab for ads.linkedin.com and reload. LinkedIn's troubleshooting page says that request should return status 302.
Create an Insight Tag conversion
Go to Measure > Conversion tracking > Create conversion > Insight Tag conversion (LinkedIn Help). Name the rule and set its category, value, click and view windows and attribution model. Then pick how it counts:
- Page load counts a visit to pages you name by address, such as
/demo/thank-you. Use it when a form ends on its own thank-you page. - Event-specific counts an action, such as a button click, through a line of code Campaign Manager generates. It's a
window.lintrk("track", { conversion_id: ... })call.
If your forms collect emails, turn on enhanced matching. The tag then hashes the email on your site and sends it with the visit, and LinkedIn says it only matches information it already has (Insight Tag FAQs). Check enhanced conversion tracking too. It adds the li_fat_id click ID to ad clicks and keeps it in a first-party cookie for 30 days, and it's on by default for recently created tags (Microsoft Learn). The Conversions API uses that ID to match people.
Set up the Conversions API
The API needs 3 things in order: a conversion rule, the ad sets attached to it, and a stream of events (Microsoft Learn). With a partner tool you mostly fill in forms. A direct build makes those requests from your own code, shown below with curl and Node.
Partner or direct
LinkedIn lists 14 partners on its Conversions API page and calls them typically fast to set up, though some may charge. Its estimate for a direct build is 2 to 4 weeks on average, plus developer time to maintain it afterward.
| Route | Options LinkedIn lists | Pick it when |
|---|---|---|
| Partner: the app that holds the record | HubSpot, Salesforce Data 360, Zoho Recruit | The stage or hire you want to send already lives there |
| Partner: data pipelines | Adobe, Hightouch, Segment, Tealium | Your events already flow through 1 of them |
| Partner: automation | Zapier, Make | A handful of events and no developer |
| Partner: tag management | Google Tag Manager | Your site's tags already live there |
| Partner: attribution and reporting | Dreamdata, Factors.ai, Funnel, Supermetrics | You already pay for 1 of them |
| Direct build | Your own code against the API | You need custom events, your own matching logic or no extra vendor |
Create the conversion rule
In Campaign Manager, go to Measure > Conversion tracking > Create conversion > Conversions API (LinkedIn Help). The dialog asks for a name, a category such as Lead, a value, click and view windows and an attribution model, then for the ad sets to attach.
Through the API, it's 1 request to POST /rest/conversions with conversionMethod set to CONVERSIONS_API. Set the windows yourself. Leave them out and the API applies 30 days after a click and 7 after a view (schema), shorter than Campaign Manager's 90 and 90. The optional autoAssociationType parameter attaches ad sets while the rule is created, covered below.
curl -X POST 'https://api.linkedin.com/rest/conversions?autoAssociationType=OBJECTIVE_BASED' \
-H "Authorization: Bearer $LINKEDIN_TOKEN" \
-H 'Linkedin-Version: 202610' \
-H 'X-Restli-Protocol-Version: 2.0.0' \
-H 'Content-Type: application/json' \
--data '{
"name": "Demo request - Conversions API",
"account": "urn:li:sponsoredAccount:5123456",
"conversionMethod": "CONVERSIONS_API",
"type": "LEAD",
"postClickAttributionWindowSize": 90,
"viewThroughAttributionWindowSize": 90,
"attributionType": "LAST_TOUCH_BY_CAMPAIGN"
}'The new rule's ID comes back in the x-restli-id header, and events point at it as urn:lla:llaPartnerConversion:<id>. The token needs the rw_conversions and r_ads scopes from someone with a role on the ad account above viewer. Getting a LinkedIn app and token in the first place is covered in the LinkedIn Ads API guide.
Send events
Each event goes to POST /rest/conversionEvents with the rule, a timestamp in milliseconds from the past 90 days, and at least 1 way to recognize the person. That can be a SHA-256 hashed email, the li_fat_id click ID (as LINKEDIN_FIRST_PARTY_ADS_TRACKING_UUID), an IP address, a LiveRamp ID or a Google advertising ID. userInfo adds name, title, company and country for looser matching. Only matched events count toward attribution and optimization, so LinkedIn asks for every identifier you have.
{
"conversion": "urn:lla:llaPartnerConversion:104012",
"conversionHappenedAt": 1793889120000,
"eventId": "7b1e4c2a-5d2f-4a8e-9c61-3f0d2e8a9b47",
"user": {
"userIds": [
{ "idType": "SHA256_EMAIL", "idValue": "f5430f1e66e14ec2682cf1685392753f8609a9042eba435b3711294b0655d09d" },
{ "idType": "LINKEDIN_FIRST_PARTY_ADS_TRACKING_UUID", "idValue": "df5gf5-gh6t7-ph4j7h-fgf6n1" }
],
"userInfo": {
"firstName": "Ana",
"lastName": "Ruiz",
"title": "Head of Operations",
"companyName": "Northwind Analytics",
"countryCode": "US"
}
}
}For volume, batch the events. Add the X-RestLi-Method: BATCH_CREATE header and wrap the events in an elements array, up to 5,000 per request. A member token gets 600 requests a minute and 500,000 a day before LinkedIn throttles it, and batches keep a big sync well inside both.
const URL = "https://api.linkedin.com/rest/conversionEvents";
export async function sendEvents(token, events) {
for (let i = 0; i < events.length; i += 5000) {
const res = await fetch(URL, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
"Linkedin-Version": "202610",
"X-Restli-Protocol-Version": "2.0.0",
"X-RestLi-Method": "BATCH_CREATE",
},
body: JSON.stringify({ elements: events.slice(i, i + 5000) }),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
}
}1 malformed event fails the whole batch. The error names it by batchIndex, and you resend everything after fixing it (Microsoft Learn).
Hash emails with SHA-256
LinkedIn wants the email lowercased and stripped of spaces, then hashed with SHA-256 into a 64-character hex string. Hash it on your server, before it leaves. Both snippets turn " Ana.Ruiz@Northwind-Analytics.example " into the hash in the event above.
import { createHash } from "node:crypto";
export const sha256Email = (email) =>
createHash("sha256").update(email.trim().toLowerCase()).digest("hex");import hashlib
def sha256_email(email: str) -> str:
return hashlib.sha256(email.strip().lower().encode("utf-8")).hexdigest()Run both without double counting
LinkedIn recommends sending the same events through both the tag and the API, a setup it calls redundant. Left alone, each demo request would then count twice. Deduplication needs 2 things (Microsoft Learn):
- A separate conversion rule for each source, such as "Demo request - Insight Tag" and "Demo request - Conversions API".
- 1 ID per event, sent from both sides:
window._linkedin_event_id(orevent_idin alintrkcall) in the browser,eventIdfrom the server.
When an Insight Tag event and a Conversions API event arrive from the same account with the same ID, LinkedIn keeps the Insight Tag event and takes the duplicate off the API rule's count. Both rules show in the conversion breakdown. If deduplication works, the Insight Tag rule usually has the higher count, and the API rule holds what the browser missed.
Creating the ID on the server keeps both sides in step. When the form posts, make the ID, send the API event with it, and render it into the thank-you page above the tag so the page load conversion carries the same value.
import { randomUUID } from "node:crypto";
import { sha256Email } from "./hash.mjs";
import { sendEvents } from "./send-events.mjs";
app.post("/demo", async (req, res) => {
const eventId = randomUUID();
const userIds = [{ idType: "SHA256_EMAIL", idValue: sha256Email(req.body.email) }];
const clickId = req.cookies.li_fat_id; // first-party cookie from enhanced conversion tracking
if (clickId) userIds.push({ idType: "LINKEDIN_FIRST_PARTY_ADS_TRACKING_UUID", idValue: clickId });
await sendEvents(process.env.LINKEDIN_TOKEN, [{
conversion: "urn:lla:llaPartnerConversion:104012", // the Conversions API rule
conversionHappenedAt: Date.now(),
eventId,
user: { userIds },
}]);
res.render("thank-you", { eventId }); // sets window._linkedin_event_id above the tag
});With Google Tag Manager, push the ID to the data layer before the container loads and map it to the Event ID field in LinkedIn's template.
Send pipeline stages from Salesforce or HubSpot
The tag stops at the thank-you page. Whether that demo request turned into a sales qualified lead or a signed contract is recorded in Salesforce or HubSpot, and the API is how LinkedIn hears about it.
From API version 202608, conversion rules can use 2 pipeline types, MARKETING_QUALIFIED_LEAD and SALES_QUALIFIED_LEAD, next to the older QUALIFIED_LEAD (schema). Qualified leads also work as an optimization goal for Lead Generation and Website Conversion ad sets. LinkedIn recommends setting up the Conversions API before you pick that goal (LinkedIn Help).
3 details decide whether stage events count:
- The window. Conversions API and CSV rules for lead, qualified lead, marketing qualified lead, sales qualified lead, purchase, add to cart and submit application can use 180 or 365-day windows. Insight Tag rules stop at 90 (LinkedIn Help).
- The timestamp. The event itself must have happened in the past 90 days when you send it. Sync stage changes daily. The long window is about how far back the ad click can be.
- The identity. A stage change in Salesforce has no browser attached, so send the hashed email, and for a Lead Gen Form lead, its form response URN in
lead. Once anexternalIdsvalue, such as the Salesforce lead ID, matches a member alongside other identifiers, LinkedIn keeps that mapping for 1 year across ad accounts in the same Business Manager. Later stage events can carry the external ID alone.
Before anyone builds or buys a sync, Campaign Manager also takes conversions as a CSV upload. LinkedIn says uploaded data usually shows in reporting within 24 to 48 hours and sometimes takes a week (LinkedIn Help).
Adwarp's Salesforce and HubSpot integrations run the other way. They turn a query or a list into a LinkedIn matched audience, hashing emails on your machine, and don't send conversion events.
Attach every conversion to its ad sets
"Conversions will only be tracked when attached to an ad set," LinkedIn's help page says. A rule with a clean stream of events and no ad sets reports 0. The API calls ad sets campaigns, so its attachment settings say campaign.
You can attach a rule in 3 places:
- In Campaign Manager, on the rule under Conversion tracking, or while you build an ad set, where LinkedIn preselects conversions that fit its objective.
- When you create the rule through the API, with
autoAssociationType.ALL_CAMPAIGNSattaches up to 200 ad sets in the account,OBJECTIVE_BASEDattaches every ad set whose objective fits the rule's type with no cap, and both include active, paused and draft ad sets. Leave it out and nothing is attached. - Afterward through the API, 1 ad set at a time or in a batch, with
PUT /rest/campaignConversions.
Then check the 2 settings that shape the count. Campaign Manager's default window is 90 days after a click and 90 after a view, and a change applies only to conversions from then on (LinkedIn Help). Under "Last Touch - Each ad set", every ad set the person interacted with in the window gets the conversion. "Last Touch - Last ad set" gives it to the most recent 1 only (LinkedIn Help). With Each ad set, adding conversions across ad sets overstates the total.
Adwarp only touches this step. list_conversions (adwarp conversions list in the CLI) shows the rules on the account with ID, on or off, type and name. create_campaign takes conversionIds, and launch_from_brief also takes a conversionName. With neither, Adwarp attaches the rules named in defaultConversionNames in your config. It adds the new ad set to each rule's existing list without dropping the ones already there, and every ad set it creates starts as a draft.
> Which conversion rules can the new ops leaders ad set track?
adwarp.list_conversions default account: 4 rules
9100201 on LEAD Demo request - thank you page
9100202 on KEY_PAGE_VIEW All site page views
9100203 on LEAD Demo request (old site)
9100204 on SALES_QUALIFIED_LEAD Sales qualified lead - Salesforce
All site page views counts every page load. Keep it off a demo ad set.
> Draft the ad set with the demo request and sales qualified lead rules.
adwarp.create_campaign conversionIds 9100201, 9100204: DRAFT
2 conversion rules attached to the draft ad set.
Troubleshooting
When conversions go missing, check the tag first, then the rule, the attachment and the event payload, in that order.
| Symptom | Likely cause | Fix | Source |
|---|---|---|---|
| Tag shows Unverified | No visit yet, a partial copy, curly quotes, or blocked LinkedIn domains such as snap.licdn.com | Recopy the code, visit a tagged page, wait up to 24 hours, check the Network tab | a425696 |
| Tag shows No recent activity | No signal for more than 7 days, for example after a site rebuild dropped the footer code | Reinstall the tag in the shared template | a488323 |
| Conversions stay at 0 | The rule isn't attached to the ad set, or it's turned off | Attach it under Conversion tracking and turn it on | a1657171 |
| API error "Conversion time should be within 90 days" | An old event, or a timestamp in seconds | Send within 90 days and add 3 zeros to second timestamps | Microsoft Learn |
| API error "The request must contain one of these fields" | No user identifier in the event | Add a hashed email, li_fat_id or another ID type | Microsoft Learn |
| API error about a conversion "marked as deleted" | The rule was deleted | Create a new rule and point events at its ID | Microsoft Learn |
| API rule outcounts the tag rule | Event IDs differ between browser and server, or both sources share 1 rule | Log the ID on both sides, keep 1 rule per source | Microsoft Learn |
| Clicks but no conversions | Conversion page untagged, or a redirect or single-page app strips li_fat_id from the address | Tag the page, keep li_fat_id through redirects and routing | a476761 |
| Recent days look low | Reporting lag, 24 to 48 hours for CSV uploads | Leave the last 2 to 3 days out of judgments | a422513 |
Watch for the opposite problem too. A rule that counts every page view, attached next to demo requests, blends both into 1 cost per conversion. Adwarp's performance review skill asks which conversion actions the Conversions column counts before it judges an ad set on cost per conversion, and the audit skill checks tag status and which rules each ad set carries.
Put it to work
LinkedIn Ads reporting
Pull spend, click-through rate and cost per conversion by campaign, ad set or ad from your terminal.
adwarp.dev/linkedin-ads/reportingBy Stan Rymkiewicz, maintainer of adwarp. Published November 7, 2026.