X Ads — twq Universal Tag and Conversions API

Server-side conversion tracking for X Ads (formerly Twitter Ads). Pair the Pixel ID with a Conversions API key. Optional hybrid mode loads the browser twq Universal Website Tag, deduplicated via conversion_id.

Reading time: ~5 minLast updated: 2026-05-09

What you'll need

  • An X Ads Manager account with Account Administrator or Ad Manager role.
  • An existing Web Conversion event source (the X equivalent of a Pixel) attached to your domain.
  • About 5 minutes.

1. Find the X Pixel ID

  • Open X Ads Manager at ads.x.com.
  • Tools → Conversion Tracking → Web.
  • Pick the event source for this domain. Create one via Add new event source → Web if none exists.
  • The Pixel ID (alphanumeric, ~5 to 6 characters, format like ojxyz) is shown in the event source header. Copy it.

2. Generate a Conversions API key

  • On the same event source detail page: Conversion API → Generate API key.
  • X issues a long-lived bearer token, scoped to the event source. The token does not expire under normal operation but can be revoked manually.
  • The key is shown ONCE. Copy it immediately. There is no retrieve-later mechanism, you would have to revoke and regenerate.

3. Paste credentials into Beaconry

WordPress Admin → Beaconry → Tracking → X Ads. Paste the Pixel ID and the API key, save.

The key is encrypted at rest with AES-256-GCM using your WordPress auth salts. Constants alternative: BCNR_X_ADS_PIXEL_ID and BCNR_X_ADS_API_KEY in wp-config.php.

4. Send the test event

Click Send X Ads test event. Beaconry fires a synchronous PageView through the Conversions API and reports the response inline.

HTTP 200 with no error means credentials work. The event appears in Tools → Conversion Tracking → Health within ~30 minutes. X Ads does not have a real-time test view comparable to Meta's Test Events, the Health tab is what you watch.

About the click identifier

X appends ?twclid=... (the X Click ID) to every ad-click landing URL. Beaconry captures it on first page-load and persists it in the nl_ext cookie alongside fbclid, gclid, ttclid, msclkid and sc_at. Server-side events then carry twclid for accurate attribution. Without one, X falls back to hashed-PII matching plus IP and User-Agent.

Hybrid mode — twq Universal Tag

Server-side CAPI alone covers 100 percent of consenting visitors. Hybrid mode loads the browser twq Universal Website Tag (static.ads-twitter.com/uwt.js) in parallel so X sees the visitor's first-party cookies. Beaconry sends the same conversion_id from both sides as the third argument to twq('event', ..., {conversion_id: ...}), X deduplicates against double-counting.

Toggle in Beaconry → Tracking → X Ads → Hybrid mode. Better match-rate, slightly more bytes shipped to the visitor. Off by default.

What ships automatically

Beaconry maps GA4 canonical events to X's standard event vocabulary:

Beaconry eventX event
page_viewPageView
view_itemContentView
searchSearch
WooCommerce add_to_cartAddToCart
WooCommerce begin_checkoutInitiateCheckout
WooCommerce add_payment_infoAddPaymentInfo
WooCommerce purchasePurchase (with order value + currency)
Form generate_leadLead

All payloads carry hashed PII (em, ph, twitter_handle when available), the captured twclid, plus IP and User-Agent.

Troubleshooting

  • "401 invalid_credentials": API key revoked or generated under a different event source. Regenerate on the event source detail page and replace in Beaconry.
  • "400 invalid_event_name": a custom event name was sent that does not match X's standard vocabulary. Beaconry only emits standard names from the table above. Custom NLData.track() events with non-standard names get dropped on the X channel.
  • Health tab shows zero events received: outbound HTTP from your origin to ads-api.x.com may be blocked. Check WAF or hosting-firewall outbound rules. Beaconry's Logs tab shows the full HTTP error.
  • Counter spikes after enabling hybrid mode: the third argument to twq('event', ...) must be {conversion_id: ...}, not {eventID: ...}. X uses a different field name than Meta. Beaconry's hybrid loader sets the correct one automatically, but verify you don't have a parallel custom twq snippet with the wrong field.