Kova Commerce
Prepared for Sew Heart Felt

CLIENT IMPLEMENTATION HANDOFF · 14 OCTOBER 2026

Finish OpenAI conversion tracking

Shopify default cookie banner + Cloudflare Worker

Install in order • Test consent and matching • Enable live recording last

Read first: status and launch condition

We have updated the Shopify custom purchase pixel. Its purchase switch is currently false. The client owns the remaining Cloudflare and theme work. Follow these steps in order; do not create another pixel, Worker or webhook.

ItemExisting value
Pixel IDBgCs8neHjGo12KJT4wL814
Workeropenai-shopify-conversions
Webhook URLhttps://openai-shopify-conversions.sewheartfelt.co.uk/webhooks/orders
Shopify webhookOrder creation, JSON, API version 2026-10

Required before live activation: complete the consent tests in Step 5. If an order placed after rejecting or withdrawing cookies still shows granted, keep live CAPI disabled and tell us.

This measures orders created, which may include unpaid orders. It has not been tested against this store. Never put API keys or the Shopify signing secret in theme or custom pixel code.

1. Back up and pause server processing

  1. Open Cloudflare → Workers & Pages → openai-shopify-conversions → Settings → Variables and secrets.
  2. Edit MEASUREMENT_ENABLED to the Text value false. Save and deploy.
  3. Click Edit code. Copy the current Worker code into a private backup file.
  4. In Shopify → Online Store → Themes, use the live theme’s three-dot menu → Duplicate. Name the backup with today’s date.

During the pause, signed order webhooks receive 503 and Shopify may retry. Keep this installation window short. Validation is for controlled tests, not days of normal customer traffic.

2. Replace the Worker code

  1. Cloudflare → Workers & Pages → openai-shopify-conversions → Edit code.
  2. Open the existing main source file, usually worker.js or index.js.
  3. Select all its contents. Paste the complete block below. Do not add HTML script tags.
  4. Click Deploy.
  5. Open the Worker base URL. Expect Shopify conversion Worker is running.

The code below supersedes the original guide. It adds hashed customer IDs, outcome logs, and test-webhook protection. The consent check is mandatory; the old REQUIRE_ORDER_CONSENT toggle cannot bypass it.

const PIXEL_ID = "BgCs8neHjGo12KJT4wL814";

