Marketing & Advertising8 min read

How to Integrate LinkedIn Ads with Your Headless CMS

Connect LinkedIn Ads to your headless CMS so approved campaign copy, landing page URLs, images, UTMs, and disclaimers can move from editorial workflow to paid B2B campaigns without copy-paste errors.

Published April 29, 2026
01Overview

What is LinkedIn Ads?

LinkedIn Ads is LinkedIn’s advertising platform for running Sponsored Content, Message Ads, Dynamic Ads, lead generation forms, and matched audience campaigns across LinkedIn’s professional network. It’s commonly used by B2B marketing teams that target by company, job title, seniority, industry, skills, and account lists. Campaign Manager handles budgeting, targeting, creative review, reporting, and lead capture, while the LinkedIn Marketing API lets approved apps create and update campaign assets programmatically.


02The case for integration

Why integrate LinkedIn Ads with a headless CMS?

If your team runs 20 LinkedIn campaigns a quarter, the slow part usually isn’t buying media. It’s keeping campaign copy, landing page URLs, hero images, UTM parameters, legal disclaimers, and regional variations aligned across your website, ad creative, and reporting spreadsheets. A disconnected workflow means someone publishes a landing page, someone else copies text into Campaign Manager, another person updates UTMs, and a fourth person checks whether the ad still matches the page. That works until a headline changes after launch or a compliance note gets missed.


03Architecture

Architecture overview

A typical flow starts when an editor publishes a `linkedinAd` document in Sanity Studio. A webhook filtered with GROQ, for example `_type == "linkedinAd" && defined(linkedin.campaignUrn)`, sends the document ID to a Sanity Function or your own webhook endpoint. The Function uses `@sanity/client` to fetch the latest approved fields from the Content Lake, including referenced landing page data, image asset URLs, UTM values, and the LinkedIn sponsored campaign URN. It then calls LinkedIn’s REST Marketing API with OAuth, usually `POST /rest/posts` to create Direct Sponsored Content for the organization and `POST /rest/adCreatives` to attach that post to a sponsored campaign. The response IDs, such as the post URN and creative URN, can be written back to Sanity for audit and reporting. The end user sees the approved Sponsored Content in their LinkedIn feed, clicks through to the landing page, and lands on a web experience powered by the same structured content.


04Use cases

Common use cases

🎯

Launch Sponsored Content from approved campaign pages

Publish a campaign page in Sanity, then create the matching LinkedIn Sponsored Content with the same headline, intro text, image, URL, and UTMs.

🧾

Keep regulated ad copy consistent

Sync approved disclaimers, product claims, and regional variants into LinkedIn creative payloads instead of relying on manual edits in Campaign Manager.

🧪

Run structured creative variants

Model A/B headline and intro text variants in Sanity, then push each approved version to a LinkedIn campaign for controlled testing.

📥

Connect lead gen campaigns to content context

Tie LinkedIn Lead Gen Form campaigns back to the exact offer, landing page, audience, and content version that produced the lead.


05Implementation

Step-by-step integration

  1. 1

    Set up LinkedIn Ads API access

    Create or use a LinkedIn Campaign Manager account, create a LinkedIn Developer app, associate it with your company page, and request access to the Marketing Developer Platform if you don’t already have it. Configure OAuth 2.0 and request the scopes your integration needs, commonly `r_ads`, `rw_ads`, `r_organization_social`, and `w_organization_social`. You’ll also need the sponsored account URN, campaign URN, and organization URN.

  2. 2

    Install the integration dependencies

    For a TypeScript webhook or Sanity Function, install `@sanity/client`. You can call LinkedIn’s Rest.li Marketing API with `fetch`, as shown below, or use LinkedIn’s Rest.li client package if that fits your stack. Store the LinkedIn access token, organization URN, Sanity project ID, dataset, and Sanity token in environment variables.

  3. 3

    Model LinkedIn ad content in Sanity Studio

    Create a schema such as `linkedinAd` with fields for `title`, `introText`, `description`, `landingPage`, `heroImage`, `utmCampaign`, `linkedinCampaignUrn`, `targetStatus`, and `linkedinCreativeUrn`. Keep LinkedIn-specific fields separate from your landing page content, but reference the landing page so GROQ can join them when syncing.

  4. 4

    Create the publish trigger

    Add a Sanity webhook or Function trigger for publish events on `linkedinAd` documents. Use a GROQ filter so only ad-ready documents trigger the sync, for example documents where the campaign URN exists and the status is `approved`.

  5. 5

    Call the LinkedIn Marketing API

    In the handler, fetch the current document from the Content Lake with GROQ, validate required fields, create a Direct Sponsored Content post through `POST /rest/posts`, then create an ad creative through `POST /rest/adCreatives`. Save the LinkedIn IDs back to Sanity so editors can see what was created.

  6. 6

    Test the full path before activating spend

    Start with a test campaign or set new creatives to `PAUSED`. Confirm the LinkedIn post preview, landing page URL, UTM parameters, image crop, and legal text. Then test the frontend page, analytics tags, and lead form routing before switching creatives to active.


