How to verify that Pandectes works with the Shopify Customer Privacy API

Overview


Pandectes GDPR Compliance is integrated with Shopify's Customer Privacy API. When a visitor chooses Accept, Reject, or a custom selection in the preferences window, the banner reports that choice to Shopify through the official API. Shopify then applies it to the surfaces it controls: web pixels, customer events, audiences, and checkout.


This article shows how to confirm that the signal arrives in your own store. It also lists checks that look like failures but are not.


For how the integration behaves, see How the app works with Shopify's Customer Privacy API. Shopify's own reference is the Customer Privacy API.


This guide is written for a normal Shopify storefront (Online Store theme). On a headless or custom storefront the same checks apply, with a few practical limits. Read Headless and custom storefronts before you start.


How it works


The app's job in this integration is to pass the visitor's decision to Shopify, correctly and immediately. Everything afterwards belongs to Shopify: loading or holding back web pixels on the storefront and in checkout, gating sales channels such as Google & YouTube or Meta, and applying consent to customer events, audiences, and checkout.


Those are Shopify-owned systems that read consent from Shopify's own API. Once the checks below show that the signal arrives, the app has done what it is meant to do.


setTrackingConsent() is the documented way for an app to tell Shopify what a visitor decided. Pandectes calls it. That call is the interface between the app and Shopify.


Key points


  • Use currentVisitorConsent() to verify the visitor's raw decision ('', 'yes', or 'no').
  • The Allowed() methods mix merchant settings, location, and consent. A true or false alone does not prove that the visitor decided anything.
  • shouldShowBanner() is a region check. It can stay true after Accept.
  • Do not read Shopify cookies, dataLayer, or network requests to judge this integration.
  • The first check installs nothing. The theme snippet and the test pixel are temporary. Remove them when you are done.


In a hurry? Do the Quick Console Check only. If the values go from empty to 'yes' when you click Accept, the integration is writing consent to Shopify.


Before you start


Three things, or the results will be misleading:


  1. Use a clean browser profile. Open a new Incognito / Private window, or clear cookies and local storage for your domain. If you already gave consent, the banner will not ask again and you will only see the "after" state.
  2. Test as a visitor from a region where consent is required (for example the EU/EEA/UK if that is how your banner is configured). In regions where Shopify is not configured to require consent, everything is allowed by default and the test proves nothing.
  3. Make sure the Pandectes app embed is enabled in your theme. The Shopify Customer Privacy integration is always active and has no setting to turn on.


Open the browser Developer Console:


  • Chrome / Edge: press F12, then open the Console tab
  • Firefox: press F12, then open the Console tab
  • Safari: enable the Develop menu in Settings → Advanced, then Option + Command + C


The first check installs nothing. The two snippets that follow are temporary diagnostic tools. Remove them as soon as verification is finished. They are meant to run for a few minutes on your screen, not to stay on a live store.


What Pandectes sends to Shopify


Shopify's API uses four purposes. Pandectes maps its cookie categories onto them:


Pandectes category

Shopify purpose

Functionality / Preferences

preferences

Performance / Analytics

analytics

Targeting / Marketing

marketing

Sale of data opt-out (Do not sell my data)

sale_of_data


Strictly necessary cookies have no equivalent. They are always allowed and are not part of this consent write.


Option 1: Quick Console Check


You do not need to install anything. One command, run three times, is enough.


Step 1. Open your storefront in a new Incognito window. The banner appears. Do not click anything.


Step 2. Open the Console, paste this, and press Enter:


window.Shopify.customerPrivacy.currentVisitorConsent();


You should get four empty strings. That is Shopify's way of saying the visitor has not decided yet:


{marketing: '', analytics: '', preferences: '', sale_of_data: ''}


Step 3. Click Accept all on the banner, then run the same command again:


{marketing: 'yes', analytics: 'yes', preferences: 'yes', sale_of_data: ''}


Step 4. Reload the page and run it one last time. The values must still be there.


Empty before the decision, filled in right after it, still filled in on the next page load. That is the verification. If you click Reject all instead, you get 'no' in place of 'yes'. That is equally correct: the visitor was asked and said no.


Only preferences, analytics, and marketing need to change. sale_of_data can stay empty. That is normal. See the note under What you should not use to verify.


