When you need your systems to react the moment something happens in a Shopify store — an order placed, a product updated, a customer registered — polling APIs every few seconds is not the answer. Shopify webhooks are. They push data to your endpoint the instant an event fires, making them the backbone of every serious real-time Shopify integration. Whether you are syncing orders to an ERP, triggering warehouse workflows, or feeding data into a CRM, understanding shopify webhooks end-to-end is non-negotiable for any developer working in the Shopify ecosystem.
This guide covers everything: what webhooks are, how to register them, how to verify their authenticity, and how to build reliable, production-grade consumers. Code examples are provided in Node.js and PHP throughout.
What Are Shopify Webhooks?
Per the official Shopify webhooks documentation, a Shopify webhook is an HTTP POST request that Shopify sends to a URL you define whenever a specific event occurs in a store. The request body contains a JSON payload describing the event — the order data, the product details, the customer record — depending on the topic subscribed to.
Webhooks are event-driven by design. Instead of your application asking Shopify "did anything change?" on a schedule, Shopify proactively notifies your endpoint. The flow looks like this:
- You register a webhook subscription for a specific topic (e.g.,
orders/create) and provide a destination URL. - When that event occurs in the store, Shopify sends an HTTP POST to your URL within seconds.
- Your endpoint processes the payload and responds with an HTTP 2xx status code.
- If Shopify does not receive a 2xx within 5 seconds, it marks the delivery as failed and will retry.
This push-based model makes Shopify webhooks dramatically more efficient than any polling strategy and is the foundation of modern Shopify app development.
Shopify Webhooks vs Polling: Why Real-Time Wins
To appreciate why Shopify webhooks matter, consider the alternative. A polling approach queries the Shopify REST or GraphQL API on a fixed interval — say, every 30 seconds — asking for new or updated orders. This introduces several problems:
- Latency: Events are only detected on the next poll cycle, introducing up to 30 seconds (or more) of delay.
- API rate limits: Every poll consumes a request against Shopify's rate limit bucket (2 requests/second on REST, leaky-bucket on GraphQL). High-frequency polling burns through your allowance quickly.
- Wasted compute: Most polls return nothing new, burning server resources and API quota for zero value.
- Complexity: You need to track state — last-checked timestamps, cursor pagination — to avoid processing the same records twice.
Webhooks eliminate all of this. One registration, zero polling overhead, sub-second event delivery, and a clean separation between the trigger and the handler. For any integration that needs to feel real-time to the end user, webhooks are the only sensible choice.
Most Important Shopify Webhook Events
Shopify exposes dozens of shopify webhook events across its resources. The following table covers the topics you will encounter most often in production integrations:
| Topic | Fires When | Typical Use Case |
|---|---|---|
orders/create | A new order is placed | ERP sync, fulfilment trigger, email notification |
orders/paid | Payment is captured for an order | Accounting, revenue reporting, digital delivery |
orders/updated | Any order field changes | Status sync, customer-facing order tracking |
orders/cancelled | An order is cancelled | Inventory restock, refund processing |
orders/fulfilled | All line items are fulfilled | Shipping confirmation emails, 3PL updates |
products/create | A new product is added | Sync to marketplace, PIM, or search index |
products/update | A product or its variants change | Price feed updates, catalog replication |
customers/create | A new customer account is created | CRM onboarding, welcome email sequence |
customers/update | Customer data changes | CRM sync, consent management |
inventory_levels/update | Stock level changes at a location | Low-stock alerts, multi-channel inventory sync |
checkouts/create | A checkout session is opened | Abandoned cart recovery workflows |
checkouts/update | A checkout is modified | Abandoned cart targeting, analytics |
app/uninstalled | Your app is uninstalled from a store | Clean up access tokens and data |
shop/update | Store settings change | Locale/currency sync |
How to Create Shopify Webhooks
There are three ways to register Shopify webhooks: through the Admin UI (suitable for one-off setups), via the REST Admin API, or via the GraphQL Admin API (the modern, recommended approach for apps).
Via Shopify Admin UI
For quick manual setups or testing, navigate to Settings → Notifications → Webhooks in the Shopify Admin. Click Create webhook, select the event topic, set the format to JSON, and enter your endpoint URL. This method is fine for one store but does not scale to multi-merchant app deployments.
Via REST Admin API
Send a POST request to /admin/api/2024-01/webhooks.json with the topic and address:
POST /admin/api/2024-01/webhooks.json
Content-Type: application/json
X-Shopify-Access-Token: {access_token}
{
"webhook": {
"topic": "orders/create",
"address": "https://yourapp.example.com/webhooks/orders-create",
"format": "json"
}
}Shopify responds with the created webhook object including its id, which you can store and use for later deletion or updates.
Via GraphQL Admin API (Modern Approach)
For apps targeting the latest API versions, the GraphQL webhookSubscriptionCreate mutation is preferred. It supports HTTP and PubSub/EventBridge delivery simultaneously and gives fine-grained control over included fields:
mutation {
webhookSubscriptionCreate(
topic: ORDERS_CREATE
webhookSubscription: {
format: JSON
callbackUrl: "https://yourapp.example.com/webhooks/orders-create"
}
) {
webhookSubscription {
id
topic
callbackUrl
createdAt
}
userErrors {
field
message
}
}
}When building a full Shopify integration, programmatic registration via API is essential so that webhooks are automatically created during the OAuth install flow and torn down on uninstall. For a deeper look at the overall architecture, see our guide on building custom Shopify apps.
Shopify Webhook Payload Structure
Every Shopify webhook delivers a JSON body. Below is a condensed example of an orders/create payload showing the fields you will interact with most frequently in a shopify webhook implementation:
{
"id": 5678901234567,
"email": "customer@example.com",
"created_at": "2024-03-15T14:22:01-05:00",
"updated_at": "2024-03-15T14:22:01-05:00",
"number": 1042,
"note": null,
"token": "b1946ac92492d2347c6235b4d2611184",
"gateway": "shopify_payments",
"total_price": "129.00",
"subtotal_price": "119.00",
"total_tax": "10.00",
"currency": "USD",
"financial_status": "paid",
"fulfillment_status": null,
"line_items": [
{
"id": 9876543210987,
"variant_id": 44556677889900,
"title": "Premium Widget",
"quantity": 2,
"sku": "PWD-001-BLK",
"price": "59.50",
"vendor": "Widget Co",
"product_id": 112233445566778
}
],
"shipping_address": {
"first_name": "Jane",
"last_name": "Doe",
"address1": "123 Main St",
"city": "New York",
"province": "New York",
"country": "United States",
"zip": "10001"
},
"customer": {
"id": 33445566778899,
"email": "customer@example.com",
"first_name": "Jane",
"last_name": "Doe",
"orders_count": 3
}
}Key fields to index on in your handler: id (use as idempotency key), financial_status, fulfillment_status, line_items[].sku for inventory matching, and customer.id for CRM lookup.
Verifying Shopify Webhook HMAC Signatures
Every Shopify webhook request includes an X-Shopify-Hmac-Sha256 header containing a Base64-encoded HMAC-SHA256 signature computed over the raw request body using your app's client secret. Shopify webhook verification is not optional — skipping it exposes your endpoint to spoofed payloads from any actor who discovers your URL.
The Shopify webhook HMAC verification process:
- Read the raw request body as bytes — do not parse JSON before this step.
- Compute HMAC-SHA256 of the raw body using your Shopify app client secret as the key.
- Base64-encode the result.
- Compare it to the value in the
X-Shopify-Hmac-Sha256header using a timing-safe comparison. - Reject requests that do not match with a 401 response.
Node.js Verification Example
const crypto = require('crypto');
function verifyShopifyWebhook(req, secret) {
const hmacHeader = req.headers['x-shopify-hmac-sha256'];
if (!hmacHeader) return false;
// req.rawBody must be the raw Buffer — set this in your body-parser config
const digest = crypto
.createHmac('sha256', secret)
.update(req.rawBody)
.digest('base64');
// Timing-safe comparison prevents timing attacks
const digestBuffer = Buffer.from(digest);
const hmacBuffer = Buffer.from(hmacHeader);
if (digestBuffer.length !== hmacBuffer.length) return false;
return crypto.timingSafeEqual(digestBuffer, hmacBuffer);
}
// Express middleware usage
app.post('/webhooks/orders-create', express.raw({ type: 'application/json' }), (req, res) => {
req.rawBody = req.body;
if (!verifyShopifyWebhook(req, process.env.SHOPIFY_CLIENT_SECRET)) {
return res.status(401).send('Unauthorized');
}
const order = JSON.parse(req.body);
// Queue async processing — respond immediately
orderQueue.add({ order });
res.status(200).send('OK');
});PHP Verification Example
Never use == or === for the comparison — always use a timing-safe function (crypto.timingSafeEqual in Node.js, hash_equals in PHP) to prevent timing-based attacks.
Shopify Webhook Best Practices
Respond with 2xx Within 5 Seconds
Shopify's delivery system expects your endpoint to return an HTTP 2xx status within 5 seconds. If it does not, the delivery is marked as failed. Do not do heavy lifting — database writes, external API calls, complex transformations — synchronously inside the handler. Acknowledge receipt immediately and hand off to a background worker.
Queue Async Processing
Use a message queue (Redis + BullMQ, RabbitMQ, SQS, or a simple database-backed job queue) to decouple reception from processing. Your webhook handler writes the raw payload to the queue and responds with 200. A separate worker picks it up and handles the business logic. This pattern also makes retries and dead-letter handling straightforward.
Handle Duplicates with Idempotency
Shopify may deliver the same webhook more than once — especially after retries. Use the payload's resource id field as an idempotency key. Before processing, check whether you have already handled that ID. If yes, return 200 immediately without reprocessing. A simple Redis SET with NX flag or a database unique constraint on the webhook ID works well.
Graceful Retry Handling
When Shopify cannot reach your endpoint, it retries delivery up to 19 times over 48 hours using an exponential backoff schedule. Your infrastructure must handle spikes — if your endpoint was down for an hour, you may receive a large burst of retried deliveries when it recovers. Design your queue and workers to handle burst load gracefully.
Log and Monitor
Log every incoming Shopify webhook — the topic, the resource ID, the timestamp, the processing outcome, and any errors. Set up alerting on error rates and processing lag. Shopify's Partner Dashboard (for public apps) and the Admin webhook settings page show recent delivery attempts and their HTTP response codes, which is invaluable for debugging production issues.
Common Shopify Webhook Events Use Cases
Order Sync to ERP
The orders/create and orders/paid shopify webhook events are the primary triggers for pushing order data into ERP systems such as SAP, NetSuite, or Dynamics 365. On receipt, your handler maps Shopify's order structure to the ERP's order schema, creates the sales order via the ERP's API, and stores the ERP reference back on the Shopify order using a metafield. For common pitfalls in this integration pattern, see our deep dive on Shopify ERP sync issues.
Inventory Sync
The inventory_levels/update topic fires whenever stock changes at any location. Subscribe to this topic to propagate inventory counts to other sales channels, marketplace listings, or a centralised inventory management system. Pay attention to the location_id field in the payload if the store operates multiple fulfilment locations.
Customer CRM Integration
Use customers/create and customers/update webhooks to keep your CRM — HubSpot, Salesforce, Klaviyo — in sync with Shopify customer data. Map the Shopify customer ID to the CRM contact ID and store both in each system so future events can be correlated without additional API lookups.
Abandoned Checkout Notifications
The checkouts/create and checkouts/update topics let you build abandoned cart recovery flows outside of Shopify's native tooling. When a checkout is created, start a timer. If no orders/create event arrives within your threshold window (typically 1–3 hours), send a recovery email or SMS via your preferred messaging platform. Store the token field from the checkout payload to reconstruct the recovery URL.
Debugging Shopify Webhooks
Testing Shopify webhooks during local development requires exposing your local server to the internet. Several tools make this straightforward:
- ngrok: Run
ngrok http 3000to get a public HTTPS URL tunnelled to your local port. Register this URL as your webhook endpoint in Shopify, then inspect every request and response in ngrok's local dashboard athttp://localhost:4040. The replay feature lets you re-send captured payloads without triggering a real store event. - Shopify CLI: The Shopify CLI (
shopify app dev) automatically handles tunnelling and webhook registration for app development, forwarding delivery attempts directly to your local server. - webhook.site: Generate a free public URL at webhook.site to inspect raw payloads without writing any code. Useful for quickly examining the exact JSON structure Shopify sends for a given topic before you write your handler.
- Shopify Admin delivery logs: Under Settings → Notifications → Webhooks, each registered webhook shows its most recent delivery attempts with the HTTP status returned. This is your first debugging stop in production.
When verifying signatures locally, make sure your client secret matches the app registered in the Partner Dashboard — a common source of 401 errors during development is using a test secret that does not match the store's installed app.
Webhook Limits and Scaling Considerations
Before going to production with a Shopify webhook implementation, understand the platform's limits and failure behaviours:
- 5-second response window: Hard limit. Any response taking longer is treated as a failure regardless of the eventual outcome. Asynchronous queue offloading is mandatory for any non-trivial processing.
- Retry schedule: Shopify retries failed deliveries up to 19 times. Retries follow an exponential backoff over approximately 48 hours. After 19 consecutive failures, the webhook subscription is automatically deleted by Shopify — you will lose the subscription silently. Monitor for this and implement automatic re-registration logic in your app.
- Delivery ordering: Shopify does not guarantee in-order delivery. A burst of rapid updates to the same resource may arrive out of sequence. Use the
updated_attimestamp in the payload to detect stale events and discard them if a newer event has already been processed. - Webhook volume at scale: High-volume merchants can generate thousands of webhook events per minute during flash sales. Size your ingestion layer (reverse proxy, queue) to handle peak burst, not average throughput. Consider a dedicated webhook ingestion service separate from your main application.
- Subscription limits: Shopify allows up to 1,000 webhook subscriptions per app per store. In practice you will rarely approach this, but be aware of the limit in multi-topic integrations.
- HTTPS required: All webhook endpoints must use HTTPS with a valid TLS certificate. Self-signed certificates are not accepted in production (only in development via tools like ngrok).
Conclusion
Mastering Shopify webhooks is a fundamental skill for any developer building production-grade integrations with the Shopify platform. From registering subscriptions via the GraphQL API to verifying Shopify webhook HMAC signatures in constant time, every step in the pipeline has sharp edges that separate a fragile prototype from a robust, scalable system. The patterns covered here — async queue offloading, idempotency keys, graceful retry handling, comprehensive logging — apply regardless of whether you are processing ten orders a day or ten thousand.
If you need an expert team to design and build your Shopify integration from the ground up, the specialists at MGroup have deep experience in Shopify app development across ERP connectors, marketplace integrations, and custom automation workflows. Reach out to discuss your project requirements.
FAQ
What are Shopify webhooks used for?
Shopify webhooks send an HTTP POST to your endpoint when a store event happens, such as an order or product update. They let systems react in real time instead of polling the API.
Which shopify webhook events are most common in integrations?
Common shopify webhook events include orders/create, orders/paid, products/update, customers/create, inventory_levels/update, and app/uninstalled. Each supports a different sync or workflow.
How do you create Shopify webhooks for an app?
You can create Shopify webhooks in Admin, via the REST Admin API, or with the GraphQL Admin API. For apps, the article recommends API-based shopify webhook implementation during install.
How does Shopify webhook verification work?
Shopify webhook verification uses the X-Shopify-Hmac-Sha256 header. You compute an HMAC-SHA256 on the raw body with your client secret, Base64-encode it, and compare it safely before processing.
Why should webhook handlers respond quickly?
Shopify expects a 2xx response within 5 seconds. If the endpoint is slower, the delivery is marked failed and retried, so handlers should queue work and return fast.


