Fix guide · Framer · advanced · about 60 min

OpenAI Pixel event_id missing on Framer: fix deduplication

For the finding “OpenAI events have no event ID”. Also for: Any website, Shopify, WordPress, Webflow.

What this means

Each event the OpenAI Pixel sends can carry an event ID that your site sets: the event_id option in the fourth argument of oaiq("measure", ...), or the Event ID field of a Google Tag Manager (GTM) tag. Your Framer site sends its events without one.

The requests still contain an ID: when your site passes none, the pixel puts a random value in the event’s id field by itself (OpenAI does not document this, but you can see it in the request payload). That value exists only in the visitor’s browser. Your server never learns it, so it cannot send the same ID with its copy of the event.

Whether this costs you money depends on one question: does your site also send these events to OpenAI from a server, through the OpenAI Conversions API?

  • Yes, or you are not sure: OpenAI recognizes a browser event and a server event as one conversion when they share the Pixel ID, the event name and the event ID. With a random browser ID they never match, so the same lead can be counted twice.
  • No, you only use the browser pixel: there is nothing to deduplicate yet. It matters the day you add the Conversions API.

We cannot see server-to-server traffic from a browser, so we cannot tell whether your Conversions API works. We only see whether your site passes its own event IDs to the pixel.

How to confirm it yourself

Because the pixel fills in an ID on its own, the Network tab cannot tell you whether your site sets one. Look at your setup instead.

  1. Search Site Settings > Code, each page’s own custom code, your code overrides and your GTM tags for oaiq("measure". For each call, look for a fourth argument with event_id, as in the general guide.
  2. In GTM, open each OpenAI tag. In OpenAI’s template (OpenAI Ads Measurement Pixel) the field is Event ID; in Stape’s OpenAI Ads Pixel template it is in the Server-side tracking settings section. An empty field means the pixel uses its own random value.
  3. Submit a test form on the published site with the Network tab open (filter bzr.openai.com) and compare the id in the lead_created event with the ID your server sends for the same submission. If they differ, deduplication fails.

How to fix

OpenAI keeps the first event it receives for the same Pixel ID, event name (for custom events, custom_event_name) and event ID, and ignores later duplicates. So: give the browser event your own ID, send exactly the same ID from the server, and never reuse a fixed value, or real conversions are dropped as duplicates.

On a Framer site the key conversion is usually a form submission (lead_created). Pick the route that matches how your server events are sent.

Route 1: Tag Manager with a server container. This fits when your OpenAI tags already run in GTM behind Framer’s Cookie Banner.

  1. Add a variable that produces a unique ID per event, for example Stape’s Unique Event ID variable (third-party, from the Community Template Gallery).

  2. Put it in the web tag’s event ID field: Event ID in OpenAI’s template, Server-side tracking settings in Stape’s.

  3. Send the event to your server container (for example through server-side GA4 or Stape’s Data Tag) and map the same value to the event ID of the server tag. In Stape’s OpenAI Ads Events API tag this is Event ID under Server Event Data Parameters.

  4. To fire the lead_created tag when a Framer form is submitted, give the form a tracking ID in its settings. Framer then dispatches a framer:formsubmit browser event on each submission. Pass it to the data layer with a custom code entry in Site Settings > Code, set to Run: Once (Framer recommends Once for event listeners):

    <script>
      document.addEventListener("framer:formsubmit", function (event) {
        window.dataLayer = window.dataLayer || [];
        window.dataLayer.push({
          event: "framer_form_submit",
          form_tracking_id: event.detail.trackingId
        });
      });
    </script>

    In GTM, use a Custom Event trigger for framer_form_submit, and keep the same ad_storage consent requirement as your other OpenAI tags.

Route 2: a Framer form webhook and your own server. This fits when the form goes to a webhook (your backend or an automation tool) that sends lead_created to the Conversions API.

  1. Add a hidden field to the form: select the Input, turn on Hidden and give it the name event_id. Do not hide it with the Visible property: Framer leaves invisible inputs out of the submission.

  2. Fill it with a fresh ID for each submission. Framer does not fill hidden fields by itself; its help points to a code override for this (its example is UTM parameters). Have the override write a new random value, for example from crypto.randomUUID(), whenever the form is shown.

  3. Send the same value with the browser event. Give the form a tracking ID and read the field when Framer reports the submission (custom code entry, Run: Once). Replace contact-form with your form’s tracking ID:

    <script>
      var lastForm = null;
      // The browser's own submit event says which form was sent
      document.addEventListener("submit", function (event) {
        lastForm = event.target;
      }, true);
      document.addEventListener("framer:formsubmit", function (event) {
        if (event.detail.trackingId !== "contact-form" || !lastForm) return;
        var field = lastForm.querySelector('input[name="event_id"]');
        if (!field || !field.value || !window.oaiq) return;
        oaiq("measure", "lead_created", { type: "customer_action" }, { event_id: field.value });
      });
    </script>

    framer:formsubmit only tells you the form’s tracking ID, so the code reads the field inside the form the browser just submitted. A plain document.querySelector would take the field of the first form on the page, which is wrong on pages with several forms. Framer fires framer:formsubmit when someone submits the form, not when the submission is confirmed, so a failed submission can still send a browser event; step 2 of “How to verify the fix” compares the IDs on both sides.

    The window.oaiq check skips visitors whose pixel has not loaded because they have not consented. If the pixel runs in GTM, push field.value to the data layer instead and use it in the tag’s Event ID field through a Data Layer Variable.

  4. Reuse it on the server. Framer’s webhook sends the submission as JSON with the input names as keys, so the ID arrives as event_id. Send it as id on the Conversions API event, with the type lead_created and the same Pixel ID. Do not use the Framer-Webhook-Submission-Id header for this: it is unique per submission, but the browser never sees it, so the pixel cannot send it.

Route 3: a third-party tool sends your server events. Check its documentation for how it sets event IDs. It can only match browser events it sends itself, so remove any manually installed pixel; a second copy keeps sending random IDs and usually also shows up as “Duplicate OpenAI events”.

Publish the Framer site, and the GTM container if you changed it.

Common causes

  • The snippet and the measure calls were pasted into Framer’s custom code without the optional fourth argument.
  • The Event ID field of the GTM web tag is empty, or the server tag reads a different variable.
  • Both sides generate their own ID: for example the server uses Framer’s webhook submission ID while the pixel makes up its own.
  • The hidden field was hidden with Visible instead of Hidden, so the ID never reaches the webhook.
  • The Conversions API was added later, and the browser calls were never updated.

How to verify the fix

  1. Every key OpenAI event (tag or measure call) passes an event ID, and the value changes from one submission to the next.
  2. For one test submission, the id in the browser request equals the event_id in the webhook payload and the id your server sends.
  3. With access to the Advertiser API, OpenAI’s recent-events endpoint (GET /v1/conversions/events?pid=<PIXEL-ID>) lists a sample of events from roughly the last 15 minutes with their channel: pixel_sdk for the browser and server_to_server for your server.
  4. Re-run the check on IsPixelWorking. The issue should be gone.

Sources

More OpenAI guides