If the command returns an error saying customerPrivacy is undefined, the API has not finished loading yet. Wait a moment and try again, or run this once first:


window.Shopify.loadFeatures([{name: 'consent-tracking-api', version: '0.1'}], () => {});


Why currentVisitorConsent and not marketingAllowed


Merchants often check only this:


window.Shopify.customerPrivacy.preferencesProcessingAllowed();  // true / false


That method is useful, but it can only answer true or false. Shopify's documentation explains that these methods combine merchant settings, user location, and user consent.


So false might mean the visitor refused, or that the visitor is in a consent-required region and has not answered yet. true might mean the visitor accepted, or that this region does not require consent. The boolean alone cannot tell you whether the visitor decided anything.


currentVisitorConsent() returns the preferences selected by the user and does not mix in location or merchant configuration:


window.Shopify.customerPrivacy.currentVisitorConsent();
// → {marketing: 'yes', analytics: 'no', preferences: 'yes', sale_of_data: ''}


Value

Meaning

'' (empty string)

The visitor has not yet granted or denied consent

'yes'

The visitor actively granted consent

'no'

The visitor actively denied consent


Empty before the visitor decides, filled in afterwards. That transition is the proof that Pandectes wrote the decision into Shopify.


Option 2: On-Screen Theme Test


This snippet automates the Quick Console Check. It prints the consent state to the console when the page loads, and again when the visitor decides. It only reads and logs. It changes nothing.


Where to paste the theme snippet


  1. Shopify admin → Online Store → Themes
  2. On your live theme click … → Edit code (safer: Duplicate the theme first and test on the copy)
  3. Open layout/theme.liquid
  4. Paste the snippet immediately before the closing </body> tag
  5. Save


{% comment %} PANDECTES CONSENT CHECK: temporary, remove after testing {% endcomment %}
<script>
(function () {
var pandectesTag = '[Pandectes theme check]';

function pandectesSnapshot(label) {
var cp = window.Shopify && window.Shopify.customerPrivacy;
if (!cp) {
console.warn(pandectesTag, label, 'API not available');
return;
}

var consent = cp.currentVisitorConsent();

console.log('%c' + pandectesTag + ' ' + label, 'font-weight:bold;color:#0a7');
console.table({
'currentVisitorConsent().preferences' : consent.preferences,
'currentVisitorConsent().analytics' : consent.analytics,
'currentVisitorConsent().marketing' : consent.marketing,
'currentVisitorConsent().sale_of_data': consent.sale_of_data,
'preferencesProcessingAllowed()' : cp.preferencesProcessingAllowed(),
'analyticsProcessingAllowed()' : cp.analyticsProcessingAllowed(),
'marketingAllowed()' : cp.marketingAllowed(),
'saleOfDataAllowed()' : cp.saleOfDataAllowed()
});
}

document.addEventListener('visitorConsentCollected', function (event) {
console.log(pandectesTag, 'visitorConsentCollected →', event.detail);
pandectesSnapshot('AFTER the visitor decided');
});

(function pandectesWaitForShopify(tries) {
tries = tries || 0;
if (window.Shopify && window.Shopify.loadFeatures) {
window.Shopify.loadFeatures(
[{ name: 'consent-tracking-api', version: '0.1' }],
function (error) {
if (error) {
console.error(pandectesTag, 'could not load the Customer Privacy API', error);
return;
}
pandectesSnapshot('ON PAGE LOAD');
}
);
return;
}
if (tries > 100) {
console.error(pandectesTag, 'window.Shopify.loadFeatures never became available');
return;
}
setTimeout(function () { pandectesWaitForShopify(tries + 1); }, 100);
})();
})();
</script>


How to run the theme test


Open your storefront in a new Incognito window, open the Console, and follow these three steps.


Step 1: Before touching the banner


The banner is on screen. You have not clicked anything. Look for the ON PAGE LOAD table.


What you should see

Meaning

currentVisitorConsent().preferences"" (empty)

No decision made yet

currentVisitorConsent().analytics"" (empty)

No decision made yet

currentVisitorConsent().marketing"" (empty)

No decision made yet

preferencesProcessingAllowed()false

Nothing non-essential is allowed

analyticsProcessingAllowed()false

Nothing non-essential is allowed

marketingAllowed()false

Nothing non-essential is allowed


