Gareth Wilson Gareth Wilson

Guide to Shopify Webhooks Features and Best Practices

Published · Updated


Shopify powers over 4 million online stores worldwide, making it the dominant force in e-commerce platforms. For developers building integrations, apps, and custom solutions, webhooks are the primary mechanism for receiving real-time notifications when events occur in a Shopify store, from new orders and inventory changes to customer updates and fulfilment status changes.

This guide covers everything you need to know about Shopify webhooks: their features, how to configure them, best practices for production deployments, and the common pain points developers face along with solutions to address them.

What are Shopify webhooks?

Shopify webhooks are HTTP callbacks that deliver event notifications to your endpoints whenever specific actions occur in a Shopify store. When a customer places an order, a product is updated, or inventory levels change, Shopify sends a JSON payload containing the relevant data to URLs you configure. This enables real-time integration with inventory systems, fulfilment services, accounting software, CRMs, and any service that can receive HTTP requests.

Webhooks in Shopify can be created through multiple channels:

  • Shopify Admin Dashboard - Manual configuration through the store's settings
  • GraphQL Admin API - Programmatic subscription management (recommended)
  • REST Admin API - Legacy programmatic approach (deprecated as of October 2024)
  • App configuration (TOML) - Declarative configuration for Shopify CLI apps

This guide focuses on building reliable webhook integrations regardless of your subscription method.

Shopify webhook features

FeatureDetails
Webhook configurationShopify Admin, GraphQL Admin API, REST Admin API (legacy), or app TOML
Endpoint typesHTTPS URLs, Google Pub/Sub, Amazon EventBridge
Hashing algorithmHMAC-SHA256
Timeout5-second response timeout
Retry logic8 retries over 4 hours with exponential backoff
Alert logicWarning email on repeated failures, deletion notification when removed
Manual retryNot available
Browsable log7-day delivery metrics in Partner Dashboard
Payload formatJSON (default) or XML
API versioningWebhook payloads follow your app's configured API version

Supported webhook topics

Shopify provides webhook topics covering nearly every event in a store's lifecycle. Here are the main categories:

CategoryExample Topics
Ordersorders/create, orders/updated, orders/paid, orders/fulfilled, orders/cancelled
Productsproducts/create, products/update, products/delete
Customerscustomers/create, customers/update, customers/delete, customers/enable, customers/disable
Inventoryinventory_levels/connect, inventory_levels/update, inventory_levels/disconnect
Fulfillmentfulfillments/create, fulfillments/update
Cartscarts/create, carts/update
Checkoutscheckouts/create, checkouts/update, checkouts/delete
Refundsrefunds/create
Shopshop/update, app/uninstalled
Compliancecustomers/data_request, customers/redact, shop/redact

For apps distributed through the Shopify App Store, subscribing to the three mandatory compliance webhooks (customers/data_request, customers/redact, shop/redact) is required for GDPR compliance.

Webhook payload structure

When Shopify sends a webhook, it delivers a JSON payload containing the relevant resource data. Here's an example orders/create payload structure:

{
  "id": 5678901234567,
  "admin_graphql_api_id": "gid://shopify/Order/5678901234567",
  "email": "customer@example.com",
  "created_at": "2025-01-15T10:30:00-05:00",
  "updated_at": "2025-01-15T10:30:00-05:00",
  "number": 1001,
  "order_number": 1001,
  "total_price": "125.00",
  "subtotal_price": "100.00",
  "total_tax": "10.00",
  "currency": "USD",
  "financial_status": "paid",
  "fulfillment_status": null,
  "customer": {
    "id": 1234567890,
    "email": "customer@example.com",
    "first_name": "Jane",
    "last_name": "Doe"
  },
  "line_items": [
    {
      "id": 9876543210,
      "product_id": 1122334455,
      "variant_id": 5566778899,
      "title": "Example Product",
      "quantity": 2,
      "price": "50.00",
      "sku": "EXAMPLE-001"
    }
  ],
  "shipping_address": {
    "address1": "123 Main Street",
    "city": "New York",
    "province": "New York",
    "country": "United States",
    "zip": "10001"
  }
}

Important payload considerations

  • Product webhooks truncate variants: Shopify supports up to 2,000 variants per product, but webhooks only include full details for the first 100 variants. For variants 101+, only variant_ids with updated_at timestamps are included. You'll need additional API calls for complete data.
  • Payload size limits: Large payloads may be truncated. The truncated_fields array indicates which fields were omitted.
  • API version affects payload: The webhook payload structure follows your app's configured API version. When your version becomes unsupported, Shopify automatically "falls forward" to the next stable version.

Webhook headers

Every Shopify webhook includes standard headers for verification and context:

