Marketing & Advertising8 min read

How to Integrate Twitter (X) Ads with Your Headless CMS

Connect Twitter (X) Ads to your structured content so campaign copy, landing page URLs, and creative metadata can move from publish to paid distribution without copy-paste work.

Published April 29, 2026
01Overview

What is Twitter (X) Ads?

Twitter (X) Ads is the paid advertising platform for X, where teams run promoted posts, video ads, follower campaigns, website traffic campaigns, app campaigns, and audience targeting. It gives marketers API access to accounts, campaigns, line items, promoted posts, creatives, audiences, and reporting. Growth, performance, and brand teams use it when they want paid placement inside X timelines, search results, profiles, and conversation surfaces.


02The case for integration

Why integrate Twitter (X) Ads with a headless CMS?

Paid social teams often build the same campaign assets twice. A product launch page goes live with a headline, short description, image, offer URL, UTM parameters, and compliance copy. Then someone re-enters that same information into Twitter (X) Ads, or pastes it into a spreadsheet for a media buyer. That works for 3 ads. It breaks down at 300 localized creatives across 12 markets.


03Architecture

Architecture overview

A typical integration starts when an editor publishes an ad creative document in Sanity Studio. A GROQ-powered webhook matches documents where _type == "xAdCreative" and status == "approved", then sends the document ID to a Sanity Function or webhook listener. The function uses @sanity/client to fetch the latest fields from the Content Lake with GROQ, including referenced campaign, locale, image, landing page, and UTM data. It then signs a request to the Twitter (X) Ads API with OAuth 1.0a and calls the account endpoint, for example POST /12/accounts/:account_id/tweet to create a promoted-only post, followed by POST /12/accounts/:account_id/promoted_tweets to attach that post to a line item. After Twitter (X) Ads accepts the creative, the ad can enter the platform’s normal review and delivery flow, and the end user sees the paid post in X placements based on the campaign’s targeting and budget settings.


04Use cases

Common use cases

🚀

Launch paid social with product releases

Publish a launch page in Sanity, then create matching promoted-only posts in Twitter (X) Ads with the same headline, URL, and campaign image.

🌎

Sync localized ad creatives

Send approved English, Spanish, French, and Japanese copy to the right X line items without rebuilding each ad by hand.

🏷️

Keep offer and pricing copy consistent

When an editor updates a sale end date, disclaimer, or product price, trigger a new creative sync instead of leaving stale ad copy live.

📊

Connect content metadata to campaign reporting

Attach Sanity document IDs, campaign slugs, and UTM values to ad URLs so performance reports can be tied back to specific content variants.


05Implementation

Step-by-step integration

  1. 1

    Set up Twitter (X) Ads API access

    Create or use an X Ads account, confirm it has an active funding source, and apply for developer access with Ads API permissions. In the X Developer Portal, create an app, generate the API key, API secret, access token, and access token secret, then note the ads account ID, campaign ID, and line item ID you’ll sync creatives into.

  2. 2

    Install the integration dependencies

    In your Sanity Function, Next.js route, or Node webhook listener, install @sanity/client, oauth-1.0a, and a supported fetch runtime. Twitter (X) Ads API requests use OAuth 1.0a signing, so keep tokens in environment variables, not in Sanity documents.

  3. 3

    Model Twitter (X) Ads creatives in Sanity Studio

    Create a schema such as xAdCreative with fields for headline, body, destinationUrl, image, locale, status, campaignSlug, xAccountId, xLineItemId, and utmCampaign. Keep budget, bid strategy, and audience targeting in Twitter (X) Ads unless your media team explicitly wants those fields controlled from code.

  4. 4

    Create the publish trigger

    Add a GROQ-powered webhook that fires only for approved ad creative documents, for example _type == "xAdCreative" && status == "approved". Send a small payload such as { "id": _id } to a Sanity Function or webhook route, then fetch the full document inside the handler.

  5. 5

    Call the Twitter (X) Ads API

    Use GROQ to fetch the exact fields Twitter (X) Ads needs, sign the Ads API request, create a promoted-only post with POST /12/accounts/:account_id/tweet, and attach it to a line item with POST /12/accounts/:account_id/promoted_tweets. Store returned X IDs back on the Sanity document if your team needs audit history.

  6. 6

    Test the full publishing path

    Start with a test line item and a small daily budget. Publish one creative from Sanity Studio, confirm the promoted post appears in Twitter (X) Ads, check review status, validate UTM parameters on the landing page, and confirm updates don’t create duplicate live ads unless that’s your intended behavior.