The empty strings are the important part. Shopify is saying this visitor has not decided yet.


Step 2: Click a button on the banner


Click Accept all. Two things must appear in the console immediately:


  1. A line visitorConsentCollected → {marketingAllowed: true, saleOfDataAllowed: …, analyticsAllowed: true, preferencesAllowed: true}
  2. A second table, AFTER the visitor decided:


What you should see

Meaning

currentVisitorConsent().preferences"yes"

Consent actively granted

currentVisitorConsent().analytics"yes"

Consent actively granted

currentVisitorConsent().marketing"yes"

Consent actively granted

preferencesProcessingAllowed()true

Processing now permitted

analyticsProcessingAllowed()true

Processing now permitted

marketingAllowed()true

Processing now permitted


If you click Reject all instead, the values become "no" and the Allowed() methods stay false. "no" is not the same as "": the visitor was asked and said no.


Step 3: Reload the page


Reload and look at the new ON PAGE LOAD table. The values from Step 2 must still be there. That proves Shopify stored the decision and returns it on later pages.


If Steps 1, 2, and 3 behave as above, the integration is writing consent to Shopify.


Now remove the snippet from layout/theme.liquid and save. Do not leave it on a live theme. It is a testing tool, not part of your store.


Option 3: Shopify Pixel Test


The theme snippet proves Shopify recorded the decision. This second snippet proves the decision reaches Shopify's web pixels (Google & YouTube, Meta, Klaviyo, and any custom pixel).


Pixels run inside a sandbox, separate from your theme. Shopify hands them the consent state directly. The snippet creates a small test pixel that reports what it was handed.


Where to paste the pixel snippet


  1. Shopify admin → Settings → Customer events
  2. Click Add custom pixel, name it Pandectes consent check
  3. Under Customer privacy, set Permission to Not required, so this test pixel always runs and can report the "before" state too
  4. Paste the code, click Save, then Connect


// Pandectes consent check. TEMPORARY test pixel. Disconnect and delete when finished.
const pandectesTag = '[Pandectes pixel check]';

function pandectesLog(label, cp) {
console.log(pandectesTag + ' ' + label, {
preferencesProcessingAllowed: cp.preferencesProcessingAllowed,
analyticsProcessingAllowed: cp.analyticsProcessingAllowed,
marketingAllowed: cp.marketingAllowed,
saleOfDataAllowed: cp.saleOfDataAllowed
});
}

let pandectesConsentState = init.customerPrivacy;
pandectesLog('state when the pixel started', pandectesConsentState);

api.customerPrivacy.subscribe('visitorConsentCollected', (event) => {
pandectesConsentState = event.customerPrivacy;
pandectesLog('state after the visitor decided', pandectesConsentState);
});

analytics.subscribe('page_viewed', (event) => {
pandectesLog('state at page_viewed', pandectesConsentState);
});


Why the long variable names? The pixel sandbox already defines globals such as status and name, so a plain let status = … is reported as a redefinition. Every name in both snippets is prefixed with pandectes. Keep them as they are.


What you should see in the pixel test


Again in a fresh Incognito window with the console open:


Moment

Expected log

Page loads, banner visible, nothing clicked

state when the pixel startedpreferencesProcessingAllowed, analyticsProcessingAllowed, and marketingAllowed all false

You click Accept all

state after the visitor decided → the same three are now true

You reload the page

state when the pixel started → the three are still true


saleOfDataAllowed is not part of this check. Outside regions with a data-sale opt-out it can be true from the start. That is expected.


These lines come from Shopify's pixel sandbox, so in Chrome they appear with a web-pixels…sandbox source next to them. If you do not see them, make sure the console filter is not set to "selected context only" and that no filter text is typed in the filter box.


If those three values go from false to true when you click Accept, Shopify is handing the consent state to pixels.


Disconnect and delete this test pixel as soon as you are finished. It is set to run without consent, so it must not stay on a live store.


Once the signal arrives, the rest is Shopify's job


Shopify's web pixels run inside sandboxed, cross-origin iframes that Shopify controls. No consent app can start them, stop them, or change what they send. The consent signal is the lever that exists.


The verification splits in two:


  1. Does the signal arrive? That is what the checks above answer. That is the part Pandectes is responsible for.
  2. Does Shopify act on the signal? That is a Shopify question, answered in Shopify's settings and, if needed, by Shopify support.


