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.
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 event | X event |
|---|---|
page_view | PageView |
view_item | ContentView |
search | Search |
WooCommerce add_to_cart | AddToCart |
WooCommerce begin_checkout | InitiateCheckout |
WooCommerce add_payment_info | AddPaymentInfo |
WooCommerce purchase | Purchase (with order value + currency) |
Form generate_lead | Lead |
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.commay 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.