From 04e8138ec6db16995670d10730af72eeb8e31068 Mon Sep 17 00:00:00 2001 From: Parag More Date: Mon, 27 Jul 2026 15:06:47 +0530 Subject: [PATCH] [SURF-1546] feat(conversions): fire the OpenAI Ads pixel Adds OpenAI Ads (oaiq) as a conversion provider in the parent embed tag, mirroring the in-frame firing in surface_forms. Bootstraps the oaiq SDK if absent, then measures a `customer_action` conversion with the response ID as the event_id dedupe key. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_018VUdEBjARoRAXvcVtaCS2N --- src/conversions/conversion-listener.ts | 2 +- src/conversions/providers.ts | 24 ++++++++++++++++++++++-- surface_embed_v1.js | 20 +++++++++++++++++++- surface_tag.js | 20 +++++++++++++++++++- 4 files changed, 61 insertions(+), 5 deletions(-) diff --git a/src/conversions/conversion-listener.ts b/src/conversions/conversion-listener.ts index 15c490f..69723da 100644 --- a/src/conversions/conversion-listener.ts +++ b/src/conversions/conversion-listener.ts @@ -5,7 +5,7 @@ import { fireConversion, type ConversionProvider } from "./providers"; const CONVERSION_MESSAGE_TYPE = "surface:conversion"; const CONVERSION_ACK_TYPE = "surface:conversion:ack"; -const VALID_PROVIDERS: ConversionProvider[] = ["x", "meta", "ga4", "linkedin"]; +const VALID_PROVIDERS: ConversionProvider[] = ["x", "meta", "openai", "ga4", "linkedin"]; // Idempotency: a form may retry a send; only fire once per message id. const firedIds = new Set(); diff --git a/src/conversions/providers.ts b/src/conversions/providers.ts index 6fc5f1d..a016448 100644 --- a/src/conversions/providers.ts +++ b/src/conversions/providers.ts @@ -6,11 +6,11 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -export type ConversionProvider = "x" | "meta" | "ga4" | "linkedin"; +export type ConversionProvider = "x" | "meta" | "openai" | "ga4" | "linkedin"; export type ConversionEventPayload = | { event_id: string; pixel_id: string } // x - | { pixel_id: string; event_name: string } // meta + | { pixel_id: string; event_name: string } // meta / openai | { measurement_id: string; event_name: string } // ga4 | { partner_id: string; conversion_id: string }; // linkedin @@ -60,6 +60,21 @@ const ensureFbq = (pixelId: string) => { win.fbq("init", pixelId); }; +const ensureOaiq = (pixelId: string) => { + const win = w(); + if (!win.oaiq) { + const q: any = (win.oaiq = function (...args: any[]) { + q.q.push(args); + }); + q.q = []; + const el = document.createElement("script"); + el.async = true; + el.src = "https://bzrcdn.openai.com/sdk/oaiq.min.js"; + document.head.appendChild(el); + } + win.oaiq("init", { pixelId }); +}; + const ensureGtag = (measurementId: string) => { const win = w(); if (!win.gtag) { @@ -122,6 +137,11 @@ export const fireConversion = ( ensureFbq(event.pixel_id); win.fbq("track", event.event_name, {}, { eventID: ctx.response_id }); return true; + case "openai": + ensureOaiq(event.pixel_id); + // event_id is the platform dedupe key (matches browser + server sends). + win.oaiq("measure", event.event_name, { type: "customer_action" }, { event_id: ctx.response_id }); + return true; case "ga4": ensureGtag(event.measurement_id); // send_to pins the event to our property when the page's gtag also has diff --git a/surface_embed_v1.js b/surface_embed_v1.js index 929378a..3255785 100644 --- a/surface_embed_v1.js +++ b/surface_embed_v1.js @@ -314,6 +314,20 @@ } win.fbq("init", pixelId); }; + var ensureOaiq = (pixelId) => { + const win = w(); + if (!win.oaiq) { + const q = win.oaiq = function(...args) { + q.q.push(args); + }; + q.q = []; + const el = document.createElement("script"); + el.async = true; + el.src = "https://bzrcdn.openai.com/sdk/oaiq.min.js"; + document.head.appendChild(el); + } + win.oaiq("init", { pixelId }); + }; var ensureGtag = (measurementId) => { const win = w(); if (!win.gtag) { @@ -363,6 +377,10 @@ ensureFbq(event.pixel_id); win.fbq("track", event.event_name, {}, { eventID: ctx.response_id }); return true; + case "openai": + ensureOaiq(event.pixel_id); + win.oaiq("measure", event.event_name, { type: "customer_action" }, { event_id: ctx.response_id }); + return true; case "ga4": ensureGtag(event.measurement_id); win.gtag("event", event.event_name, { @@ -385,7 +403,7 @@ // src/conversions/conversion-listener.ts var CONVERSION_MESSAGE_TYPE = "surface:conversion"; var CONVERSION_ACK_TYPE = "surface:conversion:ack"; - var VALID_PROVIDERS = ["x", "meta", "ga4", "linkedin"]; + var VALID_PROVIDERS = ["x", "meta", "openai", "ga4", "linkedin"]; var firedIds = /* @__PURE__ */ new Set(); var isConversionMessage = (data) => !!data && data.type === CONVERSION_MESSAGE_TYPE && typeof data.id === "string" && VALID_PROVIDERS.includes(data.provider) && !!data.event && typeof data.response_id === "string"; var handleConversionMessage = (event, log2) => { diff --git a/surface_tag.js b/surface_tag.js index 929378a..3255785 100644 --- a/surface_tag.js +++ b/surface_tag.js @@ -314,6 +314,20 @@ } win.fbq("init", pixelId); }; + var ensureOaiq = (pixelId) => { + const win = w(); + if (!win.oaiq) { + const q = win.oaiq = function(...args) { + q.q.push(args); + }; + q.q = []; + const el = document.createElement("script"); + el.async = true; + el.src = "https://bzrcdn.openai.com/sdk/oaiq.min.js"; + document.head.appendChild(el); + } + win.oaiq("init", { pixelId }); + }; var ensureGtag = (measurementId) => { const win = w(); if (!win.gtag) { @@ -363,6 +377,10 @@ ensureFbq(event.pixel_id); win.fbq("track", event.event_name, {}, { eventID: ctx.response_id }); return true; + case "openai": + ensureOaiq(event.pixel_id); + win.oaiq("measure", event.event_name, { type: "customer_action" }, { event_id: ctx.response_id }); + return true; case "ga4": ensureGtag(event.measurement_id); win.gtag("event", event.event_name, { @@ -385,7 +403,7 @@ // src/conversions/conversion-listener.ts var CONVERSION_MESSAGE_TYPE = "surface:conversion"; var CONVERSION_ACK_TYPE = "surface:conversion:ack"; - var VALID_PROVIDERS = ["x", "meta", "ga4", "linkedin"]; + var VALID_PROVIDERS = ["x", "meta", "openai", "ga4", "linkedin"]; var firedIds = /* @__PURE__ */ new Set(); var isConversionMessage = (data) => !!data && data.type === CONVERSION_MESSAGE_TYPE && typeof data.id === "string" && VALID_PROVIDERS.includes(data.provider) && !!data.event && typeof data.response_id === "string"; var handleConversionMessage = (event, log2) => {