App reviews, troubleshooting, and recommendations
Problem:
I’m building a Shopify app using Remix and have been struggling to properly configure the required Privacy Law Compliance Webhooks. The documentation is somewhat inconsistent, and I’m unsure how to properly register and handle these webhooks.
What I’ve Noticed:
Current shopify.app.toml Configuration:
# Learn more about configuring your app at https://shopify.dev/docs/apps/tools/cli/configuration client_id = "05285bcaafb1760bd6804d389165f97e" name = "test-subscription" handle = "test-subscription" application_url = "https://watches-transport-boulder-generally.trycloudflare.com" embedded = true [build] automatically_update_urls_on_dev = true include_config_on_deploy = true [webhooks] api_version = "2025-04" [[webhooks.subscriptions]] topics = [ "app/uninstalled" ] uri = "https://working-photographer-college-race.trycloudflare.com/webhooks/app/uninstalled" [[webhooks.subscriptions]] topics = [ "customers/create" ] uri = "https://working-photographer-college-race.trycloudflare.com/webhooks/customers/create" [access_scopes] # Learn more at https://shopify.dev/docs/apps/tools/cli/configuration#access_scopes scopes = "read_customers,read_orders,read_products,write_customers,write_orders,write_products" [auth] redirect_urls = [ "https://watches-transport-boulder-generally.trycloudflare.com/auth/callback", "https://watches-transport-boulder-generally.trycloudflare.com/auth/shopify/callback", "https://watches-transport-boulder-generally.trycloudflare.com/api/auth/callback" ] [pos] embedded = false
Webhook Handler (Remix):
import { ActionFunctionArgs, json } from "@remix-run/node"; import { authenticate } from "../shopify.server"; /** * Central Webhook Handler for Shopify * * This route processes both regular webhooks and required compliance webhooks: * * Regular Webhooks: * - app/uninstalled: Triggered when the app is uninstalled * - customers/create: Triggered when a new customer is created * * Compliance Webhooks: * - customers/data_request: Requests to view stored customer data * - customers/redact: Requests to delete customer data * - shop/redact: Requests to delete shop data */ export const action = async ({ request }: ActionFunctionArgs) => { try { // HMAC verification is automatically performed by authenticate.webhook // If verification fails, the function throws an error which is returned as a 401 const { topic, shop, session, payload } = await authenticate.webhook(request); console.log(`Webhook received: ${topic} for shop ${shop}`); console.log(`Payload: ${JSON.stringify(payload, null, 2)}`); switch (topic) { // Regular Webhooks case "app/uninstalled": await handleAppUninstalled(shop, session, payload); break; case "customers/create": await handleCustomerCreate(shop, session, payload); break; // Compliance Webhooks case "customers/data_request": await handleCustomersDataRequest(shop, session, payload); break; case "customers/redact": await handleCustomersRedact(shop, session, payload); break; case "shop/redact": await handleShopRedact(shop, session, payload); break; default: console.warn(`Unknown webhook topic: ${topic}`); } // Return 200 OK to confirm that the webhook was received return json({ success: true }); } catch (error) { console.error(`Error processing webhook:`, error); // Log error details for better diagnosis if (error instanceof Error) { console.error("Error message:", error.message); console.error("Stack:", error.stack); } // Return 200 despite error to acknowledge the request // We will handle the error later return json({ success: true }); } }; /** * Handles app uninstallation */ async function handleAppUninstalled(shop: string, session: any, payload: any) { console.log('App was uninstalled from shop:', shop); // TODO: Implementation for your app // 1. Clean up all shop-related data // 2. Remove permissions or tokens // 3. Update database status } /** * Handles the creation of a new customer * Code integrated from webhooks.customers.create.ts */ async function handleCustomerCreate(shop: string, session: any, payload: any) { console.log("📣 Processing webhook for customers/create"); console.log(`🏪 Shop: ${shop}`); try { // Format data for the external API const userData = { user: { id: payload.id?.toString() || "", email: payload.email || "", first_name: payload.first_name || "", last_name: payload.last_name || "", source: "shopify", created_at: payload.created_at || new Date().toISOString() } }; console.log("📤 Sending data to webhook.site:", JSON.stringify(userData, null, 2)); // Send data to webhook.site (for testing purposes) const response = await fetch("https://webhook.site/3d67bbe9-3b68-4c98-a11c-d33a25373074", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(userData), }); const responseText = await response.text(); console.log("📩 Webhook.site status:", response.status); console.log("📩 Webhook.site response:", responseText); // TODO: Implement your own business logic here // e.g., storing customer data in the database } catch (error) { console.error("❌ Error processing customer-create webhook:", error); // Log error but continue execution // Webhook handlers should not throw errors } } /** * Processes requests to view customer data * * Example payload: * { * "shop_id": 954889, * "shop_domain": "shop-name.myshopify.com", * "orders_requested": [299938, 280263, 220458], * "customer": { * "id": 191167, * "email": "john@example.com", * "phone": "555-625-1199" * }, * "data_request": { * "id": 9999 * } * } */ async function handleCustomersDataRequest(shop: string, session: any, payload: any) { console.log('Data Request received for shop:', shop); // TODO: Implementation for your app // 1. Collect all relevant customer data for the specified customer.id or customer.email // 2. Create a complete summary of all data your app stores // 3. Provide this data to the merchant through a secure method // (e.g., encrypted download link, API response, or email notification) // If you use a database, you would run queries here to gather all customer data // Example for logging purposes console.log('Data requested for customer:', payload.customer); // In a complete implementation, you would retrieve all stored data here // and send it to the merchant } /** * Processes requests to delete customer data * * Example payload: * { * "shop_id": 954889, * "shop_domain": "shop-name.myshopify.com", * "customer": { * "id": 191167, * "email": "john@example.com", * "phone": "555-625-1199" * }, * "orders_to_redact": [299938, 280263, 220458] * } */ async function handleCustomersRedact(shop: string, session: any, payload: any) { console.log('Customer Redact Request received for shop:', shop); // TODO: Implementation for your app // 1. Identify all stored data for the specified customer // 2. Delete or anonymize this data // 3. Document the deletion for compliance verification // Example for logging purposes console.log('Deletion requested for customer:', payload.customer); // In a complete implementation, you would delete all customer data here // e.g., with Prisma (if your app uses it): // await prisma.customerData.deleteMany({ // where: { // customerId: payload.customer.id.toString(), // }, // }); } /** * Processes requests to delete shop data * * Example payload: * { * "shop_id": 954889, * "shop_domain": "shop-name.myshopify.com" * } */ async function handleShopRedact(shop: string, session: any, payload: any) { console.log('Shop Redact Request received for shop:', shop); // TODO: Implementation for your app // 1. Identify all data stored for the entire shop // 2. Delete or anonymize this data // 3. Document the deletion for compliance verification // Example for logging purposes console.log('Deletion requested for shop:', payload.shop_domain); // In a complete implementation, you would delete all shop data here // e.g., with Prisma (if your app uses it): // await prisma.shopData.deleteMany({ // where: { // shopDomain: payload.shop_domain, // }, // }); // If your app stores files, these should also be deleted }
Questions:
1. Do webhook URIs need to be absolute URLs or can they be relative paths in the shopify.app.toml config?
2. Can compliance and regular webhooks be defined together, or should they be kept in separate blocks?
3. How can I confirm if compliance webhooks are successfully registered with Shopify?
4. Do I need a separate route handler for each compliance webhook, or can one shared handler handle all three topics?
5. Is there a specific API version required for compliance webhooks?
6. Has anyone implemented these compliance webhooks in a Remix app using Shopify CLI 3.0? Any best practices or working examples?
Discover how to increase customer engagement on your store with articles from Shopify A...
By Jacqui Apr 23, 2025Hey Community 👋 Did you know that March 15th is National Everything You Think Is W...
By JasonH Apr 1, 2025Discover how to increase the efficiency of commerce operations with Shopify Academy's l...
By Jacqui Mar 26, 2025