06Code

Code example

typescriptx-ads-webhook.ts
import { createClient } from '@sanity/client'
import OAuth from 'oauth-1.0a'
import crypto from 'node:crypto'

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

const oauth = new OAuth({
  consumer: { key: process.env.X_API_KEY!, secret: process.env.X_API_SECRET! },
  signature_method: 'HMAC-SHA1',
  hash_function(base, key) {
    return crypto.createHmac('sha1', key).update(base).digest('base64')
  }
})

async function xAdsPost(accountId: string, path: string, data: Record<string, string>) {
  const url = `https://ads-api.x.com/12/accounts/${accountId}${path}`
  const auth = oauth.toHeader(oauth.authorize({ url, method: 'POST', data }, {
    key: process.env.X_ACCESS_TOKEN!,
    secret: process.env.X_ACCESS_TOKEN_SECRET!
  }))

  const res = await fetch(url, {
    method: 'POST',
    headers: { ...auth, 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams(data)
  })
  if (!res.ok) throw new Error(`X Ads API error ${res.status}: ${await res.text()}`)
  return res.json()
}

export default async function handler(req: Request) {
  const { id } = await req.json()
  const ad = await sanity.fetch(`*[_id == $id][0]{
    headline,
    destinationUrl,
    xAccountId,
    xLineItemId,
    "utm": utmCampaign
  }`, { id })

  const url = `${ad.destinationUrl}?utm_source=x&utm_medium=paid_social&utm_campaign=${ad.utm}`
  const tweet = await xAdsPost(ad.xAccountId, '/tweet', {
    text: `${ad.headline} ${url}`,
    nullcast: 'true'
  })

  await xAdsPost(ad.xAccountId, '/promoted_tweets', {
    line_item_id: ad.xLineItemId,
    tweet_ids: tweet.data.id
  })

  return new Response(JSON.stringify({ ok: true, tweetId: tweet.data.id }))
}

07Why Sanity

How Sanity + Twitter (X) Ads works

Build your Twitter (X) Ads integration on Sanity

Sanity gives you the AI Content Operating System, structured content foundation, real-time event system, and flexible APIs to connect approved campaign content with Twitter (X) Ads.

Start building free →

08Comparison

CMS approaches to Twitter (X) Ads

CapabilityTraditional CMSSanity
Creative data structureAd copy often lives inside pages, rich text fields, or plugin-specific records, so extraction usually needs custom parsing.Campaign creative, locale, product, image, and legal copy can be modeled as schema-defined JSON in the Content Lake.
Sync timingSyncs often run on schedules or depend on manual exports, which can leave Twitter (X) Ads with old copy.GROQ-powered webhooks and Functions can trigger on specific publish events and run the Twitter (X) Ads sync server-side.
Field-level controlThe integration may receive entire pages or rendered content, then strip out the pieces needed for ads.GROQ can fetch the exact headline, URL, locale, image, product, and line item metadata in one query.
Editorial and media team boundariesEditors and media buyers may work in separate tools with spreadsheets as the handoff layer.Sanity Studio can include approval status, comments, tasks, and campaign fields while Twitter (X) Ads remains the place for budget and targeting.
Multi-channel reuseWebsite content and ad content often diverge because each channel has its own fields and workflows.One structured back end can feed web, mobile, Twitter (X) Ads, reporting pipelines, and AI agents through 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 Twitter (X) Ads and 200+ other tools.