Enrich Your CRM with LinkedIn Profile Data (2026 Guide)
Backfill CRM contact records with LinkedIn job titles, companies, and skills using Thirdwatch. No login. Salesforce + HubSpot recipes.

Thirdwatch's LinkedIn Profile Scraper backfills CRM contact records with structured LinkedIn data — title, company, skills, certifications, location, summary — on a pay-per-profile basis. No login, no cookies, no CAPTCHA solver. Built for sales-ops teams maintaining Salesforce, HubSpot, and Pipedrive contact databases, customer-success teams enriching ABM accounts, and marketing teams building lookalike audiences.
Why enrich CRM with LinkedIn data
CRM contact data decays. According to Salesforce's 2024 State of Sales Operations report, B2B contact records lose accuracy at roughly 30% per year — job changes, company moves, and stale entries compound. For sales pipelines, marketing automation, and customer-success workflows, fresh job-title and company data is the difference between a relevant outreach and a returned-to-sender campaign.
The job-to-be-done is structured. A sales-ops team maintaining 25K Salesforce contacts wants quarterly enrichment refreshes to keep titles and companies current. A marketing-ops team building ABM lookalike audiences wants skills-array data per contact for segmentation. A customer-success team wants buyer-context flags (job change, promotion, company exit) per account in the renewal pipeline. A revenue-operations team wants to enrich newly-imported leads within 24 hours of CRM creation. All reduce to LinkedIn URL list + structured profile output → CRM upsert.
How does this compare to the alternatives?
Three options for CRM enrichment with LinkedIn data:
| Approach | Cost per 1,000 contacts | Reliability | Setup time | Maintenance |
|---|---|---|---|---|
| ZoomInfo / Lusha / Clearbit | $5–$15 per contact ($5K–$15K/1K) | High, with email/phone | Hours | Per-seat license |
| LinkedIn Sales Navigator (paid seat) | $1,000–$1,200/seat/year | Manual lookups | Hours | Per-seat per-month |
| Thirdwatch LinkedIn Profile Scraper | Pay per profile | Production-tested with anti-bot bypass | 5 minutes | Thirdwatch tracks LinkedIn changes |
ZoomInfo is the canonical CRM-enrichment vendor but the per-contact cost makes it impractical for high-volume operations. The LinkedIn Profile Scraper actor page gives you raw LinkedIn data at 500-1500x lower cost, with the trade-off that you handle CRM upsert wiring yourself.
How to enrich CRM with LinkedIn in 4 steps
Step 1: How do I authenticate against Apify?
Sign in at apify.com (free tier, no credit card), open Settings → Integrations, and copy your personal API token. Every example below assumes the token is in APIFY_TOKEN:
export APIFY_TOKEN="apify_api_xxxxxxxxxxxxxxxx"Step 2: How do I batch-enrich a list of LinkedIn URLs?
Pass an array of LinkedIn profile URLs.
import os, requests, pandas as pd
ACTOR = "thirdwatch~linkedin-profile-scraper"
TOKEN = os.environ["APIFY_TOKEN"]
# CRM contact list with linkedin_url already mapped
crm_df = pd.read_csv("salesforce_export.csv")
urls = crm_df.linkedin_url.dropna().tolist()
# Batch in chunks of 200 to stay under timeout
BATCH = 200
enriched = []
for i in range(0, len(urls), BATCH):
chunk = urls[i:i+BATCH]
resp = requests.post(
f"https://api.apify.com/v2/acts/{ACTOR}/run-sync-get-dataset-items",
params={"token": TOKEN},
json={"profileUrls": chunk},
timeout=3600,
)
enriched.extend(resp.json())
print(f"{len(enriched)} of {len(urls)} enriched")
enriched_df = pd.DataFrame(enriched)
enriched_df.to_csv("linkedin_enriched.csv", index=False)A 5,000-contact enrichment in batches of 200 takes ~25 wall-clock minutes — well within budget for a quarterly refresh.
Step 3: How do I upsert into Salesforce?
Match enriched data back to CRM by LinkedIn URL and update standard fields.
from simple_salesforce import Salesforce
sf = Salesforce(
username=os.environ["SF_USER"],
password=os.environ["SF_PASS"],
security_token=os.environ["SF_TOKEN"],
)
merged = crm_df.merge(enriched_df, left_on="linkedin_url", right_on="profileUrl", how="inner")
for _, row in merged.iterrows():
sf.Contact.update(row.contact_id, {
"Title": row.headline,
"Company__c": row.currentCompany,
"LinkedIn_Skills__c": ", ".join(row.skills[:10]) if isinstance(row.skills, list) else None,
"LinkedIn_Last_Enriched__c": pd.Timestamp.utcnow().isoformat(),
})
print(f"Upserted {len(merged)} Salesforce contacts")The same pattern works for HubSpot, Pipedrive, and Close.com — replace simple_salesforce with the respective SDK.
Step 4: How do I detect job changes for re-engagement?
Compare current company + title against the previous snapshot to flag changes.
prev = pd.read_csv("linkedin_enriched_previous.csv")
combined = enriched_df.merge(prev, on="profileUrl", suffixes=("", "_prev"))
changed = combined[
(combined.currentCompany != combined.currentCompany_prev)
| (combined.headline != combined.headline_prev)
]
print(f"{len(changed)} contacts changed jobs/titles")
print(changed[["profileUrl", "currentCompany_prev", "currentCompany",
"headline_prev", "headline"]].head(20))
# Forward to Slack for sales follow-up
import requests as r
for _, c in changed.iterrows():
r.post("https://hooks.slack.com/services/.../...",
json={"text": (f":briefcase: *{c.profileUrl}* changed: "
f"{c.currentCompany_prev} → {c.currentCompany}")})Job-change alerts are the highest-value re-engagement signal in B2B sales — buyers in transition are typically open to vendor evaluation in the first 90 days at a new role.
Sample output
A single LinkedIn profile record looks like this. Five rows weigh ~10 KB.
{
"profileUrl": "https://www.linkedin.com/in/johndoe",
"fullName": "John Doe",
"headline": "Director of Engineering at Stripe",
"currentCompany": "Stripe",
"currentTitle": "Director of Engineering",
"location": "San Francisco Bay Area",
"summary": "20+ years building payments infrastructure...",
"skills": ["Python", "Distributed Systems", "Payments", "API Design",
"Engineering Management", "PostgreSQL"],
"experience": [...],
"education": [...],
"certifications": [...]
}profileUrl is the canonical natural key for CRM upserts. currentCompany + currentTitle are the most-referenced fields for sales/marketing pipelines. skills array is the underrated segmentation field — combining a Director title + Analytics skill is a stronger ABM-targeting signal than title alone. summary works well as a personalisation seed for outreach copy.
Common pitfalls
Three things go wrong in production CRM-enrichment pipelines. URL drift — LinkedIn occasionally allows users to change their vanity URL (/in/johndoe → /in/john-doe-engineer); cache the canonical URL after first fetch and re-resolve URL changes via name + company match. Skills-array variance — LinkedIn's skill-tag system updates terms (e.g., "Big Data" rebranded to "Data Engineering"); for stable segmentation, normalise skills to a controlled vocabulary in the CRM rather than passing through raw. GDPR / CCPA compliance — LinkedIn data is technically public but EU/California regulations require a lawful basis for processing; for compliant enrichment, document a legitimate-interest assessment per regulation requirement.
Thirdwatch's actor uses a lightweight HTTP path so you pay only for the data, not for proxy or compute overhead. Pair LinkedIn Profiles with Career Site Job Scraper for full ABM enrichment (current title + employer's open roles + hiring velocity in one pipeline). A fourth subtle issue worth flagging: LinkedIn's currentCompany field reflects the most recent employer per the public profile, but for contacts who haven't updated their LinkedIn after a recent move, the field can lag reality by 60-90 days; cross-reference with company-domain in the email field for a sanity check before triggering high-value outbound. A fifth pattern unique to enrichment workflows: high-profile contacts (C-suite at well-known companies) sometimes have multiple LinkedIn profiles (personal + investor + advisory variants), and the actor returns whichever the URL points to; for accurate primary-employer enrichment, always pass the /in/{username} URL associated with their work email rather than discovering URLs via name search alone. A sixth and final pitfall: enrichment queues that retry profile-not-found failures too aggressively can trigger LinkedIn rate-limiting on your proxy pool; use a 24-hour cooldown after first failure and a 7-day cooldown after second failure before declaring a profile permanently inaccessible.
Related use cases
Frequently asked questions
What CRM fields can LinkedIn data populate?
Job title, current company, company URL, location, headline, summary, and skills array all map directly to standard Salesforce/HubSpot contact fields. The skills array often surfaces hidden buyer signals (e.g., a 'Data Analytics' skill on a Marketing Director suggests buying authority for analytics tools). Profile photo URL and certifications enrich high-touch outreach. Most teams populate 8-12 LinkedIn fields per CRM contact.
How does this avoid LinkedIn's anti-scraping defenses?
Thirdwatch's actor uses anti-bot bypass (anti-bot bypass: document, anti-bot bypass: navigate, anti-bot bypass: none) which let the request appear as a fresh navigation rather than scripted automation. LinkedIn returns the public JSON-LD profile data without requiring login or cookies. About 90-95% of public profiles return successfully on first try; failed ones retry with a different proxy IP.
Can I match by email or name to LinkedIn URL?
The actor accepts LinkedIn profile URLs directly — for CRM enrichment you need a URL-mapping step first. The cheapest path: pass contact email + name to a search endpoint (Hunter, Clearbit, or a Google search trick) to resolve the LinkedIn URL, then pass URLs to the actor. About 60-70% of B2B contacts have publicly-discoverable LinkedIn profiles via email-domain matching.
How fresh does CRM-enrichment data need to be?
For active sales/marketing pipelines, refresh every 90 days — that's the typical cadence at which a meaningful share of contacts change roles or companies. For dormant CRM segments (last activity 6+ months ago), refresh annually before re-engaging. New contact onboarding should enrich within 24 hours of CRM creation.
What's the cost for typical CRM enrichment?
Pay-per-profile pricing with volume tiers — cost drops at higher tiers. A quarterly refresh on a 5K-contact active list runs cheaply enough to budget without negotiation. Compare to ZoomInfo's per-seat $15K+ annual cost — for raw LinkedIn data, scraping is dramatically cheaper.
How do I handle profile-not-found cases?
About 5-10% of LinkedIn URL fetches return empty (private profile, deleted account, or LinkedIn temporarily blocking the URL). Mark these as 'enrichment_failed' in CRM and retry monthly — most resolve within 60 days. For privacy compliance, purge URL records that consistently fail for 6+ months from your enrichment queue.
Related
100 free credits, no credit card.
About 30 real searches. Add the MCP to Claude or Cursor in two minutes.