Act V: The Appearance of Completion
Compliance Is A State, Not A Project
The auditor was not unkind about it. That is the part the CTO kept replaying afterward—that the man across the table had asked the question gently, almost as a formality, the way you ask for a receipt you fully expect to be handed. They were most of the way through a privacy review at a brand doing two hundred million a year, and it had been going well. The order data was clean. The native deletion worked. The commercial apps in the stack all had their webhooks wired, because the vendors had wired them, because the vendors had to. And then the auditor looked at his list and said, in the same even tone he’d used for everything else, and the loyalty app—where’s the deletion endpoint for that one?
The CTO opened the code. He knew the app well; he’d been there when it was scoped, three years and one platform ago, back when loyalty was a quarter’s worth of roadmap and nobody in the room had said the word erasure once. He scrolled. He searched the repo. He searched it again with a different term, because surely. There was no deletion endpoint. There had never been a deletion endpoint. The app had been writing customer data into its own little database, faithfully, for three years, and there was no door out of that database because no one had thought to build one—not out of negligence, not out of some failure of character, but because at the time the requirement was an abstraction and the loyalty program was a deadline. The reasons were ordinary. The gap was total.
He did not panic, exactly. But he had the specific feeling of a man who has just discovered that a room he passes every day has no floor, and has had no floor the entire time, and the only reason he hasn’t fallen through is that he’d never once stepped on it.
So let us talk about that floor, because the brand reading this has one too. The reassuring thing people say about Shopify and privacy law is that the platform handles GDPR. The accurate thing—and the difference between the two will, on some Tuesday you cannot currently see, be sitting across a table from your CTO with a clipboard—is that Shopify partially handles GDPR. The whole chapter lives inside that adverb.
What the platform genuinely handles is real, and worth knowing precisely, because you will be tempted to either over-trust it or wave it off, and both are mistakes. When a customer asks to be erased, Shopify deletes what Shopify natively holds: order history, addresses, stored payment methods. Gone, properly, no asterisk. And every app in the ecosystem—every one, as a condition of existing in the ecosystem—is required to expose a deletion webhook. When erasure fires, Shopify dutifully knocks on the door of each installed app and says this person, please, clear your records, and the app is obligated to comply. There is even a side effect tucked into this machinery that is worth carrying around in your head, because it surprises people at the worst moment: erasing a customer’s personal data cancels any preauthorized payment on that account. The preorder. The active subscription. Erasure is not a quiet bookkeeping operation; it reaches into the commercial relationship and ends things. Knowing that in advance is the difference between a clean process and a confused support escalation about why a VIP’s subscription evaporated the same week she filed a privacy request.
That is the plumbing, and it is good plumbing. The trouble is that the plumbing only reaches the rooms Shopify built and the rooms its vendors built. It does not reach the room you built.

Here is the rule, and the reason it is dangerous. Custom apps—the bespoke ones your team wrote, the loyalty program, the personalization layer, the thing that quietly writes customer records to a database off to the side—are bound by exactly the same requirement as every commercial app in the store. They must expose a deletion webhook. They must actually delete when it fires. The law does not have a soft spot for code you wrote yourself.
But nothing enforces it. That is the entire problem in one sentence. A commercial app gets its deletion webhook because the vendor had to build one to get listed, had to keep it working to stay listed, has a compliance team and a review process and a business reason to care. Your custom app got built because there was a launch date. The deletion webhook was not on the ticket, because at the time the requirement was a paragraph in a regulation nobody on the build team had read, and the launch date was a real thing with a real number attached. So the commercial half of your stack is covered—handsomely, automatically, invisibly—and the custom half, the half with your brand’s actual cleverness in it, the half you are proudest of, is the half with no floor. In the compliance audits we have run, this is the single most consistently found gap. Not occasionally. Consistently. It is so reliable that you should assume, right now, before you check, that you have it.
(Senior Engineer Reading This At 11 PM, you already know which app it is. You thought of it three paragraphs ago. That feeling is the chapter working.)
The same shape repeats one room over, in data portability. Shopify gives you a button labeled Request customer data, and it is easy to read that button as the answer to a portability request. It is not the answer; it is the first paragraph of the answer. That export contains what Shopify knows about the customer—not what your apps know. If a customer exercises their right to portability and you hand them the Shopify export, you have handed them a partial response and called it complete, which is its own kind of finding. Stitching together the full picture across your app stack is the brand’s job, and nobody is going to do it for you, and the time to discover that is not the day the request arrives.
CCPA, mercifully, mostly takes care of itself if you have done the above. The two frameworks overlap heavily on the rights that generate work—deletion, portability—and GDPR is the stricter of the pair. Treat GDPR as the floor and you have built high enough to clear CCPA in the same motion. They diverge on opt-out rights for the sale of personal data, but for the DTC brand that isn’t selling its customer lists to third parties—which is most of them—that divergence stays theoretical. Build to the stricter standard once; stop maintaining two compliance postures that mostly say the same thing.
And then there is the directive nobody flies over from the US to worry about, which is precisely why it is the one that catches them.
The Omnibus Directive is about pricing honesty and review honesty, and the US brand’s instinct is to assume it doesn’t apply, because the brand is incorporated in Delaware and Omnibus sounds European and far away. The instinct is wrong, and the way it is wrong matters. GDPR applies when you process the personal data of EU residents. Omnibus applies when you market to EU consumers. Those are different triggers. You can trip the second without going anywhere near the first. If you are running a campaign aimed at European shoppers—and your Markets configuration, your translated storefront, your euro pricing all say you are—then you are inside the directive’s reach.
There is no “we’re American” exemption. Say it out loud once, because some part of the org is quietly assuming there is.
The piece of Omnibus that catches the most brands is the thirty-day price-history rule, and it is worth being exact about, because the imprecise version of it is useless. Any time you show a promotional price—a was/now label, a sale badge, a Black Friday strikethrough—you are required to display the lowest price you actually charged for that product in the thirty days before the promotion. Not the recommended retail price. Not the list price you’d love to anchor against. The lowest transacted price. The number a real customer actually paid, at the bottom of whatever quiet little promotion you ran six weeks ago and forgot about. That is the number the strikethrough has to honor.
Shopify maintains no such log. There is no native price-history feature waiting to be switched on. The brands that handle this do it one of two ways: an app built for the purpose, of which several exist, or a custom job—usually a daily operation that snapshots current prices and writes them to a metafield or an external store, accruing the history one day at a time. Either approach is fine. What is not fine, what is the actual failure mode, is the brand that sets the thing up once, congratulates itself, and lets it lapse. Because this is not a checkbox you tick at launch. It is a process that has to run continuously—through every campaign, across every market and every variant where you discount—and a price log with a three-week hole in it from the time the snapshot job silently died is not a price log. It is a liability with good intentions.

