Shopify · Webhook · GDPR
GDPR compliance webhooks for Shopify apps (HMAC and mandatory webhook…
Every app published on the Shopify App Store must handle the GDPR compliance webhooks. It is not optional: Shopify's automated reviewer sends exactly these webhooks, and if your app doesn't handle them or gets signature verification wrong, your submission is rejected. It's the same "small but blocking" problem as eBay's account deletion webhook: a mandatory endpoint you must implement well, once.
The three mandatory webhooks
| Topic | When it arrives | What you must do |
|---|---|---|
| customers/data_request | A customer requests their data | Collect and provide the personal data your app stores |
| customers/redact | A customer must be erased | Delete/anonymise that customer's personal data |
| shop/redact | 48 hours after uninstall | Delete all data for that shop |
Every app distributed through the store must respond to these requests, even if it doesn't collect personal data.
Verify the HMAC signature on the raw body
Each webhook includes the X-Shopify-Hmac-Sha256 header: a base64 HMAC-SHA256 that Shopify computes with the app's client secret over the request body. You must recompute and compare it. The catch: compute the HMAC over the raw body (Buffer), not the parsed JSON object. Middleware like express.json() breaks verification because it alters the bytes.
app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
const received = req.get('X-Shopify-Hmac-Sha256');
const digest = crypto
.createHmac('sha256', process.env.SHOPIFY_API_SECRET)
.update(req.body) // Buffer: the raw body, not the parsed object
.digest('base64');
// constant-time comparison to avoid timing attacks
const ok = crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(received));
if (!ok) return res.status(401).send('Unauthorized');
// handle customers/data_request, customers/redact, shop/redact
return res.status(200).send('OK');
});
What to respond, and how fast
- valid signature: respond
200quickly, then process asynchronously; - invalid signature or unknown shop: respond
401and do nothing; customers/data_request: you must provide the data (typically within 30 days), not necessarily in real time in the response;shop/redact: arrives about 48 hours after uninstall, so plan a deferred deletion path.
Registering the webhooks
For public apps the recommended way is to declare the compliance topics in the shopify.app.toml file (managed via the Shopify CLI), pointing them at a single endpoint that branches on the topic field. Avoid manual registrations that diverge across environments.
Keep the client secret safe
The whole verification depends on the client secret: if it ends up hardcoded or in logs, the signature protects nothing. Store it in a secret manager and inject it at runtime, as described in the guide on securing API secrets and credentials with Azure Key Vault. If the app also syncs catalog and orders, it's worth reading how to structure the Shopify–ERP integration.
Common mistakes
- computing the HMAC over the parsed JSON instead of the raw body;
- handling only business webhooks and ignoring the GDPR topics (Shopify's check fails);
- a non-constant-time comparison (vulnerable to timing attacks);
- processing synchronously and timing out instead of replying 200 immediately and queueing.
Conclusion
Compliance webhooks are a low-volume but high-impact requirement: either they're correct, or the app doesn't pass review. The recipe is simple: verify the HMAC on the raw body, respond 200/401, queue the processing and protect the client secret. References: Privacy law compliance (Shopify) and delivering webhooks over HTTPS.