Shopify Webhooks Skill
Webhooks are the preferred way to stay in sync with Shopify data. They allow your app to receive real-time notifications when events occur in a shop (e.g., orders/create , app/uninstalled ).
- Verification (CRITICAL)
ALL webhook requests must be verified to ensure they came from Shopify.
HMAC Verification
Shopify includes an X-Shopify-Hmac-Sha256 header in every webhook request. This is a base64-encoded HMAC-SHA256 digest of the request body, using your Client Secret (API Secret Key) as the signing key.
[!IMPORTANT] Always use the raw request body (Buffer) for verification. Parsed JSON bodies may have subtle differences that cause verification to fail.
Node.js Example (Generic)
const crypto = require('crypto');
function verifyWebhook(rawBody, hmacHeader, apiSecret) { const digest = crypto .createHmac('sha256', apiSecret) .update(rawBody, 'utf8') .digest('base64');
return crypto.timingSafeEqual( Buffer.from(digest), Buffer.from(hmacHeader) ); }
Remix / Shopify App Template (Recommended)
If using @shopify/shopify-app-remix , verification is handled automatically by the authenticate.webhook helper.
/* app/routes/webhooks.tsx */ import { authenticate } from "../shopify.server";
export const action = async ({ request }) => { const { topic, shop, session, admin, payload } = await authenticate.webhook(request);
if (!admin) { // The webhook request was not valid. return new Response(); }
switch (topic) {
case "APP_UNINSTALLED":
if (session) {
await db.session.deleteMany({ where: { shop } });
}
break;
case "ORDERS_CREATE":
console.log(Order created: ${payload.id});
break;
}
return new Response(); };
- Registration
App-specific Webhooks (Recommended)
These are configured in shopify.app.toml . They are automatically registered when the app is deployed and are easier to manage. Best for topics that apply to the app in general (e.g., app/uninstalled ).
[webhooks] api_version = "2025-10"
[[webhooks.subscriptions]] topics = [ "app/uninstalled", "orders/create" ] uri = "/webhooks"
Shop-specific Webhooks (Advanced)
Use webhookSubscriptionCreate via the Admin API. Best for:
-
Per-shop customization.
-
Topics not supported by config.
-
Dynamic runtime registration.
mutation webhookSubscriptionCreate($topic: WebhookSubscriptionTopic!, $webhookSubscription: WebhookSubscriptionInput!) { webhookSubscriptionCreate(topic: $topic, webhookSubscription: $webhookSubscription) { userErrors { field message } webhookSubscription { id } } }
- Mandatory Compliance (GDPR)
Public apps MUST implement specific webhooks to comply with privacy laws. These are configured in the Partner Dashboard (App > Configuration > Privacy compliance), NOT in shopify.app.toml or via API.
-
customers/data_request : Request to export customer data.
-
customers/redact : Request to delete customer data.
-
shop/redact : Request to delete shop data (48 hours after uninstall).
[!WARNING] Failure to handle these can result in app removal.
- Best Practices
-
Idempotency: Webhooks can be delivered multiple times. Ensure your processing logic is idempotent (e.g., check if an order has already been processed before taking action).
-
Response Time: Respond with a 200 OK immediately (within 5 seconds). Perform long-running tasks asynchronously (e.g., using a background queue).
-
App Uninstalled: ALWAYS handle app/uninstalled to clean up shop data and cancel subscriptions. This webhook acts as the "offboarding" signal.
-
App Uninstalled: ALWAYS handle app/uninstalled to clean up shop data and cancel subscriptions. This webhook acts as the "offboarding" signal.
Common Topics
-
APP_UNINSTALLED : App removed. Cleanup required.
-
ORDERS_CREATE : New order placed.
-
ORDERS_PAID : Order payment status changed to paid.
-
PRODUCTS_UPDATE : Product details changed.