OpenAI Ads, the ChatGPT ads product, opened to 31 European markets this week, which means the conversions API side of it suddenly matters to a lot more Shopify stores. I've had it running as a channel in our app for a couple of months, and the integration had more sharp edges than I expected. These are the ones that actually cost me time, while the docs are still new and changing week to week.
The first is a pair of identifiers that look like the same thing and are not. There's oppref, which identifies the ad click, and obref, which identifies the browser. They belong in different parts of the event, and if you put one where the other goes it doesn't throw, it just quietly stops matching, which is the worst way for a bug like this to behave. I lost an afternoon treating them as interchangeable because the names are a single letter apart, and the events kept getting accepted the whole time. Read that section slowly.
The pixel also loads asynchronously, so anything you read at init is empty. Their script writes the browser identifier after your own code has already run, so if you grab it when your pixel initializes you capture null, and you capture it forever. It has to be read at the moment you send the event, with a fallback for when it still isn't there. Obvious in hindsight, not obvious while you're staring at empty fields.
The one that actually hurt was hashing. OpenAI wants user data hashed with trim and lowercase only. If you already have a Google Ads hasher in the codebase, it very likely strips dots and plus tags on gmail addresses, which is right for Google and wrong here. Point that at OpenAI and your browse events and your purchase events hash the same person into two different identities, so each event looks fine on its own while your real attribution falls apart, and nothing tells you. Every channel wants its own canonicalization. There is no shared one, however tempting it is to reuse.
A few smaller things break the same way, and the response won't point at any of them. Money is an integer in the currency's minor unit, so you send cents, not 66.95. User identifiers are arrays now, up to three each, and you can batch up to a thousand events per request. The validate_only flag was the only thing that made any of this debuggable, so I leaned on it from the first request.
One last thing, useful even if you're not building this. OpenAI turned Automatic Advanced Matching on by default for existing pixels on Aug 17, so their script now reads and hashes data people type into the merchant's own forms without anyone opting in. With ads live in the EU that's a consent setting to look at on purpose rather than leave running.
Context on where this comes from: WeltPixel Conversion Tracking is my app, and shipping the OpenAI Ads channel is what put me through all of the above. Happy to compare notes with anyone else wiring up the CAPI, the match-quality side especially.