Twitter / X · Web Pixels API replacement · confidence: high
Dies 2026-08-26
// Romeo8 (2026-08-24) SECURITY FIX -- Fable-Alpha P0: the previous version
// of this template shipped a browser-side fetch with
// `Authorization: Bearer BEARER_TOKEN` inside the Web Pixels sandbox. That
// is a CREDENTIAL LEAK: anything in the Web Pixel source is visible to any
// visitor who opens DevTools, so a merchant pasting it would publish their
// X Ads API bearer token in HTML source. Unlike Meta Pixel (cookie-signed)
// or Klaviyo Client Events (public read-limited company_id), X's Conversion
// API has NO browser-safe credential mode -- there is no equivalent of a
// public writable key. Server-side is the only correct path.
//
// ============================================================
// SECURITY WARNING
// NEVER put an X Ads API Bearer token in HTML, in a Web Pixel, in a Shopify
// theme, in Additional Scripts, or in any client-side JS bundle. Anything
// pasted into the storefront is world-readable. If you have already deployed
// a browser-side variant with a real Bearer token, ROTATE the token in
// ads.x.com > API keys before doing anything else.
// ============================================================
//
// Paste-ready browser-side sentinel: NO Bearer token is sent from this
// block. It exists so the Web Pixels sandbox has a checkout_completed
// listener at all (the ex-twq() call point), and so anyone auditing the
// pixel source sees the server-side redirect message.
analytics.subscribe('checkout_completed', (event) => {
// Intentionally does not call the X Conversion API from the browser.
// See Variant B below -- run the equivalent fetch from your backend
// where the X_ADS_CONVERSION_API_BEARER env var is safe to read.
// Deleting this listener is fine; leaving it in place is fine.
const { checkout } = event.data;
if (!checkout) return;
// Optional: enqueue a beacon to YOUR OWN first-party endpoint (same
// origin as the storefront), which then relays server-side to X.
// fetch('/api/x-conversions/relay', { method: 'POST', keepalive: true,
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify({
// order_id: checkout.order ? String(checkout.order.id) : checkout.token,
// value: String(checkout.totalPrice ? checkout.totalPrice.amount : 0),
// currency: checkout.totalPrice ? checkout.totalPrice.currencyCode : 'USD',
// items: (checkout.lineItems || []).reduce((s, i) => s + i.quantity, 0),
// }),
// });
});
// (Original documentation continues below.)
// VARIANT A -- BROWSER-SIDE (Web Pixels sandbox).
// STATUS: NOT SUPPORTED. X Conversion API has no cookie-signed / public-key
// mode. The only browser-safe X tracking mechanism is the legacy twq()
// pixel loader, which does NOT run inside the Web Pixels sandbox (window.twq
// is undefined there) and is exactly what stops working Aug 26. There is no
// paste-ready browser-side replacement -- use Variant B.
//
// ============================================================
// VARIANT B -- SERVER-SIDE (X Conversion API from your backend).
// Use this in a Shopify webhook handler (orders/create or orders/paid) that
// runs on your own server, a Cloudflare Worker, an AWS Lambda, or a Shopify
// App backend. Load BEARER_TOKEN from an env var, never hard-code it.
// Docs: https://developer.x.com/en/docs/x-ads-api/measurement/conversions-api
//
// // Shopify webhook: orders/create (or orders/paid)
// // Docs: https://shopify.dev/docs/api/admin-rest/current/resources/webhook
// export async function handleShopifyOrderCreated(order) {
// const pixelId = process.env.X_ADS_PIXEL_ID; // e.g. "o1234"
// const bearer = process.env.X_ADS_CONVERSION_API_BEARER; // rotate in ads.x.com > API keys
// const twEventId = process.env.X_ADS_TW_EVENT_ID; // e.g. "tw-o1234-abcde" -- must match the exact event configured in X Ads Manager
//
// if (!pixelId || !bearer || !twEventId) throw new Error('X Conversion API env not configured');
//
// const res = await fetch(`https://ads-api.x.com/12/measurement/conversions/${pixelId}`, {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json',
// 'Authorization': `Bearer ${bearer}`,
// },
// body: JSON.stringify({
// conversions: [{
// conversion_time: order.created_at,
// event_id: twEventId,
// identifiers: [],
// price_currency: order.currency,
// number_items: (order.line_items || []).reduce((sum, li) => sum + li.quantity, 0),
// value: String(order.total_price),
// conversion_id: String(order.id),
// }],
// }),
// });
// if (!res.ok) throw new Error(`X Conversion API ${res.status}: ${await res.text()}`);
// }
//
// // If you have no backend today, cheapest options ranked by setup time:
// // 1. Shopify Flow -> HTTP request action (no code, but limited retry).
// // 2. A single Cloudflare Worker with a Shopify orders/create webhook.
// // 3. Zapier/Make.com Shopify -> Webhook step with the payload above.
// // All three keep the Bearer server-side.What this does NOT cover
What this does NOT cover: (a) upper-funnel events -- PageView, ViewContent, AddToCart, InitiateCheckout -- which the legacy twq() pixel may have been firing elsewhere; those need separate Conversion API calls fired from where each event actually happens (server for order, browser for page view via a first-party endpoint that then relays server-side). (b) X's audience-matching pixel behavior; Conversion API is measurement only, not retargeting audience seeding -- if the storefront relied on twq() to build retargeting audiences, add a first-party endpoint that relays hashed identifiers to X Custom Audiences API. (c) SHA-256 hashing of email/phone identifiers; the snippet omits identifier hashing so the reader supplies it per their PII stance.
Secondary paid-social pixels · LinkedIn Insight Tag + Twitter/X Pixel — small-share paid social channels pasted alongside Meta Pixel.