# Kibble installation contract: section 20 of 29 (guide version 3)

> This is **one section only**, not the whole contract. Complete document: https://www.usekibble.dev/AI/guide.md

## 20. Shopify {#shopify}

> **Status: fixture-tested adapter.** Run against fake platform objects only. Not verified on a live platform.

The pixel is **not** the Kibble SDK. A Shopify custom pixel runs in a sandboxed iframe that Shopify calls a lax sandbox ([custom pixels](https://help.shopify.com/en/manual/promoting-marketing/pixels/custom-pixels)); it has no access to the page DOM. Shopify gives it the globals `analytics`, `init`, `browser` and `customerPrivacy` ([Web Pixels API](https://shopify.dev/docs/api/web-pixels-api)). The SDK reads `window`, `document`, cookies and storage, so it cannot run there. The packaged pixel is therefore self-contained: it builds the collection batches itself and posts them to the collector. It is the one place in this contract where a collection request is written by hand; do not copy that pattern anywhere else.

### What the sandbox means for the pixel

- **Page.** The pixel's own window URL belongs to the sandbox, not the store. The page host and path come from `event.context.document.location` ([page_viewed](https://shopify.dev/docs/api/web-pixels-api/standard-events/page_viewed) lists `href`, `host`, `hostname`, `pathname`, `search`, `hash`). Only host and path are used, never the query or fragment; opaque checkout and order tokens in a path are redacted the way the SDK redacts them.
- **Storage.** `browser.localStorage`, `browser.sessionStorage` and `browser.cookie` are asynchronous and run in the top frame ([browser](https://shopify.dev/docs/api/web-pixels-api/standard-api/browser)). The pixel keeps the browser id under `_kibble.<property key>` in `browser.localStorage` (the SDK's `b_<id>.<created>.<last>` record, 30 days rolling, 90 days hard) and the stream id under `_kibble_s.<property key>` in `browser.sessionStorage`. That storage belongs to the host that renders the pixel (the checkout host), which is generally not the storefront's host: without a handoff a visitor is a separate browser in checkout.
- **Initial data.** `init.data.shop.myshopifyDomain` is the shop's `myshopify.com` domain, `init.customerPrivacy` is the consent state at load, and `init.context.document.location` is the page the pixel loaded on ([init](https://shopify.dev/docs/api/web-pixels-api/standard-api/init)).
- **Events.** The pixel subscribes to `page_viewed`, `product_viewed`, `product_added_to_cart`, `checkout_started`, `checkout_contact_info_submitted`, `checkout_shipping_info_submitted`, `payment_info_submitted` and `checkout_completed` from Shopify's [standard events](https://shopify.dev/docs/api/web-pixels-api/standard-events). Search phrases and page titles are not forwarded.

### Where it works

| Surface | Status | Notes |
| --- | --- | --- |
| Shopify checkout and order status pages | **Supported** by Shopify's documentation, not tested on a live store by Kibble | Register the checkout host as an external connection (provider Shopify, tenant = the `myshopify.com` domain). `checkout_completed` with an order id is the only purchase confirmation. |
| Theme storefront pages rendered by Shopify | **Supported** for the events above; not tested | The website snippet on the website host is simpler and richer (page titles, markup, search); do not install both on the same pages or page views are counted twice. Report site search from the search template with `search.results_count` ([Site search](https://www.usekibble.dev/AI#search)). |
| A headless storefront (for example Hydrogen on Oxygen) | **Uncertain** | Shopify's pixel documentation does not say that custom pixels receive events from a headless storefront's pages. Measure those pages with the website snippet on the website host, not with this pixel. Report site search from Hydrogen's `search_viewed` with the result count ([Site search](https://www.usekibble.dev/AI#search)). |
| Continuity from the storefront into checkout | **Uncertain** until observed | The link token must survive Shopify's cart-to-checkout redirects; it is not claimed until Connections shows an accepted handoff and a linked visit. A pixel cannot remove `_kbh` from the address bar. |
| shop.app and purchases made in the Shop app | **Unavailable** | Merchant pixels do not run on shop.app. A purchase is reported only if `checkout_completed` fires on the merchant's own checkout. |

### Rules the pixel follows

- **Tenant.** It does nothing unless `init.data.shop.myshopifyDomain` equals `TENANT`, and sends `TENANT` as `tn` on every batch and on the handoff redemption. Put the shop's `myshopify.com` domain, as shown in Shopify admin, in `TENANT`.
- **Consent** follows Shopify's customer privacy API ([customerPrivacy](https://shopify.dev/docs/api/web-pixels-api/standard-api/customerprivacy)): the initial state is `init.customerPrivacy.analyticsProcessingAllowed`, and changes arrive through `customerPrivacy.subscribe('visitorConsentCollected', callback)`, whose event carries `customerPrivacy.analyticsProcessingAllowed`. Only a boolean `true` collects. If the privacy API is missing the pixel collects nothing. Withdrawal discards queued events and erases the stored ids and the order list. Do not map marketing, preferences or sale-of-data flags. Also set the pixel's customer privacy requirement in Shopify admin so Shopify itself only runs it with analytics permission.
- **Confirmation.** A purchase is an `outcome` only from `checkout_completed` with an order id. Its reference is the SHA-256 hex of `<property key>:<order id>` computed with `crypto.subtle`; the raw order id is never sent, and each order is reported once per browser (the hashes are remembered in `browser.localStorage`). If `crypto.subtle` is unavailable no outcome is sent, only an `outcome_candidate` with signal `shopify_checkout_completed`. Checkout start, contact, shipping and payment events are `checkout_step` events, never conversions.
- **Handoff.** If the checkout page was reached through a link carrying `_kbh`, the pixel reads it from the event context URL, and once consent is granted redeems it at `/v1/handoff/redeem` with `tn`, then adopts the returned browser id. Nothing is sent before the redemption has been tried. The first page view then has `nav: handoff`. A pixel cannot remove the token from the address bar, so the token stays visible in that URL until the visitor navigates; it works once and expires in about two minutes.
- **Privacy.** The pixel never reads customer email, phone, addresses or names, nor `init.data.customer`. Only product, cart and checkout identifiers and amounts leave the browser.
- **Delivery.** Batches use the standard envelope (`v`, `key`, `batch_id`, `sent_at`, `consent`, `browser_id`, `stream_id`, `attempt`, `tn`, `events`) as `text/plain`, `fetch` with `keepalive` and no credentials. A 503 or 429 answer is retried once with the same batch id. `ROUTES` (optional) restricts the pages the pixel reports.

1. Register the checkout host as an external connection on the property with the tenant. The property's Data tracking → Setup page then shows this pixel with the key, origin, tenant and routes filled in.
2. Shopify admin: Settings, Customer events, Add custom pixel. Paste the code, replace the placeholders `KEY`, `ORIGIN`, `TENANT` (and optionally `ROUTES`), set the privacy requirement, save and connect.
3. Test with Shopify's customer event testing or a development store: nothing is sent while analytics permission is off; with it on, a collect request answers 200 with `tn` set; a test order sends exactly one hashed purchase; reloading the thank-you page sends none.
4. The storefront itself keeps the website snippet. List the checkout host in its `data-connected-hosts` only if the storefront links to it and you can observe the handoff.

Custom pixel (complete; the same file is integrations/examples/shopify/kibble-custom-pixel.js):

```js
/*
 * Kibble custom pixel for Shopify checkout (Settings > Customer events > Add custom pixel). Fixture-tested; not run on a
 * live store. Self-contained: it loads no script. Collects only while Shopify's customer privacy allows analytics, only for
 * TENANT's shop, and never reads customer email, phone, address or name. An order id is hashed in the browser.
 * Details: the /AI guide, section "shopify". Values: KEY (public), ORIGIN, TENANT, ROUTES (optional).
 */
(function () {
  var KEY = 'pk_YOUR_PROPERTY_KEY';
  var ORIGIN = 'https://use.example-kibble-host';
  var TENANT = 'your-shop.myshopify.com';
  var ROUTES = []; // optional, up to 8 patterns such as '/checkouts/**'; empty = every page the pixel sees

  // ---- guards: tenant, privacy API, storage and crypto must all be present; otherwise do nothing (fail closed)
  var tenant = String(TENANT).toLowerCase();
  var shop = init && init.data && init.data.shop;
  var shopDomain = String((shop && (shop.myshopifyDomain || shop.myShopifyDomain)) || '').toLowerCase();
  if (!/^[a-z0-9._-]{1,128}$/.test(tenant) || shopDomain !== tenant) return;
  if (typeof browser === 'undefined' || !browser.localStorage || !browser.sessionStorage) return;
  var cr = typeof crypto !== 'undefined' ? crypto : null;
  if (!cr || typeof cr.getRandomValues !== 'function') return;

  var DAY = 86400000, ROLLING = 30 * DAY, HARD = 90 * DAY;
  var LS_ID = '_kibble.' + KEY, SS_STREAM = '_kibble_s.' + KEY, LS_ORDERS = '_kibble_o.' + KEY;
  var B64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-';
  var ID_RE = /^b_[A-Za-z0-9_-]{16,64}$/, SID_RE = /^s_[A-Za-z0-9_-]{16,64}$/, TOKEN_RE = /^[A-Za-z0-9_-]{16,64}$/;

  var consent = false;
  var epoch = 0;
  var browserId = null, streamId = null, adopted = false, adoptedId = null, token = null, redeemed = false;
  var seq = 0, queue = [], timer = null, firstPageView = null, orders = null;

  // ---- helpers
  function randId(prefix) {
    var b = new Uint8Array(22), s = prefix;
    cr.getRandomValues(b);
    for (var i = 0; i < b.length; i++) s += B64.charAt(b[i] & 63);
    return s;
  }
  function uuid() {
    var b = new Uint8Array(16), h = '';
    cr.getRandomValues(b);
    b[6] = (b[6] & 15) | 64; b[8] = (b[8] & 63) | 128;
    for (var i = 0; i < 16; i++) { h += (b[i] + 256).toString(16).slice(1); if (i === 3 || i === 5 || i === 7 || i === 9) h += '-'; }
    return h;
  }
  var num = function (v) { var n = typeof v === 'string' ? parseFloat(v) : v; return typeof n === 'number' && isFinite(n) ? n : undefined; };
  var sid = function (v) { return typeof v === 'string' || typeof v === 'number' ? String(v) : undefined; };
  function drop(o) { var r = {}; for (var k in o) if (o[k] !== undefined && o[k] !== '') r[k] = o[k]; return r; }
  function lsGet(k) { try { return Promise.resolve(browser.localStorage.getItem(k)).then(function (v) { return v == null ? null : String(v); }, function () { return null; }); } catch (e) { return Promise.resolve(null); } }
  function lsSet(k, v) { try { return Promise.resolve(browser.localStorage.setItem(k, v)).catch(function () {}); } catch (e) { return Promise.resolve(); } }
  function lsDel(k) { try { return Promise.resolve(browser.localStorage.removeItem(k)).catch(function () {}); } catch (e) { return Promise.resolve(); } }
  function ssGet(k) { try { return Promise.resolve(browser.sessionStorage.getItem(k)).then(function (v) { return v == null ? null : String(v); }, function () { return null; }); } catch (e) { return Promise.resolve(null); } }
  function ssSet(k, v) { try { return Promise.resolve(browser.sessionStorage.setItem(k, v)).catch(function () {}); } catch (e) { return Promise.resolve(); } }
  function ssDel(k) { try { return Promise.resolve(browser.sessionStorage.removeItem(k)).catch(function () {}); } catch (e) { return Promise.resolve(); } }

  // ---- path redaction: the same rules as the SDK's normalizePath (checkout and order status paths carry opaque tokens)
  var UUID_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;
  function tokenLike(s) {
    if (s.length < 24 || !/^[A-Za-z0-9_-]+$/.test(s)) return false;
    var d = /\d/.test(s), l = /[A-Za-z]/.test(s);
    if (d && l) return true;
    return /[a-z]/.test(s) && /[A-Z]/.test(s) && !/[-_]/.test(s);
  }
  function redact(raw) {
    var semi = raw.indexOf(';'), seg = semi >= 0 ? raw.slice(0, semi) : raw, d = seg;
    for (var i = 0; i < 3; i++) { try { var x = decodeURIComponent(d); if (x === d) break; d = x; } catch (e) { break; } }
    var digits = (d.match(/\d/g) || []).length;
    if (/[^\s@]+@[^\s@]+/.test(d) || UUID_RE.test(d) || /\d{6,}/.test(d) || digits >= 9 || tokenLike(d) || /[\u0000-\u001f\u007f]/.test(d)) return ':id';
    return seg;
  }
  function normPath(p) {
    p = String(p || '/').split(/[?#]/)[0] || '/';
    if (p.charAt(0) !== '/') p = '/' + p;
    return p.split('/').map(function (s, i) { return i === 0 ? s : redact(s); }).join('/').slice(0, 256) || '/';
  }

  // ---- route patterns (CONTRACT 3.2): literal | {tenant} | * | **
  var LIT = /^[A-Za-z0-9._~%:@!$&'()+,;=-]+$/;
  function parsePattern(p) {
    if (typeof p !== 'string' || p.charAt(0) !== '/') return null;
    if (p === '/') return [];
    var s = p.slice(1).split('/');
    if (s.length > 16) return null;
    for (var i = 0; i < s.length; i++) {
      if (s[i] === '**' && s[i - 1] === '**') return null;
      if (s[i] !== '**' && s[i] !== '*' && s[i] !== '{tenant}' && !LIT.test(s[i])) return null;
    }
    return s;
  }
  function glob(p, pi, s, si) {
    for (; pi < p.length; pi++) {
      if (p[pi] === '**') { for (var k = si; k <= s.length; k++) if (glob(p, pi + 1, s, k)) return true; return false; }
      if (si >= s.length || !s[si]) return false;
      if (p[pi] !== '*' && s[si].toLowerCase() !== (p[pi] === '{tenant}' ? tenant : p[pi]).toLowerCase()) return false;
      si++;
    }
    return si === s.length;
  }
  var patterns = (ROUTES || []).slice(0, 8).map(parsePattern).filter(Boolean);
  var routed = (ROUTES || []).length > 0;
  function routeOk(path) {
    if (!routed) return true;
    var s = path.split('/').slice(1);
    if (s.length && s[s.length - 1] === '') s.pop();
    return patterns.some(function (p) { var q = p.slice(); var last = q[q.length - 1]; if (last !== '*' && last !== '**') q.push('**'); return glob(q, 0, s, 0); });
  }

  // ---- page from the event's own document location, never the sandbox's URL
  function locOf(e) { var c = e && e.context, d = c && c.document; return d && d.location ? d.location : null; }
  function pageOf(loc) {
    if (!loc) return null;
    var host = String(loc.host || loc.hostname || '').toLowerCase(), pathname = loc.pathname;
    if ((!host || typeof pathname !== 'string') && typeof loc.href === 'string') {
      try { var u = new URL(loc.href); host = u.host.toLowerCase(); pathname = u.pathname; } catch (e) { return null; }
    }
    if (!host || typeof pathname !== 'string') return null;
    var path = normPath(pathname);
    if (!routeOk(path)) return null;
    return { host: host, path: path, content_id: 'page:' + path, content_type: 'page', title: '' };
  }
  function noteToken(loc) {
    if (token || redeemed || !loc) return;
    var q = String(loc.search || '');
    if (!q) { var h = String(loc.href || ''), i = h.indexOf('?'); q = i >= 0 ? h.slice(i).split('#')[0] : ''; }
    var m = /[?&]_kbh=([^&#]*)/.exec(q);
    if (m && TOKEN_RE.test(m[1])) token = m[1];
  }

  // ---- identity (the SDK's record format and limits)
  function parseId(v) {
    if (!v) return null;
    var p = v.split('.');
    if (p.length !== 3 || !ID_RE.test(p[0])) return null;
    var created = Number(p[1]), last = Number(p[2]);
    return isFinite(created) && isFinite(last) ? { id: p[0], created: created, last: last } : null;
  }
  function writeId(rec) { return lsSet(LS_ID, rec.id + '.' + rec.created + '.' + rec.last); }
  function identity() {
    return Promise.all([lsGet(LS_ID), ssGet(SS_STREAM)]).then(function (r) {
      var now = Date.now(), rec = parseId(r[0]);
      if (rec && now - rec.last <= ROLLING && now - rec.created <= HARD && rec.created <= now + DAY) {
        if (now - rec.last > 5 * 60000) { rec.last = now; writeId(rec); }
      } else {
        rec = { id: randId('b_'), created: now, last: now };
        writeId(rec);
      }
      var s = r[1];
      if (!s || !SID_RE.test(s)) { s = randId('s_'); ssSet(SS_STREAM, s); }
      return { bid: rec.id, sid: s };
    });
  }
  function redeem(ep) {
    if (!token || redeemed) return Promise.resolve();
    var t = token; redeemed = true; token = null;
    return fetch(ORIGIN + '/v1/handoff/redeem', {
      method: 'POST', body: JSON.stringify({ v: 1, key: KEY, token: t, tn: tenant }),
      headers: { 'Content-Type': 'text/plain;charset=UTF-8' }, credentials: 'omit', referrerPolicy: 'no-referrer', mode: 'cors'
    }).then(function (r) { return r.ok ? r.json() : null; }).then(function (j) {
      if (ep !== epoch || !consent) return;
      var id = j && typeof j.browser_id === 'string' && ID_RE.test(j.browser_id) ? j.browser_id : null;
      if (id) { var now = Date.now(); writeId({ id: id, created: now, last: now }); adoptedId = id; adopted = true; }
    }).catch(function () {});
  }

  // ---- consent: analytics processing allowed, nothing else
  function setConsent(allowed) {
    allowed = allowed === true;
    if (allowed === consent) return;
    consent = allowed;
    epoch++;
    if (allowed) start(epoch); else wipe();
  }
  function start(ep) {
    identity().then(function (ids) {
      if (ep !== epoch || !consent) return;
      return redeem(ep).then(function () {
        if (ep !== epoch || !consent) return;
        browserId = adoptedId || ids.bid; streamId = ids.sid; // nothing is sent before the handoff has been tried
      });
    }).then(function () {
      if (ep !== epoch || !consent || !browserId) return;
      if (adopted && firstPageView && queue.indexOf(firstPageView) >= 0) firstPageView.attrs.nav = 'handoff';
      if (queue.length) schedule(0);
    });
  }
  function wipe() {
    queue = []; firstPageView = null; orders = null; browserId = null; streamId = null; adopted = false; adoptedId = null; token = null; redeemed = false;
    if (timer) { clearTimeout(timer); timer = null; }
    lsDel(LS_ID); lsDel(LS_ORDERS); ssDel(SS_STREAM);
  }

  // ---- transport (contract: collect.md; text/plain, no credentials, keepalive)
  function schedule(ms) {
    if (timer && ms > 0) return;
    if (timer) clearTimeout(timer);
    timer = setTimeout(function () { timer = null; flush(); }, ms);
  }
  function flush() {
    if (!consent || !browserId || !streamId || !queue.length) return;
    var ep = epoch, evs = queue.splice(0, 50), batch = { id: uuid(), events: evs, bid: browserId, sid: streamId };
    send(batch, 1, ep);
    if (queue.length) schedule(0);
  }
  function send(b, attempt, ep) {
    if (ep !== epoch || !consent) return;
    var body = JSON.stringify({ v: 1, key: KEY, batch_id: b.id, sent_at: Date.now(), consent: 'granted', browser_id: b.bid, stream_id: b.sid, attempt: attempt, tn: tenant, events: b.events });
    var retry = function (wait) { if (attempt < 2) setTimeout(function () { send(b, 2, ep); }, wait); };
    fetch(ORIGIN + '/v1/collect', {
      method: 'POST', body: body, headers: { 'Content-Type': 'text/plain;charset=UTF-8' },
      credentials: 'omit', keepalive: true, referrerPolicy: 'no-referrer', mode: 'cors'
    }).then(function (r) {
      if (r.status === 503 || r.status === 429) {
        var ra = Number(r.headers && r.headers.get && r.headers.get('Retry-After'));
        retry(isFinite(ra) && ra > 0 ? Math.min(ra, 10) * 1000 : 1000);
      }
    }, function () { retry(1000); });
  }

  // ---- capture
  function capture(kind, loc, attrs, nums) {
    if (!consent) return null;
    var page = pageOf(loc);
    if (!page) return null;
    if (kind !== 'page_view') page.title = '';
    var ev = { id: uuid(), kind: kind, t: Date.now(), seq: ++seq, page: page, attrs: drop(attrs || {}), nums: drop(nums || {}) };
    queue.push(ev);
    if (queue.length >= 20) schedule(0); else schedule(250);
    return ev;
  }
  function sub(name, fn) {
    try {
      analytics.subscribe(name, function (e) {
        try {
          var loc = locOf(e);
          noteToken(loc);
          fn((e && e.data) || {}, loc);
        } catch (x) { /* never break the checkout */ }
      });
    } catch (x) { /* ignore */ }
  }

  var pageViews = 0;
  sub('page_viewed', function (d, loc) {
    var ev = capture('page_view', loc, { nav: pageViews++ ? 'spa' : 'load' });
    if (ev && !firstPageView) firstPageView = ev;
  });
  sub('product_viewed', function (d, loc) {
    var v = d.productVariant || {}, p = v.product || {};
    capture('product_view', loc, { product: sid(p.id || v.id), category: typeof p.type === 'string' ? p.type : undefined }, { price: num(v.price && v.price.amount) });
  });
  sub('product_added_to_cart', function (d, loc) {
    var l = d.cartLine || {}, m = l.merchandise || {};
    capture('add_to_cart', loc, { product: sid((m.product && m.product.id) || m.id) }, { quantity: num(l.quantity), value: num(l.cost && l.cost.totalAmount && l.cost.totalAmount.amount) });
  });
  [['checkout_started', 'cart'], ['checkout_contact_info_submitted', 'details'], ['checkout_shipping_info_submitted', 'shipping'], ['payment_info_submitted', 'payment']].forEach(function (p) {
    sub(p[0], function (d, loc) { capture('checkout_step', loc, { step: p[1] }, { value: num(d.checkout && d.checkout.totalPrice && d.checkout.totalPrice.amount) }); });
  });

  function seenOrder(h) {
    var ep = epoch;
    return (orders ? Promise.resolve() : lsGet(LS_ORDERS).then(function (v) {
      var a = []; try { a = JSON.parse(v || '[]'); } catch (e) { /* ignore */ }
      orders = (Array.isArray(a) ? a : []).filter(function (x) { return /^[0-9a-f]{64}$/.test(x); }).slice(-20);
    })).then(function () {
      if (ep !== epoch || !consent) return true;
      if (orders.indexOf(h) >= 0) return true;
      orders.push(h); orders = orders.slice(-20);
      lsSet(LS_ORDERS, JSON.stringify(orders));
      return false;
    });
  }
  sub('checkout_completed', function (d, loc) {
    var c = d.checkout || {}, id = sid(c.order && c.order.id);
    if (!id || !consent) return; // no order id: not a confirmed purchase
    var ep = epoch;
    var items;
    if (Array.isArray(c.lineItems)) items = c.lineItems.reduce(function (s, i) { return s + (num(i && i.quantity) || 1); }, 0);
    var value = num(c.totalPrice && c.totalPrice.amount);
    var cur = sid(c.totalPrice && (c.totalPrice.currencyCode || c.currencyCode)) || sid(c.currencyCode);
    if (!cr.subtle || typeof cr.subtle.digest !== 'function') {
      capture('outcome_candidate', loc, { signal: 'shopify_checkout_completed', source: 'adapter' });
      schedule(0);
      return;
    }
    cr.subtle.digest('SHA-256', new TextEncoder().encode(KEY + ':' + id)).then(function (buf) {
      var h = Array.prototype.map.call(new Uint8Array(buf), function (x) { return (x + 256).toString(16).slice(1); }).join('');
      return seenOrder(h).then(function (dup) {
        if (dup || ep !== epoch || !consent) return;
        capture('outcome', loc, { type: 'purchase', source: 'adapter', ref: h, currency: cur ? cur.toUpperCase() : undefined }, { value: value, items: items });
        schedule(0);
      });
    }, function () {
      if (ep === epoch && consent) { capture('outcome_candidate', loc, { signal: 'shopify_checkout_completed', source: 'adapter' }); schedule(0); }
    });
  });

  // ---- start: follow Shopify's privacy API (initial status, then changes). Fail closed when it is absent.
  try {
    if (typeof customerPrivacy !== 'undefined' && customerPrivacy && typeof customerPrivacy.subscribe === 'function') {
      customerPrivacy.subscribe('visitorConsentCollected', function (e) {
        setConsent(!!(e && e.customerPrivacy && e.customerPrivacy.analyticsProcessingAllowed === true));
      });
    }
  } catch (x) { /* ignore */ }
  var ic = init && init.context && init.context.document && init.context.document.location;
  noteToken(ic);
  setConsent(!!(init.customerPrivacy && init.customerPrivacy.analyticsProcessingAllowed === true));
})();

```

> **Note:** The npm adapter `subscribeShopify` in the SDK sources is for storefront code that has the SDK, not for checkout. The same sources hold `subscribeHydrogenSearch` (Hydrogen `search_viewed`, the count from `searchResults.total`). The `@kibble/sdk` package is not published; use the pixel above for checkout and the snippets in [Site search](https://www.usekibble.dev/AI#search) for storefront search.

END OF SECTION shopify
