Skip to content
Learni
View all tutorials
Marketing Digital

How to Launch LinkedIn Ads Campaigns in 2026

Lire en français

Introduction

LinkedIn Ads remains the top platform for B2B advertising in 2026, with over 1 billion professionals. Unlike Google Ads or Meta, it excels at targeting by job title, company, and skills—perfect for generating qualified leads. This intermediate tutorial guides you step by step: from Campaign Manager setup to automation via the Marketing Developer Platform API.

Why it matters: Average ROI hits 2.5x thanks to CPC of 5-9€ and ultra-precise targeting (e.g., "IT Director at French SMEs"). We cover 2026 updates like automated A/B tests and AI audience integration. Result: scalable, measurable campaigns ready to bookmark for your pro projects.

Get ready to go from beginner to expert in 30 minutes of reading + 1 hour of hands-on practice.

Prerequisites

  • LinkedIn Premium or Sales Navigator account (for Ads access).
  • Campaign Manager access (via linkedin.com/campaignmanager).
  • LinkedIn Developer app: Create one at developer.linkedin.com (Marketing Developer Platform).
  • Node.js 20+ and npm for API examples.
  • Test budget: 50-100€ to validate a campaign.
  • Basic HTTP/OAuth knowledge (intermediate level).

Initialize the Node.js Project for the API

setup.sh
mkdir linkedin-ads-api && cd linkedin-ads-api
npm init -y
npm install axios dotenv linkedin-api-client typescript @types/node ts-node
npm install -D @types/axios
cat > .env << EOF
LINKEDIN_CLIENT_ID=votre_client_id
LINKEDIN_CLIENT_SECRET=votre_client_secret
LINKEDIN_REDIRECT_URI=http://localhost:3000/auth/callback
EOF
cat > tsconfig.json << 'EOF'
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true
  }
}
EOF

This script sets up a complete Node.js project for interacting with the LinkedIn Marketing API. It installs axios for HTTP requests, dotenv for secrets, and configures TypeScript. Replace the placeholders in .env with your LinkedIn app credentials (obtained from developer.linkedin.com).

Set Up Your LinkedIn Developer App

  1. Go to developer.linkedin.com > My Apps > Create app.
  2. Select Marketing Developer Platform as the product.
  3. Add permissions: ad:read, ad:write, account:read, audience:read/write.
  4. In OAuth 2.0, add http://localhost:3000/auth/callback as the redirect URI.
  5. Note your Client ID/Secret for .env.
Tip: Check for 'Approved' status (may take 24h). Without it, API calls fail with 403.

Implement OAuth 2.0 Authentication

auth.ts
import axios from 'axios';
import * as dotenv from 'dotenv';
import { execSync } from 'child_process';

dotenv.config();

const CLIENT_ID = process.env.LINKEDIN_CLIENT_ID!;
const CLIENT_SECRET = process.env.LINKEDIN_CLIENT_SECRET!;
const REDIRECT_URI = process.env.LINKEDIN_REDIRECT_URI!;

// Step 1: Generate auth URL (manual for simplicity)
const authUrl = `https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=${CLIENT_ID}&redirect_uri=${encodeURIComponent(REDIRECT_URI)}&scope=r_ads%20r_ads_reporting%20w_organization_social%20r_organization_social&state=state123`;
console.log('Ouvrez ce lien:', authUrl);

// Step 2: Exchange code for access_token (run after callback)
async function getAccessToken(code: string): Promise<string> {
  const tokenResponse = await axios.post('https://www.linkedin.com/oauth/v2/accessToken', new URLSearchParams({
    grant_type: 'authorization_code',
    code,
    redirect_uri: REDIRECT_URI,
    client_id: CLIENT_ID,
    client_secret: CLIENT_SECRET,
  }), {
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  });
  return tokenResponse.data.access_token;
}

// Usage: node -r ts-node/register auth.ts
// Copy 'code' from callback URL, then:
// const token = await getAccessToken('CODE_HERE');
// console.log(token);

This code handles full OAuth 2.0 for LinkedIn: auth URL generation and code-to-token exchange. Run with npx ts-node auth.ts, copy the code from the callback, and get a token valid for 60 days. Pitfall: Without the state param, CSRF risk; always validate in production.

