Shopify Functions & APIs

Shopify Webhooks: Set Up, Verify, and Debug Them

By CartStack Team3 min read

Shopify webhooks push events like order creation or product updates to your app the moment they happen. They save you from polling the API on a schedule. This guide covers registering a webhook, verifying it came from Shopify, and debugging a delivery that never arrives.

Cover graphic for setting up and debugging Shopify webhooks
A Shopify webhook is only useful once you can trust and replay it.

Register a webhook with the Admin API

You can register webhooks in the Partner Dashboard, in your app’s TOML config, or directly through the Admin GraphQL API. The API approach works well when you need to subscribe a shop dynamically after install.

mutation {
  webhookSubscriptionCreate(
    topic: ORDERS_CREATE
    webhookSubscription: {
      callbackUrl: "https://app.example.com/webhooks/orders-create"
      format: JSON
    }
  ) {
    webhookSubscription { id }
    userErrors { field message }
  }
}

Check userErrors every time. A malformed callback URL or an unsupported topic fails silently otherwise, and the subscription is never created.

Verify the HMAC signature

Anyone can send a POST request to your webhook URL, so never trust the payload without checking it first. Shopify signs every webhook with an HMAC header. Recompute it on your server and compare.

const crypto = require('crypto');

function isValidWebhook(rawBody, hmacHeader, secret) {
  const digest = crypto
    .createHmac('sha256', secret)
    .update(rawBody, 'utf8')
    .digest('base64');
  return crypto.timingSafeEqual(
    Buffer.from(digest),
    Buffer.from(hmacHeader)
  );
}

Use the raw request body for this check, before any JSON parsing. Also, compare with a constant-time function, because a simple string comparison can leak timing information.

Handle retries and idempotency

Shopify retries a webhook when your endpoint does not respond quickly with a 200 status. As a result, your handler may receive the same event more than once. Each delivery includes an X-Shopify-Webhook-Id header, so store recent IDs and skip a duplicate.

if (await seenRecently(webhookId)) {
  return res.status(200).send('duplicate, skipped');
}

Respond fast, process later

Reliable Shopify webhooks depend on this step. Shopify expects a response within a few seconds. If your handler does heavy work, such as updating a database and calling other APIs, that delay can trigger a retry even though the first request succeeded. Instead, acknowledge the webhook immediately and push the real work to a queue.

app.post('/webhooks/orders-create', async (req, res) => {
  res.status(200).send('ok');
  await queue.add('process-order', req.body);
});

This queue pattern is what keeps Shopify webhooks reliable even under heavy order traffic.

Debug Shopify Webhooks That Never Arrive

Work through these checks in order when deliveries go missing.

  1. Confirm the subscription exists with a webhookSubscriptions query.
  2. Check your server logs for the request. A firewall or a redirect can block it before your code runs.
  3. Return a 200 status quickly. A timeout looks identical to a dropped connection from Shopify’s side.
  4. During development, use the Shopify CLI to trigger a sample event without waiting for a real one.
shopify app webhook trigger --topic=orders/create

For the current topic list and payload shapes, see the Shopify webhooks reference. If your app also reads large amounts of data on install, pair webhooks with Shopify GraphQL pagination for the initial sync.

Related reading: order webhooks also power agent flows, covered in Shopify UCP: agent profiles and trust tiers, and tracking pipelines, covered in Shopify server-side tracking.

FAQ about Shopify webhooks

What happens if my endpoint is down? Shopify retries with backoff for a limited time, then stops and marks the delivery failed.

Do webhooks replace polling entirely? Mostly. Still, run an occasional reconciliation job, because a rare delivery can still be missed.

Can I subscribe to the same topic twice? Yes, but each callback URL receives its own copy, so avoid duplicate subscriptions unless you need that.

Related: Shopify’s successor to classic webhooks is now generally available. See Shopify Events vs Webhooks.

Related: Planning to move handlers over? Read Migrate Shopify Webhooks to Events.