export default {
  async fetch(request, env) {
    const path = new URL(request.url).pathname;
    if (request.method === "GET" && path === "/")
      return new Response("Shopify conversion Worker is running.");
    if (path !== "/webhooks/orders") return new Response("Not found", {status:404});
    if (request.method !== "POST") return new Response("POST required", {status:405});
    for (const name of ["OPENAI_CONVERSIONS_API_KEY", "SHOPIFY_WEBHOOK_SECRET", "SHOPIFY_STORE_DOMAIN", "STORE_URL"]) {
      if (!env[name]) return new Response("Missing configuration: " + name, {status:503});
    }
    
    try {
      const body = await request.arrayBuffer();
      if (!await verify(body, request.headers.get("X-Shopify-Hmac-Sha256"), env.SHOPIFY_WEBHOOK_SECRET))
        return new Response("Invalid signature", {status:401});
      const shop = request.headers.get("X-Shopify-Shop-Domain");
      if (shop !== env.SHOPIFY_STORE_DOMAIN) return new Response("Unexpected store", {status:403});
      if (request.headers.get("X-Shopify-Topic") !== "orders/create")
        return new Response("Expected orders/create", {status:400});
      if (env.MEASUREMENT_ENABLED !== "true")
        return outcome("measurement_disabled", 503);
      const validating = env.OPENAI_VALIDATE_ONLY !== "false";
      if (request.headers.get("X-Shopify-Test") === "true" && !validating)
        return outcome("shopify_test_skipped");
      const order = JSON.parse(new TextDecoder().decode(body));
      if (order.source_name !== "web" || order.cancelled_at) return outcome("non_web_or_cancelled_order_skipped");
      const time = Date.parse(order.created_at);
      if (!numericId(order.id) || !Number.isFinite(time)) return new Response("Invalid order", {status:400});
      // Required-consent mode: developer must populate this attribute from actual consent.
      const attrs = Object.fromEntries((order.note_attributes || []).map(a => [a.name, a.value]));
      if (attrs.openai_measurement_consent !== "granted")
        return outcome("consent_missing_or_denied");
      const event = {
        id: "shopify_order_" + order.id,
        type: "order_created", timestamp_ms: time,
        action_source: "web",
        source_url: new URL("/checkout", env.STORE_URL).href,
        data: {type:"contents"}
      };
      if (typeof attrs.oppref === "string" && attrs.oppref.trim()) event.oppref = attrs.oppref;
      const currency = String(order.currency || "").toUpperCase();
      const total = Number(order.total_price);
      if (/^[A-Z]{3}$/.test(currency) && Number.isFinite(total)) {
        const digits = new Intl.NumberFormat("en", {style:"currency", currency}).resolvedOptions().maximumFractionDigits;
        const amount = Math.round(total * 10 ** digits);
        if (Number.isSafeInteger(amount)) Object.assign(event.data, {amount, currency});
      }
      const user = {};
      const email = order.email || order.contact_email;
      if (env.SEND_HASHED_EMAIL === "true" && typeof email === "string" && email.trim())
        user.emails_sha256 = [await sha256(email.trim().toLowerCase())];
      const customerId = numericId(order.customer?.id);
      if (env.SEND_HASHED_EXTERNAL_ID === "true" && customerId)
        user.external_ids_sha256 = [await sha256("shopify_customer_" + customerId)];
      if (typeof attrs.openai_obref === "string" && attrs.openai_obref.trim())
        user.obref = attrs.openai_obref;
      if (Object.keys(user).length) event.user = user;
      const upstream = await fetch("https://bzr.openai.com/v1/events?pid=" + encodeURIComponent(PIXEL_ID), {
        method:"POST",
        headers:{Authorization:"Bearer " + env.OPENAI_CONVERSIONS_API_KEY, "Content-Type":"application/json"},
        body:JSON.stringify({validate_only:validating, integration_source:"shopify_cloudflare_worker", events:[event]}),
        signal:AbortSignal.timeout(3000)
      });
      const diagnostic = {
        upstream_status: upstream.status, validating,
        has_email: Boolean(user.emails_sha256),
        has_external_id: Boolean(user.external_ids_sha256),
        has_obref: Boolean(user.obref), has_oppref: Boolean(event.oppref)
      };
      // Do not log response bodies, orders, emails, keys or click identifiers.
      if (!upstream.ok) return outcome("openai_rejected", 502, diagnostic);
      return outcome(validating ? "validated_not_recorded" : "submitted", 200, diagnostic);
    } catch {
      return outcome("processing_failed", 503);
    }
  }
};

async function verify(body, signature, secret) {
  if (!signature) return false;
  try {
    const bytes = Uint8Array.from(atob(signature), c => c.charCodeAt(0));
    if (bytes.length !== 32) return false;
    const key = await crypto.subtle.importKey("raw", new TextEncoder().encode(secret), {name:"HMAC", hash:"SHA-256"}, false, ["verify"]);
    return await crypto.subtle.verify("HMAC", key, bytes, body);
  } catch { return false; }
}
function outcome(outcome, status = 200, details = {}) {
  console.log("OpenAI conversion", {outcome, ...details});
  return new Response(outcome, {status});
}
function numericId(value) {
  if (typeof value === "number" && !Number.isSafeInteger(value)) return null;
  const id = String(value ?? "").split("/").pop();
  return /^\d+$/.test(id) ? id : null;
}
async function sha256(value) {
  const bytes = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
  return Array.from(new Uint8Array(bytes), b => b.toString(16).padStart(2,"0")).join("");
}

3. Set the Worker configuration

Cloudflare → Worker → Settings → Variables and secrets. Use the pencil to edit a value; use Add variable for missing settings. Values below are text strings without quotes. Save/deploy changes.

NameTypeSetup value
MEASUREMENT_ENABLEDTextfalse until Step 5
OPENAI_VALIDATE_ONLYTexttrue
SEND_HASHED_EMAILTexttrue
SEND_HASHED_EXTERNAL_IDTexttrue, new setting
REQUIRE_ORDER_CONSENTTexttrue, retained for clarity; code always requires mapped permission
SHOPIFY_STORE_DOMAINTextsew-heart-felt.myshopify.com
STORE_URLTexthttps://www.sewheartfelt.co.uk/
OPENAI_CONVERSIONS_API_KEYSecretKeep the existing OpenAI conversion key
SHOPIFY_WEBHOOK_SECRETSecretKeep the existing Shopify webhook signing secret