HeaderDescription
X-Shopify-TopicThe webhook topic (e.g., orders/create, products/update)
X-Shopify-Shop-DomainThe myshopify.com domain of the store
X-Shopify-API-VersionThe API version used to serialize the payload
X-Shopify-Hmac-Sha256Base64-encoded HMAC signature for verification
X-Shopify-Webhook-IdUnique identifier for this webhook delivery
X-Shopify-Event-IdUnique identifier for the event (use for deduplication)
X-Shopify-Triggered-AtISO 8601 timestamp of when the event occurred

HTTP headers are case-insensitive. Your app may receive X-Shopify-Topic, x-shopify-topic, or any other casing variation.

Security with HMAC signatures

Shopify signs every webhook payload using HMAC-SHA256. The signature is sent in the X-Shopify-Hmac-Sha256 header as a base64-encoded string. To verify authenticity, you must recompute the signature using your app's client secret and compare it to the header value.

Always verify webhooks before processing them. Your webhook endpoint is publicly accessible, and without verification, attackers could spoof requests to trigger unintended actions.

Setting up Shopify webhooks

Via the Shopify Admin (for store owners)

  1. Navigate to Settings > Notifications
  2. Scroll to the Webhooks section
  3. Click Create webhook
  4. Select the event topic
  5. Enter your HTTPS endpoint URL
  6. Choose the format (JSON recommended)
  7. Select the API version
  8. Click Save
mutation webhookSubscriptionCreate($topic: WebhookSubscriptionTopic!, $webhookSubscription: WebhookSubscriptionInput!) {
  webhookSubscriptionCreate(topic: $topic, webhookSubscription: $webhookSubscription) {
    webhookSubscription {
      id
      topic
      format
      endpoint {
        __typename
        ... on WebhookHttpEndpoint {
          callbackUrl
        }
      }
    }
    userErrors {
      field
      message
    }
  }
}

Variables:

{
  "topic": "ORDERS_CREATE",
  "webhookSubscription": {
    "callbackUrl": "https://your-app.com/webhooks/orders",
    "format": "JSON"
  }
}

Use screaming case for topics in GraphQL (e.g., ORDERS_CREATE instead of orders/create).

Via app configuration (TOML)

For apps built with Shopify CLI, declare webhooks in your shopify.app.toml:

[webhooks]
api_version = "2025-01"

[[webhooks.subscriptions]]
topics = ["orders/create", "orders/updated"]
uri = "/webhooks/orders"

[[webhooks.subscriptions]]
topics = ["products/update"]
uri = "/webhooks/products"

[webhooks.subscriptions.compliance_topics]
customer_deletion = "/webhooks/compliance/customer-deletion"
customer_data_request = "/webhooks/compliance/customer-data-request"
shop_deletion = "/webhooks/compliance/shop-deletion"

Best practices when working with Shopify webhooks

Verifying HMAC signatures

Always verify that webhooks genuinely originated from Shopify before processing them.

Node.js

const crypto = require('crypto');
const express = require('express');
const app = express();

// Capture raw body for signature verification
app.use('/webhooks', express.raw({ type: 'application/json' }));

function verifyShopifyWebhook(rawBody, hmacHeader, secret) {
  const calculatedHmac = crypto
    .createHmac('sha256', secret)
    .update(rawBody, 'utf8')
    .digest('base64');

  return crypto.timingSafeEqual(
    Buffer.from(calculatedHmac),
    Buffer.from(hmacHeader)
  );
}