If currentVisitorConsent() goes from empty to 'yes' or 'no' when the visitor clicks, the app has done its job. If a pixel or a channel still behaves in a way you did not expect after that, look on Shopify's side:


Who

Does what

Pandectes

Shows the banner, collects the decision, writes it to Shopify via setTrackingConsent()

Shopify

Decides which web pixels load on the storefront and in checkout, gates sales channels, and applies consent to customer events, audiences, and checkout


  • Settings → Customer events: each pixel has its own Customer privacy → Permission setting. A pixel set to Not required will run regardless of consent, because that is what the store told Shopify to do.
  • Each sales channel's own configuration. The Google & YouTube channel, for example, builds its own Google consent signal from Shopify's API. That translation is Shopify's code.
  • Shopify support, for anything that remains after the API holds the right value.


Think of it as a light switch: Pandectes is the switch, Shopify is the wiring and the lamps. Once you have verified the switch flips, a lamp that stays on is a wiring question.


Headless and custom storefronts


The same API, the same methods, and the same meaning for '', 'yes', and 'no' apply on a headless store. The Quick Console Check works unchanged. What changes is how you run the two snippets, plus one extra setting.


You have no theme.liquid


Your storefront pages are your own code, so there is nowhere to paste the theme snippet as written. Two options:


Option A: paste it in the browser console (nothing to deploy).
Copy everything between <script> and </script> in the theme snippet, open your storefront in a clean Incognito window with the banner showing, paste it into the Console, and press Enter. You get the ON PAGE LOAD table immediately, and the AFTER the visitor decided table when you click the banner.


Console code is wiped by a page reload. For the reload step, reload first, then paste the snippet again to read the stored values.


Option B: put it in your own root layout.
Add the same <script> block to whatever renders on every page: the root layout in Hydrogen or Next.js, index.html in a Vite/React app, and so on. Remove it once testing is done.


Snippet 2 runs on checkout, not on your storefront


Shopify's web pixels do not run on pages you host yourself. They run on surfaces Shopify owns: checkout and the order status / thank-you page. To see the pixel snippet output, add a product to the cart and continue to checkout with the console open. The consent decision made on your storefront should already be in place when the pixel starts.


One extra setting: your two domains


On a headless store, Shopify needs the storefront and checkout domains before it can record consent. Shopify's documentation requires these values for custom storefronts:


  • headlessStorefront
  • storefrontRootDomain
  • checkoutRootDomain
  • storefrontAccessToken


Pandectes sends them with every consent write, from your app configuration. If they are missing or wrong in the Pandectes admin, the write to Shopify cannot succeed, and currentVisitorConsent() will keep returning empty strings. If your headless test fails, check these first.


The domains also have to be a matching pair: storefront domain and checkout domain must belong to the same store, so the decision made on the storefront is the one checkout sees.


What you should not use to verify


These checks either measure something else, or are not part of the documented API.



Many merchants expect shouldShowBanner() to be true before consent and false afterwards, and report a bug when it stays true.


It is not a consent method. Shopify documents it as indicating whether the current customer is in a region that is configured to show a cookie banner. A German visitor is still in Germany after Accept, so the answer is still true. A shouldShowBanner() that stays true after consent is expected.


The same applies to saleOfDataRegion(), which indicates whether the current customer is in a region configured for data-sale opt-outs. That is also a region check.


Don't rely on getTrackingConsent, getRegulation, or isRegulationEnforced


These do not appear in Shopify's Customer Privacy API documentation. They are internal values that Shopify can change or remove. In particular, getTrackingConsent() returning "yes" before consent is not evidence that anything was granted.


Don't use userCanBeTracked or userDataCanBeSold


These exist, but Shopify lists them under legacy documentation and says not to begin new integrations with them. Use currentVisitorConsent() and the four Allowed() methods instead.



Shopify's documentation says not to read or modify Shopify cookies directly. Shopify does not promise that its consent cookie is readable from JavaScript, that it has a particular name, or that it exists in a given release. Not finding it proves nothing.


Don't judge the app by whether a pixel or a network request still fires


A request leaving the browser is a Shopify outcome. If the consent signal is correct and a Shopify pixel still fires, Shopify was told the right thing and chose to run the pixel, usually because that pixel's Permission is Not required in Settings → Customer events, or because the sales channel is configured to run without consent. Fix it there.