Create Your First Campaign via the Interface

Access Campaign Manager: linkedin.com/campaignmanager > Create campaign.

  1. Objective: Choose 'Leads' for native forms (13% average conversion rate).
  2. Budget: Daily 50€, Total 500€ – enable 'Cost per result' to optimize.
  3. Audience: 50k-300k members (e.g., France, "Marketing Manager", companies 50-200 employees). Avoid <10k (high cost).
  4. Placement: Turn on Audience Network for +20% reach.
Visual: Picture the 'Setup' screen with side tabs; audience matching shows a green score >70% for top performance.

Publish as draft, then move to API for scaling.

Create a Campaign via Marketing API

createCampaign.ts
import axios from 'axios';

const ACCESS_TOKEN = 'votre_access_token';
const ACCOUNT_ID = 'urn:li:sponsoredAccount:123456789'; // De /adAccounts

async function createCampaign() {
  const response = await axios.post(`https://api.linkedin.com/rest/adCampaigns`, {
    name: 'Campagne Test Leads 2026',
    status: 'DRAFT',
    campaignGroup: 'urn:li:sponsoredCampaignGroup:987654321',
    fundingSource: 'urn:li:sponsoredMarketplaceFundingSource:default',
    runSchedule: {
      start: '2026-01-15T00:00:00+00:00',
      end: '2026-02-15T23:59:59+00:00'
    },
    budgetAmount: {
      currency: 'EUR',
      amount: '500.00',
      microAmount: 50000000
    },
    bidAmount: {
      currency: 'EUR',
      amount: '5.00',
      microAmount: 500000
    },
    type: 'LEADS_GEN_FORM',
    offsiteSendTrackingValue: 'linkedin_ads_2026',
    conversionReportingGoal: 'LEAD',
  }, {
    headers: {
      'Authorization': `Bearer ${ACCESS_TOKEN}`,
      'Linkedin-Version': '202312',
      'Content-Type': 'application/json'
    }
  });
  console.log('Campaign ID:', response.data.id);
}

createCampaign();

This script creates a full Leads campaign via API v202312. Replace token/account/group IDs (get them via GET /adAccounts and /adCampaignGroups). Pitfall: 'Linkedin-Version' required, or 400 error; budget in micro-units (1€=1,000,000 micros).

Target and Launch the Audience

In Campaign Manager:

  1. Audience: Matched Audiences (upload CSV emails) + Lookalike (similar to your customers).
  2. Demos: Job titles (CEO), Skills (Salesforce), Seniority (Manager+).
  3. Exclusions: Your employees to avoid waste.

For A/B: Duplicate ad sets, test 2 creative variants (image vs. carousel).

Key 2026 metric: Lead Gen Forms fill 2x faster on mobile—prioritize it.

Define an Ad Set with Precise Targeting

createAdSet.ts
import axios from 'axios';

const ACCESS_TOKEN = 'votre_access_token';
const CAMPAIGN_ID = 'urn:li:sponsoredCampaign:123456789';

async function createAdSet() {
  const response = await axios.post(`https://api.linkedin.com/rest/adGroups`, {
    name: 'AdSet France Managers',
    status: 'DRAFT',
    campaign: CAMPAIGN_ID,
    targeting: {
      include: {
        audiences: [
          { 'urn:li:audience:multiPart': 'urn:li:audience:country:FR' },
          { 'urn:li:audience:member:currentTitle': [{ text: 'Manager', operators: ['IN'] }] },
          { 'urn:li:audience:companySize': [{ values: [51, 200] }] }
        ],
        orList: null
      },
      exclude: { audiences: [{ 'urn:li:audience:company': ['urn:li:company:123'] }] }
    },
    bidAmount: { currency: 'EUR', amount: '8.00', microAmount: 800000 },
    budgetAmount: { currency: 'EUR', amount: '200.00', microAmount: 20000000 },
  }, {
    headers: {
      'Authorization': `Bearer ${ACCESS_TOKEN}`,
      'Linkedin-Version': '202312',
      'Content-Type': 'application/json'
    }
  });
  console.log('AdSet ID:', response.data.id);
}

createAdSet();