No key regeneration or new webhook is required. Existing webhook: Shopify → Settings → Notifications → Webhooks. Confirm its HTTPS URL exactly matches the URL at the top of this report.

4. Replace the existing theme base script

  1. Shopify → Online Store → Themes → live theme → … → Edit code.
  2. Open Layout → theme.liquid.
  3. Search within the file for oaiq and bzrcdn.openai.com. Identify the OpenAI-only base pixel block, including its opening and closing script tags.
  4. Replace that OpenAI-only block with the full block below, inside <head> after Shopify’s {{ content_for_header }}. Leave that Shopify Liquid expression in place. If the old block mixes other services or is injected by an app, have the theme developer remove only the old OpenAI installation. Do not add a second initialisation.
  5. Keep OPENAI_THEME_MEASUREMENT_ENABLED = false during validation, then click Save.
  6. Shopify → Settings → Customer privacy → Cookie banner: verify the Shopify banner is enabled for the relevant customer regions and has a working consent-preference/rejection path.

The code reads Shopify permissions; it does not grant consent or subscribe anyone to email marketing. It requires both marketing and data-sharing permission. “granted” below is an internal allowed-processing marker, not proof of a new banner click. Shopify’s allowed methods account for region and store settings.

<!-- OpenAI consent and cart bridge. Replace the existing OpenAI base script. -->
<script>
(function () {
  if (window.__openaiConsentBridge) return;
  window.__openaiConsentBridge = true;
  const PIXEL_ID = "BgCs8neHjGo12KJT4wL814";
  // Leave false until ALL launch checks in this report pass.
  const OPENAI_THEME_MEASUREMENT_ENABLED = false;
  if (!window.oaiq) {
    const q = function () { q.q.push(arguments); };
    q.q = [];
    window.oaiq = q;
    const script = document.createElement("script");
    script.async = true;
    script.src = "https://bzrcdn.openai.com/sdk/oaiq.min.js";
    document.head.appendChild(script);
  }
  window.oaiq("consent", false);
  window.oaiq("init", {pixelId: PIXEL_ID});
  let chain = Promise.resolve();
  let started = false;
  function allowed() {
    const p = window.Shopify?.customerPrivacy;
    return Boolean(p && p.marketingAllowed() === true && p.saleOfDataAllowed() === true);
  }
  function cookie(name) {
    const row = document.cookie.split(";").map(s => s.trim())
      .find(s => s.startsWith(name + "="));
    if (!row) return "";
    try { return decodeURIComponent(row.slice(name.length + 1)); }
    catch { return ""; }
  }
  function sync() {
    // Stop browser measurement immediately on denial, before cart network work.
    window.oaiq("consent", OPENAI_THEME_MEASUREMENT_ENABLED && allowed());
    chain = chain.catch(() => {}).then(async () => {
      const permitted = allowed();
      const attrs = {
        openai_measurement_consent: permitted ? "granted" : "denied",
        openai_consent_updated_at: new Date().toISOString(),
        oppref: "",
        openai_obref: ""
      };
      if (permitted) {
        attrs.oppref = new URL(location.href).searchParams.get("oppref") || cookie("__oppref");
        attrs.openai_obref = cookie("__obref");
      }
      const root = window.Shopify?.routes?.root || "/";
      const response = await fetch(root + "cart/update.js", {
        method: "POST",
        headers: {"Content-Type": "application/json"},
        credentials: "same-origin",
        body: JSON.stringify({attributes: attrs})
      });
      if (!response.ok) throw new Error("Cart consent update failed");
      console.log("OpenAI cart consent", {state: attrs.openai_measurement_consent});
    });
    chain.catch(() => console.error("OpenAI cart consent sync failed; do not enable live CAPI."));
    return chain;
  }
  // For theme/developer verification. Does not change the shopper's choice.
  window.openaiSyncCartConsent = sync;
  // Before checkout, finish saving the latest consent choice to the cart.
  // Waits at most 2 seconds, so checkout is never blocked.
  function beforeCheckout(go) {
    Promise.race([sync(), new Promise(r => setTimeout(r, 2000))])
      .catch(() => {}).then(go);
  }
  document.addEventListener("submit", function (e) {
    const form = e.target;
    const button = e.submitter;
    if (!button || button.name !== "checkout" || form.dataset.openaiChecked) return;
    e.preventDefault();
    beforeCheckout(function () {
      form.dataset.openaiChecked = "true";
      const flag = document.createElement("input");
      flag.type = "hidden";
      flag.name = "checkout";
      flag.value = button.value || "";
      form.appendChild(flag);
      form.submit();
    });
  }, true);
  document.addEventListener("click", function (e) {
    const link = e.target.closest && e.target.closest('a[href*="/checkout"]');
    if (!link || e.defaultPrevented || e.metaKey || e.ctrlKey || e.shiftKey) return;
    e.preventDefault();
    beforeCheckout(function () { location.href = link.href; });
  }, true);
  function start() {
    if (started) return;
    started = true;
    document.addEventListener("visitorConsentCollected", sync);
    window.addEventListener("pageshow", sync);
    window.addEventListener("focus", sync);
    sync();
  }
  function load(attempt = 0) {
    if (window.Shopify?.customerPrivacy) return start();
    if (window.Shopify?.loadFeatures) {
      window.Shopify.loadFeatures([{name:"consent-tracking-api",version:"0.1"}], error => {
        if (error) console.error("OpenAI: Shopify privacy API unavailable.");
        else start();
      });
    } else if (attempt < 40) {
      setTimeout(() => load(attempt + 1), 250);
    } else console.error("OpenAI: Shopify privacy API unavailable.");
  }
  load();
})();
</script>