app.post('/webhooks/orders', (req, res) => {
  const hmacHeader = req.get('X-Shopify-Hmac-Sha256');

  if (!verifyShopifyWebhook(req.body, hmacHeader, process.env.SHOPIFY_CLIENT_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

  // Acknowledge immediately
  res.status(200).send('OK');

  // Process asynchronously
  const payload = JSON.parse(req.body.toString());
  processOrderWebhook(payload);
});

Python

import hmac
import hashlib
import base64
import os
from flask import Flask, request, abort

app = Flask(__name__)

def verify_shopify_webhook(data, hmac_header, secret):
    calculated_hmac = base64.b64encode(
        hmac.new(
            secret.encode('utf-8'),
            data,
            hashlib.sha256
        ).digest()
    ).decode('utf-8')

    return hmac.compare_digest(calculated_hmac, hmac_header)

@app.route('/webhooks/orders', methods=['POST'])
def handle_order_webhook():
    hmac_header = request.headers.get('X-Shopify-Hmac-Sha256')

    if not verify_shopify_webhook(
        request.get_data(),
        hmac_header,
        os.environ['SHOPIFY_CLIENT_SECRET']
    ):
        abort(401)

    # Acknowledge immediately, process async
    return 'OK', 200

Verify the HMAC against the raw request body before any parsing. Body parsers can modify the payload and break signature verification.

Respond within 5 seconds

Shopify waits only 5 seconds for a response. If your endpoint doesn't respond in time, Shopify considers the delivery failed and initiates retries.

Implement idempotency

Shopify may send the same webhook multiple times. Use the X-Shopify-Event-Id header to track processed events:

async function handleWebhook(req, payload) {
  const eventId = req.get('X-Shopify-Event-Id');

  // Check if already processed
  const exists = await redis.get(`processed:${eventId}`);
  if (exists) {
    console.log(`Event ${eventId} already processed, skipping`);
    return;
  }

  // Process the webhook
  await processPayload(payload);

  // Mark as processed with 48-hour TTL
  await redis.setex(`processed:${eventId}`, 172800, '1');
}

Use upserts to handle out-of-order delivery

Shopify doesn't guarantee webhook ordering. A products/update webhook might arrive before products/create. Design your database operations to handle this.

Implement reconciliation jobs

Webhooks are not 100% reliable. Implement periodic reconciliation to catch any missed events.

Shopify webhook limitations and pain points

Aggressive 5-second timeout

The Problem: Shopify enforces a strict 5-second timeout for webhook responses. Any processing that takes longer, such as database operations, external API calls, or complex business logic, results in a failed delivery.

Why It Happens: This timeout is a platform-level constraint that cannot be configured per webhook or app. Shopify prioritises consistent, predictable delivery performance across millions of stores.

Workarounds:

  • Acknowledge webhooks immediately with a 200 response before any processing
  • Use a message queue (Redis, RabbitMQ, SQS) to decouple receipt from processing
  • Implement background workers to handle the actual business logic

How Hookdeck Can Help: Hookdeck accepts webhooks on your behalf and provides configurable delivery timeouts when forwarding to your endpoint. Your application can take longer to process without risking failed deliveries or subscription removal.

Automatic subscription removal after failures

The Problem: After 8 consecutive failed deliveries over a 4-hour window, Shopify automatically deletes your webhook subscription without requiring confirmation. You must manually re-register the webhook and reconcile any missed data.

Why It Happens: Shopify removes failing subscriptions to prevent resource waste on both their infrastructure and endpoints that appear permanently unavailable.

Workarounds:

  • Monitor the emergency developer email for failure warnings (sent before deletion)
  • Use the Partner Dashboard webhook metrics to track delivery health
  • Implement automatic re-subscription logic that periodically verifies active subscriptions
  • Build alerts around your endpoint's health and response times

How Hookdeck Can Help: Hookdeck provides a stable, highly-available ingestion layer that maintains a 200 OK response to Shopify even when your downstream endpoint is experiencing issues. Failed deliveries to your endpoint are preserved and can be replayed once your systems recover.

No guaranteed delivery ordering

The Problem: Webhooks for the same resource can arrive out of order. A products/update event might be delivered before the corresponding products/create, causing database errors or inconsistent state.

Why It Happens: Shopify's distributed architecture processes and delivers webhooks through multiple queues and workers. Network conditions, retries, and internal routing can all affect delivery order.

Workarounds:

  • Use database upserts instead of separate INSERT/UPDATE operations
  • Leverage the updated_at timestamp in payloads to determine data freshness
  • Compare X-Shopify-Triggered-At headers to order related events
  • Design state machines that handle events arriving in any sequence

How Hookdeck Can Help: Hookdeck's ordering feature can buffer and deliver webhooks in sequence based on configurable keys, ensuring your endpoint receives events in the order they occurred.

Duplicate webhook deliveries

The Problem: The same webhook event may be delivered multiple times due to retries, network issues, or platform behaviour. Processing duplicates can cause inventory discrepancies, double charges, or corrupted data.

Why It Happens: Retries after timeouts, network failures that occur after Shopify sends but before receiving acknowledgment, and occasional platform issues can all result in duplicate deliveries.

Workarounds:

  • Track processed events using the X-Shopify-Event-Id header
  • Implement idempotent processing logic for all webhook handlers
  • Store event IDs in Redis or your database with appropriate TTLs
  • Design operations that produce the same result regardless of repetition

How Hookdeck Can Help: Hookdeck's automatic deduplication filters duplicate webhooks based on content hashing or custom identifiers, ensuring your endpoint only receives unique events.

Limited visibility into delivery status

The Problem: The Partner Dashboard provides only 7 days of webhook delivery logs with limited filtering and no real-time visibility. Debugging delivery issues often requires correlating multiple data sources.

Why It Happens: The webhook metrics feature is designed for troubleshooting rather than comprehensive monitoring. Detailed logging at Shopify's scale would require significant additional infrastructure.

Workarounds:

  • Implement comprehensive logging in your webhook handlers
  • Build internal dashboards tracking received webhooks by topic and shop
  • Set up alerts for anomalies in webhook volume or error rates
  • Use the X-Shopify-Webhook-Id to correlate with Shopify's logs when available

How Hookdeck Can Help: Hookdeck provides real-time visibility into all webhook traffic, including full request/response details, latency metrics, and error tracking. Configure alerts to notify you immediately when delivery issues occur.

No dead letter queue or manual replay

The Problem: When webhooks fail after all retry attempts, they're lost. Shopify doesn't maintain a record of permanently failed deliveries, and there's no way to manually trigger a retry from their side.

Why It Happens: Shopify's webhook system is designed for forward-only delivery. Once retries are exhausted, the platform moves on without maintaining failed delivery history.

Workarounds:

  • Ensure high availability for your webhook endpoints
  • Implement comprehensive logging to identify gaps
  • Use reconciliation jobs to detect and backfill missed events
  • Consider running redundant webhook receivers for critical topics

How Hookdeck Can Help: Hookdeck automatically preserves all webhook deliveries, including those that fail. Browse, inspect, and replay failed webhooks whenever you're ready, with no time limit on retention.

Truncated payloads for large resources

The Problem: Shopify now supports up to 2,000 variants per product, but product webhooks only include full variant details for the first 100 variants. For variants 101+, the payload includes only variant_ids with updated_at timestamps. You must make additional API calls for complete data. High-cardinality resources may also have other fields omitted, indicated by a truncated_fields array.

Why It Happens: Shopify limits payload sizes to maintain consistent delivery performance within the 5-second timeout window. Very large payloads could cause timeout issues and increased infrastructure costs.

Workarounds:

  • Check for truncated_fields in every payload
  • Make follow-up API calls to fetch complete data when truncation is detected
  • For products with many variants, use the variant_ids field to identify and fetch missing variant details
  • Consider subscribing to more granular topics (e.g., inventory_levels/update instead of relying on product webhooks for inventory)

How Hookdeck Can Help: Hookdeck's transformation feature can enrich truncated payloads by making API calls to fetch complete data before forwarding to your endpoint, though this requires careful implementation to avoid rate limits.

REST API deprecation affecting webhook management

The Problem: The REST Admin API was designated as a legacy API on October 1, 2024. Starting April 1, 2025, all new public apps submitted to the Shopify App Store must use GraphQL exclusively. Existing public apps using deprecated product APIs needed to migrate by February 1, 2025. Custom apps can continue using REST but won't receive new features.

Why It Happens: Shopify is consolidating on GraphQL as their standard API approach, offering benefits like precise field selection and reduced over-fetching.

Workarounds:

  • Migrate webhook subscription code to use webhookSubscriptionCreate mutation
  • Update existing apps before REST endpoints lose support
  • Use Shopify CLI and TOML configuration for declarative webhook management
  • Review the GraphQL Admin API documentation for current best practices

How Hookdeck Can Help: Hookdeck's Shopify source handles webhook reception regardless of how subscriptions were created, providing a stable integration point that doesn't require changes when Shopify's APIs evolve.

Testing Shopify webhooks

Use the Shopify Admin test feature

For webhooks created through the Admin dashboard, click Send test notification to trigger a sample payload. Note that test payloads may differ slightly from real events.

Trigger real events in a development store

Create a development store through your Partner Dashboard and trigger actual events:

  • Create/update products to test products/create and products/update
  • Place test orders using Shopify's Bogus Gateway
  • Update customer information for customers/update

Use Hookdeck Console for inspection

Before building your handler, inspect real payloads:

  1. Set up a temporary endpoint using Hookdeck Console
  2. Configure it as your webhook URL in a development store
  3. Trigger real events
  4. Inspect headers, payload structure, and timing

Validate in staging with realistic scenarios

Test your integration with:

  • Single events (order creation, product update)
  • Rapid successive events (bulk product updates)
  • High-volume scenarios (flash sale simulation)
  • Edge cases (orders with 100+ line items, products with 100+ variants)
  • Failure scenarios (what happens when your endpoint is down?)

Conclusion

Shopify webhooks provide the foundation for real-time integrations with the world's most popular e-commerce platform. The combination of comprehensive event topics, HMAC security, and multiple configuration options makes it possible to build sophisticated integrations for inventory management, order fulfilment, analytics, and automation.

However, the strict 5-second timeout, automatic subscription removal, lack of delivery ordering guarantees, and limited replay capabilities mean production deployments require careful architecture. Implementing proper signature verification, idempotent processing, asynchronous handling, and reconciliation jobs will address most common issues.

For apps with moderate webhook volumes and reliable endpoint infrastructure, Shopify's built-in webhook system combined with proper error handling works well. For high-volume integrations, multi-store apps, or scenarios where delivery guarantees are critical, webhook infrastructure like Hookdeck can address Shopify's limitations, providing extended timeouts, automatic deduplication, delivery ordering, dead letter queues, and comprehensive monitoring without modifying your Shopify configuration.

Additional resources


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.