Creates an ad set with advanced targeting: France, Managers, 51-200 employees. Use standard URNs (see LinkedIn docs). Pitfall: Too narrow targeting (<50k) triggers rejection; test with /adAnalytics for estimated reach.

Create and Associate a Sponsored Content Creative

createAd.ts
import axios from 'axios';
import FormData from 'form-data';

const ACCESS_TOKEN = 'votre_access_token';
const AD_SET_ID = 'urn:li:sponsoredAdGroup:987654321';

async function uploadAsset(assetPath: string, type: 'image' | 'video') {
  const form = new FormData();
  form.append('file', require('fs').createReadStream(assetPath));
  form.append('owner', 'urn:li:organization:123456789');
  form.append('registerUpload', 'true');
  const upload = await axios.post('https://api.linkedin.com/rest/images?action=registerUpload', form, {
    headers: { ...form.getHeaders(), 'Authorization': `Bearer ${ACCESS_TOKEN}`, 'Linkedin-Version': '202312' }
  });
  // Upload file to signed URL (simplified; implement full flow)
  console.log('Asset URN:', upload.data.value);
  return upload.data.value;
}

async function createAd(imageUrn: string) {
  const response = await axios.post(`https://api.linkedin.com/v2/adGroups/${AD_SET_ID}/ads`, {
    name: 'Ad Test Leads',
    status: 'ACTIVE',
    type: 'SPONSORED_CONTENT',
    visibility: { com.linkedin.ugc.ShareVisibility: 'PUBLIC' },
    childContent: {
      'com.linkedin.ugc.ShareContent': {
        shareCommentary: { text: 'Découvrez notre solution IA pour marketers !' },
        shareMediaCategory: 'IMAGE',
        media: [{ status: 'READY', originalUrl: imageUrn }],
        visibility: { 'com.linkedin.ugc.ShareVisibility': 'PUBLIC' }
      }
    }
  }, {
    headers: {
      'Authorization': `Bearer ${ACCESS_TOKEN}`,
      'X-Restli-Protocol-Version': '2.0.0',
      'Content-Type': 'application/json'
    }
  });
  console.log('Ad ID:', response.data.id);
}

// Usage: const img = await uploadAsset('./banner.jpg', 'image'); createAd(img);

Handles asset upload (image) and Sponsored Content ad creation. Requires form-data npm package. Pitfall: Different API versions (/v2 for ads); assets must be <5MB, JPG/PNG format.

Retrieve Performance Metrics (Reporting)

report.ts
import axios from 'axios';

const ACCESS_TOKEN = 'votre_access_token';
const CAMPAIGN_ID = 'urn:li:sponsoredCampaign:123456789';

async function getReport() {
  const response = await axios.get(`https://api.linkedin.com/rest/adCampaigns/${CAMPAIGN_ID}?q=analytics&analytics=(pivot:PIVOT_BY_campaign,dateRange:(start:20260101,end:20260131),projections:(campaign,status,impressions,clicks,costInLocalCurrency,conversions))`, {
    headers: {
      'Authorization': `Bearer ${ACCESS_TOKEN}`,
      'Linkedin-Version': '202312'
    }
  });
  console.log('Rapport:', JSON.stringify(response.data, null, 2));
}

getReport();

Fetches key metrics (impressions, clicks, cost, conversions) for a period. Use PIVOT_BY for granularity. Pitfall: Dates in YYYYMMDD format; quota 100k calls/day, cache in production.

Best Practices

  • Budget pacing: Use 'Accelerated delivery' only for launches; cap lifetime budget at 10x daily.
  • A/B testing: 50/50 split on 2 ad sets, pause the loser after 1000 impressions.
  • Retargeting: Warm audiences (video viewers 25%+) convert 3x better.
  • Compliance: No unrealistic promises; validate GDPR forms.
  • API: Rate limit 20 calls/sec; use webhooks for real-time events.

Common Errors to Avoid

  • Audience too broad/narrow: <20k = API rejection; >1M = CPC x2.
  • Missing API version: Always specify 'Linkedin-Version:202312' or get 400 Bad Request.
  • Expired token: Auto-refresh with refresh_token (60 days max); implement cron job.
  • No UTM: Add offsiteSendTrackingValue to track in GA4.

Next Steps