No extra developer work needed: this code also saves the shopper’s latest cookie choice to the cart just before they go to checkout, from the cart page or the cart drawer. “Buy it now” and express buttons on a product page skip the cart, so those orders arrive without a consent marker and the Worker does not send them. That under-counts slightly but never sends a conversion without consent.

5. Run controlled validation tests

First test cart mapping, then API validation. Open a fresh private browser session. Use a controlled test order approved by the store owner. Avoid enabling Shopify test payment mode on a busy live store without coordinating with the owner.

  1. With no banner choice, add an item to the cart. Open browser Developer Tools → Console. Run the cart check below. Expect denied in regions requiring consent.
  2. Accept the relevant permissions through the banner. Run the check again. Expect granted. It calls the sync helper and waits for the update, so this checks the bridge rather than a checkout race.
  3. Test the store’s ordinary checkout without manually calling the helper. This checks the built-in checkout step in the theme code.
  4. Enable Cloudflare MEASUREMENT_ENABLED = true, keep OPENAI_VALIDATE_ONLY = true, save/deploy. Keep both browser switches false during server validation.
  5. Complete the accepted-consent order. Shopify → Orders → open that order → Additional details: find openai_measurement_consent: granted. If not displayed, the developer must inspect the order webhook’s note_attributes securely.
  6. Cloudflare → Worker → Observability → Logs. Open the corresponding POST and its application log entries. Expect validated_not_recorded, upstream status 2xx, and has_email: true if email exists. Expect has_external_id: true when the order has a customer ID.
  7. In a separate private session, reject cookies and complete another controlled order. Expect denied or missing consent on the order, Worker log consent_missing_or_denied, and no upstream OpenAI call.
  8. Accept, then withdraw through the privacy preferences. Repeat the checkout. It must be skipped. Also test consent changes available inside checkout and each accelerated path, including Shop Pay or Buy it now if offered.
  9. After theme/browser measurement is enabled for a controlled live test, verify oppref and openai_obref forwarding where actually available. A missing click ID on a direct visit is normal. Do not manufacture a click identifier.
await window.openaiSyncCartConsent();
const cart = await fetch(window.Shopify.routes.root + "cart.js").then(r => r.json());
console.log(cart.attributes.openai_measurement_consent);

A 200 webhook status alone is insufficient: it can mean an order was skipped. Validation-mode orders are not recorded and are acknowledged to Shopify, so they will not automatically become live conversions later. Pause server processing again if controlled validation is complete but launch checks are unresolved.

