Shopify’s customers/redact webhook is not a nice-to-have. When a merchant deletes a customer, closes a store, runs a mass cleanup, or compliance tooling forces erasure, your app is expected to clear protected data and leave an audit trail. Do that wrong under load and you get deadlocks, duplicate work, or worse: a storm of redaction webhooks you caused yourself.
We hit that edge while hardening Rapora, our guided-selling app for furniture showrooms. Any bulk customer delete in Shopify—demo resets, deleted development stores, or mass fixture wipes—fires customers/redact once per customer. Do that often enough and your privacy handler stops being a quiet compliance path and becomes a self-inflicted DDoS.
Don’t manufacture redacts you don’t need
Demo and seed fixtures in Rapora are tagged (rapora-demo-fixture). On reset we still wipe tagged draft orders and clear local CRM state. We no longer bulk-delete those tagged customers on every reset—the same pattern applies whenever you would otherwise mass-delete customers you are about to recreate or that never stored protected data in your app.
Seed upserts customers by email instead. After seed, we remove only orphan tagged customers that are no longer in the current plan. Real shop customers—untagged—are never touched. The catalog stays put.
That change alone stops many webhook avalanches: bulk ops stop manufacturing redaction events for customers you do not need to erase through Shopify’s delete path.
Make redact cheap when there is nothing to erase
When a redact webhook does arrive, Rapora first checks whether this shop has any local profile or visit rows for that Shopify customer ID. If not, we log the access and return zeros—no transaction, no deletes, no lock contention.
Most noise from mass deletes (and many legitimate webhooks for customers who never opened a showroom visit) should end there.
Serialize the real work
When local data exists, erasure runs inside a transaction with a PostgreSQL advisory lock keyed by shop + customer. Concurrent redacts for the same person queue instead of racing. Transient deadlocks (Postgres 40P01) retry with backoff instead of failing the webhook and inviting Shopify to hammer you again.
Visits keep their operational history but drop the Shopify customer ID. Profiles that held protected fields are deleted. Access logs still record that a redact happened—because compliance cares about the attempt, not only the rows you removed.
What this is not
This is not a substitute for Shopify’s mandatory privacy webhooks, and it is not a guided-selling pitch. It is the unglamorous layer under a POS-adjacent app that stores customer context for floor conversations: design bulk delete and seed lifecycles so they do not fight GDPR handlers, and design those handlers so empty work is free and contested work is ordered.
If you build Shopify apps that keep customer-shaped data beside the Admin API, treat mass customer deletes as a product of your privacy model—not an afterthought script that deletes “everything tagged” and hopes the webhooks behave.
For the showroom product side of this stack, see rapora.shop. For custom software that has to live with compliance webhooks in production, talk to us about software development.