06Code

Code example

typescriptlinkedin-ad-sync.ts
import {createClient} from '@sanity/client'

const sanity = createClient({
  projectId: process.env.SANITY_PROJECT_ID!,
  dataset: process.env.SANITY_DATASET!,
  apiVersion: '2025-01-01',
  token: process.env.SANITY_WRITE_TOKEN!,
  useCdn: false,
})

async function linkedInPost(path: string, body: unknown) {
  const res = await fetch(`https://api.linkedin.com${path}`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.LINKEDIN_ACCESS_TOKEN}`,
      'Content-Type': 'application/json',
      'LinkedIn-Version': '202501',
      'X-Restli-Protocol-Version': '2.0.0',
    },
    body: JSON.stringify(body),
  })

  if (!res.ok) throw new Error(`${path} failed: ${res.status} ${await res.text()}`)
  return {id: res.headers.get('x-restli-id'), body: await res.json().catch(() => null)}
}

export async function POST(req: Request) {
  const {_id} = await req.json()

  const ad = await sanity.fetch(`*[_id == $id][0]{
    title,
    introText,
    description,
    linkedinCampaignUrn,
    "url": landingPage->url,
    "imageUrl": heroImage.asset->url
  }`, {id: _id})

  if (!ad?.linkedinCampaignUrn) return Response.json({skipped: true})

  const post = await linkedInPost('/rest/posts', {
    author: process.env.LINKEDIN_ORGANIZATION_URN,
    commentary: ad.introText,
    visibility: 'PUBLIC',
    distribution: {feedDistribution: 'NONE', targetEntities: [], thirdPartyDistributionChannels: []},
    content: {article: {source: ad.url, title: ad.title, description: ad.description}},
    lifecycleState: 'PUBLISHED',
  })

  const creative = await linkedInPost('/rest/adCreatives', {
    campaign: ad.linkedinCampaignUrn,
    intendedStatus: 'PAUSED',
    reference: post.id,
  })

  await sanity.patch(_id).set({linkedinPostUrn: post.id, linkedinCreativeUrn: creative.id}).commit()
  return Response.json({postUrn: post.id, creativeUrn: creative.id})
}

07Why Sanity

How Sanity + LinkedIn Ads works

Build your LinkedIn Ads integration on Sanity

Sanity gives you the structured content foundation, real-time event system, and flexible APIs you need to connect approved campaign content with LinkedIn Ads.

Start building free →

08Comparison

CMS approaches to LinkedIn Ads

CapabilityTraditional CMSSanity
Ad creative source dataSchema-as-code lets you model LinkedIn-specific fields, referenced landing pages, regional disclaimers, and approval status as structured data.
Sync on publishWebhooks and Functions can trigger from content mutations and call LinkedIn’s API without separate infrastructure for the first sync path.
Field-level API payloadsGROQ can select, filter, project, and join exactly the fields needed for LinkedIn posts and ad creatives.
Editorial review before spendSanity Studio can include campaign status, required-field validation, Comments, Tasks, and Content Releases before a webhook creates LinkedIn assets.
Multi-channel campaign reuseOne structured back end can feed LinkedIn Ads, landing pages, email, mobile, and AI agents through APIs and Agent Context.

09Next steps

Keep building

Explore related integrations to complete your content stack.

Ready to try Sanity?

See how Sanity's Content Operating System powers integrations with LinkedIn Ads and 200+ other tools.