Don't compare entries in window.dataLayer


The dataLayer is Google Consent Mode, a separate system from the Shopify Customer Privacy API. Pandectes writes to both, and Shopify's Google & YouTube channel also writes its own entries. Several consent entries there are normal and do not verify the Shopify API.


Don't expect a setTrackingConsent call on every page load


Pandectes only writes to Shopify when the state changes. If Shopify already holds the same decision, the app does not push it again. On a reload after consent, you should see the stored values but no new write.


Don't worry about sale_of_data staying empty


sale_of_data is relevant where a data-sale opt-out applies (mainly certain US states). If the visitor is not in such a region, or Do not sell my data is not enabled in Pandectes, this field is left out of what Pandectes sends. An empty sale_of_data for an EU visitor is expected.


Quick reference: the documented methods


Method

Returns

What it tells you

currentVisitorConsent()

{preferences, analytics, marketing, sale_of_data} each 'yes' / 'no' / ''

The visitor's own decision. Use this one.

preferencesProcessingAllowed()

true / false

Whether preference processing is allowed (region + settings + consent combined)

analyticsProcessingAllowed()

true / false

Whether analytics processing is allowed (region + settings + consent combined)

marketingAllowed()

true / false

Whether marketing is allowed (region + settings + consent combined)

saleOfDataAllowed()

true / false

Whether sharing data with third parties is allowed

shouldShowBanner()

true / false

Region only: whether a banner should be shown in this region

saleOfDataRegion()

true / false

Region only: whether a data-sale opt-out applies here

getRegion()

e.g. "DEHH"

The visitor's location, ISO 3166-2 format

visitorConsentCollected event

{marketingAllowed, saleOfDataAllowed, analyticsAllowed, preferencesAllowed}

Fires each time consent changes


If the test really does fail


First decide which half failed.


If the signal is correct, meaning currentVisitorConsent() moves from empty to 'yes' or 'no' when you click, then the app is writing consent. Anything else belongs to Shopify. Check each pixel's Permission in Settings → Customer events, then sales channel configuration, then Shopify support.


If the signal is missing, meaning currentVisitorConsent() still returns empty strings after you clicked a banner button, check in this order:


  1. The Pandectes app embed is enabled in the theme you are testing (Online Store → Themes → Customize → App embeds).
  2. You are testing the live theme, not a preview of a different one.
  3. No other consent app is installed. Two consent apps writing to the same API will conflict. Only one may be active.
  4. You are not in an admin preview session, which can behave differently from a real visit.
  5. Headless stores only: the storefront domain, checkout domain, and storefront access token are filled in correctly in the Pandectes admin.


Still failing? Contact [email protected] and include:


  • your store domain (and, for headless, your storefront and checkout domains)
  • the visitor country you tested from
  • the console output of the Quick Console Check or of the theme snippet, at all three moments (before clicking / after clicking / after reload)


Clean up when you're done


Do not skip this step. The Quick Console Check installs nothing. Both snippets are temporary.


  1. Theme snippet: open layout/theme.liquid, delete the block that starts with {% comment %} PANDECTES CONSENT CHECK, and Save. If you tested on a duplicated theme, you can delete that theme copy.
  2. Pixel snippet: go to Settings → Customer events, open Pandectes consent check, Disconnect it, then Delete it.
  3. Headless stores: if you added the script to your own layout, remove it and redeploy. If you used the browser console, closing the tab is enough.


Why it matters:


  • The theme snippet prints consent details to the console of every visitor, on every page, until you remove it.
  • The test pixel is configured with Permission: Not required, so it runs before consent by design. That is correct for a short test and wrong for a live store.
  • Both add work to every page load for no benefit once verification is done.


Removing them does not change the integration. The app keeps reporting consent to Shopify as before.


Summary


Verify this integration with currentVisitorConsent(). Empty strings before a choice, 'yes' or 'no' immediately after, and the same values after reload, mean Pandectes wrote the decision to Shopify. Pixel loading, sales channels, and checkout behavior after that point are Shopify's responsibility. Remove any temporary theme or pixel snippets when you are finished.

Updated on: 10/09/2026

Was this article helpful?

Share your feedback

Cancel

Thank you!