LinkedIn Account Engagement: Which Companies Saw Your Ads, Matched to Your CRM
B2B buyers see your LinkedIn ads at work and book the meeting three weeks later from a branded search. Click attribution gives LinkedIn nothing, the CFO asks why the line item exists, and the campaign gets cut. We have watched that happen at customers who were, by every other signal, winning the accounts LinkedIn had been warming up.
The fix is to stop asking "which clicks converted" and start asking "did the accounts we are selling to see the ads, and did those accounts progress". That needs company-level engagement joined to the CRM. Today Cogny does that with one tool call.
What shipped
get_company_engagement on the LinkedIn Ads MCP server. Give it an ad account, a date range and optionally your CRM's company list. It returns:
- Every company LinkedIn reports as having seen the ads, ranked by impressions, with clicks, click-through rate, engagements and, where LinkedIn allows it, landing-page clicks, conversions and cost.
- Each company resolved to its name, LinkedIn page slug, website and domain.
- With
time_granularity="DAILY":first_seen,last_seenandactive_days, which is what "engaging this week" needs. - A
matchblock when you passcrm_records: the matched pairs, the method used for each (linkedin_url, thendomain, then normalized name with legal suffixes like AB, Inc and GmbH stripped), the CRM accounts that did not match, and the count of engaged companies your CRM has never heard of.
The matching is deterministic code, not the model eyeballing two lists. The source is services/mcp-proxy/src/servers/linkedin_ads/tools/company_engagement.py.
A weekly template, "LinkedIn Account Engagement", in the Executive & Strategy group. It requires LinkedIn Ads plus HubSpot or Upsales and produces:
- Contact this week: accounts with an open deal that engaged in the last 7 days, and target accounts warming up with no deal yet, each with the CRM owner, stage and a one-line outreach angle.
- Reach to pipeline: the meeting-booked and stage-advanced rate for reached accounts versus not-reached accounts, with the counts, the ratio and a plain statement that it is correlation until you run a holdout.
- Campaign fit: the share of each campaign's impressions that landed on the target list, so audience expansion and mis-targeting show up as a number.
- New accounts worth a look: engaged companies missing from the CRM.
- Data quality: match rate by method and the CRM accounts missing a domain.
Why this uses the public API, and what that means
LinkedIn has a partner-only Company Intelligence API that adds organic Page engagement to the same picture. As of September 2026 LinkedIn is not accepting new applications for it. We asked; the answer was to use adAnalytics. So we did.
The public Ads Reporting API supports a MEMBER_COMPANY pivot: impressions and clicks broken down by the viewer's current employer. That is the paid half of the question, and the half that matters for "did our spend reach the accounts we care about". Three constraints come with it, and the tool states them in every response:
| Constraint | Effect |
|---|---|
| Companies with fewer than 3 events are dropped | Small accounts are under-counted, not absent by choice |
| Only the top 100 companies per creative per day are kept | Very broad campaigns lose the long tail |
| Metrics are approximate and lag 12 to 24 hours | Do not reconcile them to the penny against Campaign Manager |
The template's language follows from this: "was reached by", never "converted from"; "not visible in LinkedIn's company report", never "never saw the ads".
What you do not get is organic: which companies visited your Page or engaged with unpaid posts. That is LinkedIn's Community Management product, which we are applying for separately, and it will slot into the same tool when it lands.
Using it from an agent
From Claude, Cursor, or the Cogny CLI with LinkedIn Ads and HubSpot connected:
get_company_engagement(
ad_account_id="503770488",
start_date="2026-09-01",
end_date="2026-09-28",
time_granularity="DAILY",
crm_records=[
{"id": "1201", "name": "Acme AB", "domain": "acme.se",
"linkedin_url": "https://www.linkedin.com/company/acme-ab"},
{"id": "1202", "name": "Beta Holding", "domain": "beta.example"}
]
)
The response's match.matched[] entries each carry the CRM record you passed in, the LinkedIn company row, and method. Domain and LinkedIn-URL matches are reliable; name matches are worth a glance. If your match rate is under 40%, the usual cause is empty domain fields in the CRM, and the unmatched_crm list tells you which ones to fill in.
What to do with it
Give the "contact this week" list to whoever owns the deals, every Tuesday morning. Give the reach-to-pipeline number to whoever approves the budget. Then run the holdout: exclude a random slice of target accounts from LinkedIn targeting for a quarter, and let the difference in booking rate settle the attribution argument for good.