Views: 9
Building a public Shopify App requires meeting stringent compliance standards. Two of the most common stumbling blocks in the App Store review process are failures for Mandatory Compliance Webhooks (GDPR/CCPA) and HMAC Signature Verification.
This Shopify app compliance fix guide provides a focused, step-by-step solution, emphasizing the essential code needed to pass these automated checks, regardless of your application’s framework or database structure.
Step 1: Mandatory GDPR Webhooks Registration via shopify.app.toml
The modern, recommended way to register the mandatory GDPR webhooks (customers/data_request, customers/redact, and shop/redact) is by using the shopify.app.toml file.
1.1. Configure shopify.app.toml
Add the following configuration block to your shopify.app.toml file. This links all three mandatory compliance topics to a single, unified webhook endpoint (uri).
[webhooks]
api_version = "2025-10" # Use the latest or target API version
[[webhooks.subscriptions]]
# These are the three mandatory GDPR/CCPA compliance topics
compliance_topics = ["customers/data_request", "customers/redact", "shop/redact"]
# Set this to your app's public webhook endpoint URL
uri = "YOUR_PUBLIC_WEBHOOK_URL_HERE"
1.2. Deploy the Configuration
After saving the changes, deploy the configuration using the Shopify CLI to register the new webhook subscriptions:
shopify app deploy
This ensures the check for Provides mandatory compliance webhooks passes.
Step 2: Implement Robust HMAC Verification in Your Handler
This is the most critical step for security and compliance. Your webhook handler must correctly verify the HMAC signature and, crucially, return a 401 Unauthorized status code if the HMAC fails or if the shop sending the webhook is unknown (as the compliance test often sends a dummy shop).
2.1. The Core HMAC Verification Function (PHP)
This function takes the raw data, the HMAC header, and your app’s secret key, and performs the validation.
/* ------------------------------
HMAC Verification Function
------------------------------ */
function codoshopify_verify_webhook_hmac_internal($data, $hmac_header, $secret) {
// 1. Calculate HMAC using the raw POST body data and the App's Secret
$calculated_hmac = base64_encode(hash_hmac('sha256', $data, $secret, true));
// 2. Safely compare using hash_equals() to prevent timing attacks
return hash_equals($calculated_hmac, $hmac_header);
}
2.2. The Webhook Handler Logic (Focus on Compliance Checks)
The handler must perform three essential checks before processing any data: Identify Shop, Retrieve Secret, and Verify HMAC.
PHP
/* ------------------------------
Webhook Handler (Compliance Focus)
------------------------------ */
// Set your handler to respond to the configured webhook URL (e.g., /codoshopify-webhook/)
function handle_shopify_webhook() {
// 1. Crucial for compliance: prevent any unwanted output or caching in case of WordPress only
nocache_headers();
while (ob_get_level()) ob_end_clean();
// Extract necessary data from the request
$raw = file_get_contents('php://input'); // The unparsed POST body is REQUIRED for HMAC
$hmac = $_SERVER['HTTP_X_SHOPIFY_HMAC_SHA256'] ?? '';
$topic = strtolower(str_replace('-', '_', $_SERVER['HTTP_X_SHOPIFY_TOPIC'] ?? ''));
$shop = $_SERVER['HTTP_X_SHOPIFY_SHOP_DOMAIN'] ?? '';
// --- YOUR APP-SPECIFIC LOGIC START ---
// Placeholders for your app's data retrieval.
// Replace this section with your framework's method to find the shop and secret.
$secret = YOUR_FUNCTION_TO_GET_APP_SECRET_BY_SHOP_DOMAIN($shop);
// 2. COMPLIANCE CHECK: Shop Not Found / Secret Missing
// Shopify compliance bot may send a random shop. If we can't find it (or the secret),
// we MUST return 401.
if (empty($secret)) {
http_response_code(401);
echo json_encode(['error' => 'Shop not found or secret missing, unauthorized']);
exit;
}
// --- YOUR APP-SPECIFIC LOGIC END ---
// 3. HMAC VERIFICATION
$valid_hmac = codoshopify_verify_webhook_hmac_internal($raw, $hmac, $secret);
if (!$valid_hmac) {
// FORCE 401 UNAUTHORIZED on HMAC failure
while (ob_get_level()) ob_end_clean();
http_response_code(401);
echo json_encode(['error' => 'Invalid HMAC']);
exit;
}
// 4. PROCESS WEBHOOK (HMAC is valid)
switch ($topic) {
case 'app_uninstalled':
case 'shop_redact':
case 'customers_redact':
case 'customers_data_request':
// Add your topic-specific processing logic here
break;
}
// 5. Send SUCCESS Response
header('Content-Type: application/json');
http_response_code(200);
echo json_encode(['status' => 'ok']);
exit;
}
2.3. Key Compliance Takeaways
- Raw Data: You must use the raw, unparsed POST body (
file_get_contents('php://input')) for HMAC calculation. - Forced
401: If the HMAC verification fails or you cannot retrieve the App Secret for the shop, your handler must return anHTTP 401 Unauthorizedstatus code immediately, clearing any buffered output. - GDPR Topics: All three compliance topics must result in a
200 OKresponse if the HMAC is valid, acknowledging receipt of the request.
In case of any confusions, please feel free to ask the questions in the comments box below.