We’re upgrading our email infrastructure — for immediate response, email andrewjgaber@gmail.com meanwhile.
Skip to main content
Part of Digital Empire
Affiliate / Other

Twitter/X Pixel twq('event', 'tw-*') — server-side Conversion API replacement

Twitter / X · Web Pixels API replacement · confidence: high

Dies 2026-08-26

Paste-ready snippet
// 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.

Legacy pattern detected via: twq\(\s*['"]event['"]\s*,\s*['"]tw-

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.

Test-event verification checklist

  • event_id must match the exact tw-* event ID string configured in X Ads Manager for this conversion event -- account-specific, substituted per merchant from an env var (X_ADS_TW_EVENT_ID), never hard-coded and never client-side.
  • value is passed as a STRING per the X Conversion API schema; number_items sums Shopify line_items quantities (matching what legacy twq() browser-side reported as item count).
  • SECURITY: Bearer token stays in env var (X_ADS_CONVERSION_API_BEARER), never in Web Pixels sandbox, never in Additional Scripts, never in a Shopify theme file, never in a Chrome extension. If any prior deploy shipped the token client-side, ROTATE the token in ads.x.com > API keys and audit the storefront's HTML source with `curl -s <storefront>/ | grep -i bearer` to confirm no leaked copy remains cached in a CDN.
  • Verify live against the platform's own test-event tool: https://ads.x.com/

Related paste-ready snippets

Secondary paid-social pixels · LinkedIn Insight Tag + Twitter/X Pixel — small-share paid social channels pasted alongside Meta Pixel.