Shopify’s “Send test” confirms transport/signing if it reaches a post-verification outcome. The sample often lacks the web-order source or consent attribute and may be skipped. It is not a substitute for a controlled storefront purchase.

6. Activate only after all checks pass

Before activation, confirm: accepted consent validates; rejected and withdrawn consent never sends; every checkout path is covered; theme initialisation is consent-controlled; the Worker sends available hashes; and the custom pixel has no runtime errors in Shopify’s pixel test tools.

  1. Cloudflare → Worker → Settings → Variables and secrets: set OPENAI_VALIDATE_ONLY = false; keep MEASUREMENT_ENABLED = true. Save/deploy.
  2. Shopify → Online Store → Themes → … → Edit code → theme.liquid: change only OPENAI_THEME_MEASUREMENT_ENABLED = false to true. Save.
  3. Shopify → Settings → Customer events → existing OpenAI pixel: change only OPENAI_PURCHASE_ENABLED = false to true. Save.
  4. Complete a new accepted-consent controlled purchase. Confirm Worker submitted and the expected matching booleans.
  5. Ads Manager → Conversions → SHF OpenAI Pixel → event diagnostics: inspect the new order_created event. Confirm value/currency and matching data. The exact diagnostics labels may differ.
  6. Confirm browser and server use identical pixel ID, event name and shopify_order_<numeric order ID>. Verify one purchase after deduplication. The first received duplicate wins, so verify the browser event’s matching data as well.
  7. Check ad attribution separately using an eligible real ad interaction. A direct test order can be received without an attributed campaign conversion.
Final settingValue after successful verification
MEASUREMENT_ENABLEDtrue
OPENAI_VALIDATE_ONLYfalse
SEND_HASHED_EMAILtrue
SEND_HASHED_EXTERNAL_IDtrue
REQUIRE_ORDER_CONSENTtrue; mandatory in code
Theme OPENAI_THEME_MEASUREMENT_ENABLEDtrue
Custom pixel OPENAI_PURCHASE_ENABLEDtrue
Secrets, URLs and store domainKeep existing values

Do not expect 100% matching coverage across all events: anonymous page views and some guest purchases lack customer identifiers. Judge the new controlled purchase first. Historic warnings may persist until the reporting window changes.

7. Troubleshooting and rollback

OutcomeAction
503 / measurement_disabledExpected while paused. Enable only for controlled tests or verified live operation.
503 / Missing configurationCheck all four required names and Production deployment.
401 / Invalid signatureCheck SHOPIFY_WEBHOOK_SECRET against the Shopify Webhooks signing string. Do not use the OpenAI key.
403 / Unexpected storeSHOPIFY_STORE_DOMAIN must exactly match sew-heart-felt.myshopify.com.
consent_missing_or_deniedInspect cart-to-order attribute and the banner’s actual permission state. Never bypass the check to make an event appear.
non_web_or_cancelled_order_skippedSample or non-storefront source, or cancelled order. Test a controlled web checkout.
has_email falseCheck SEND_HASHED_EMAIL and order email/contact_email availability.
has_external_id falseCheck SEND_HASHED_EXTERNAL_ID and order.customer.id. Some orders do not expose a customer ID.
502 / openai_rejectedConfirm key/pixel pairing and request schema. Developer must inspect validation response securely; this code intentionally does not log raw responses.
503 / processing_failedDeveloper checks runtime exception or timeout; do not log full orders or secrets.
Cart sync failure or checkout withdrawal failureKeep live CAPI disabled; fix theme/checkout integration or server suppression first.

Rollback: set MEASUREMENT_ENABLED false, custom pixel purchase switch false and theme measurement switch false. Save/deploy. Preserve backups. Do not restore an old consent-uncontrolled base script merely to recover tracking. Resolve the issue before reactivation.

The Worker has no durable queue. Shopify retries failed deliveries, but this does not guarantee recovery after retries expire. Monitor failures and add durable queuing/reconciliation for operational reliability.

Return these results to us

Prepared 14 October 2026 for Sew Heart Felt. Worker logic is checked locally with simulated signed webhooks; store, theme, checkout and OpenAI acceptance still require the tests above.

Official references: Shopify Customer Privacy API · Shopify Cart API · Pixel privacy API · OpenAI Conversions API · OpenAI Measurement Pixel.