Reviews are the directive’s second front, and they fail almost everyone by default, which is the unsettling part—not that brands break the rule on purpose but that the rule is broken in the factory settings. Omnibus prohibits suppressing negative reviews and requires you to disclose how reviews are collected, processed, and verified. A shopper looking at your review block should be able to tell, somewhere near it, whether the reviews come from verified purchasers, how moderation works, and whether any filtering is applied. Now go look at how your review app shipped. The standard configuration lets you feature your highest-rated reviews, sort by score, quietly suppress anything below a threshold—and none of that is compliant, and a great many brands have been running exactly those defaults, in good conscience, for years. The good news, and it is genuinely good news after the price-history paragraph, is that the fix is small. Audit the app’s settings. Dial back the filtering you couldn’t stand up and honestly describe. Add a sentence or two near the reviews explaining how you collect and verify them. An afternoon, not a quarter.
Personalized pricing earns a brief mention on the way past, for the few it applies to. If you display different prices to different segments—member tiers, B2B pricing, anything algorithmically adjusted at the point of display—Omnibus wants that disclosed where the price is shown. On Shopify this level of price customization takes Shopify Functions or real custom development, so most brands aren’t doing it and can let the paragraph go. If yours is, you already know, and now you know there’s a disclosure attached.
So here is where to put your hands, in rough order of how much they’ll cost you, which is also—pleasingly, for once—rough order of how easy they are to fix.
Start with the review app, because it is an hour and it closes a gap a regulator could see from the storefront without logging into anything. Open the settings, look at what filtering is on, add the disclosure language if it’s missing. Done before lunch.
Then the custom-app deletion endpoints—the loyalty app, the personalization layer, every bespoke thing in the stack that writes customer data somewhere of its own. For each one, confirm there is a deletion webhook and that it does what it claims rather than merely returning a polite two-hundred and deleting nothing. This is the compliance gap that looks handled until you look closely, and the easiest one to fix once you’ve found it. The looking is the whole task. The fixing is usually an afternoon per app. The not-looking is the part that ends up across a table from your CTO.
Then, last, because it takes the most setup and you cannot do it in an afternoon, the price-history infrastructure. If you are running promotions into EU markets without a continuous price log, you are exposed on every single campaign, and the right time to have it standing is before your next sale, not in the forensic reconstruction afterward.
What ties these together is not a launch checklist. It is a cadence. App stacks grow faster than compliance audits happen—somebody installs a new app on a Thursday and your surface area changed and nobody filed a ticket about it. Price logs have to survive campaign cycles and the engineer who wrote the snapshot job leaving for a competitor. Review configurations drift every time a theme update resets a setting. None of this holds still, which means compliance cannot be a thing you achieved in the past tense. It is a state your store is in, or out of, right now, this minute, while you read this—and the only honest way to know which is to have built the habit of checking.
The CTO with the floorless loyalty app fixed it, by the way. It was, in the end, an afternoon: a deletion webhook, a process behind it, a test to prove the door actually opened. The fix was never the hard part. The hard part was that for three years the only thing standing between him and the finding was that nobody had stepped on the floor—and the audit was simply the day someone did. You don’t get to choose when that day comes. You only get to choose whether you went looking first.
Here lies the loyalty app that delighted ten thousand customers and could not, when finally asked, forget a single one of them. It was compliant in every room but the one it built itself.