Back to Blog
guides6 min readApril 12, 2026

How to Test Shopify Webhooks Locally (Without Deploying)

Shopify webhooks power order fulfilment, inventory sync, and customer events — but testing them locally means you need a public HTTPS URL that Shopify can reach. This guide shows the fastest way to receive Shopify events locally, inspect payloads, verify HMAC signatures, and iterate without a single deploy.

A
Abinash B
April 12, 2026

Why Shopify Webhook Testing Is Harder Than It Looks

Shopify fires webhook events for nearly every store action — orders placed, products updated, customers created, fulfilments shipped. Your app needs to handle all of them correctly. But testing requires a publicly reachable HTTPS URL, and Shopify is strict: the endpoint must return a 200 within 5 seconds or Shopify marks the delivery failed and starts a retry window that can last days.

The traditional workarounds:

  • Deploy to staging every change — slow, expensive, error-prone
  • ngrok — rotating URLs on the free plan mean you have to re-register the webhook URL in Shopify after every restart
  • Shopify CLI — only works for apps built with the Shopify CLI scaffolding, not custom integrations

There's a cleaner path. This guide walks through testing any Shopify webhook locally — with full payload inspection, localhost forwarding, and HMAC verification — using a permanent public endpoint that never rotates.


Shopify Webhook Basics

Before jumping into the setup, a quick primer on how Shopify webhooks work. Every event has:

  • A topic — e.g. orders/create, products/update, customers/delete
  • A format — JSON or XML (always use JSON)
  • An HMAC signature — in the X-Shopify-Hmac-Sha256 header, base64-encoded HMAC-SHA256 of the raw body using your webhook secret
  • A shop domain — in the X-Shopify-Shop-Domain header

See the full list of Shopify webhook topics and sample payloads in our provider reference.


Step 1 — Get a Public Endpoint

Go to webhookwhisper.com and click Get my free webhook URL. You get a unique HTTPS endpoint like:

https://webhookwhisper.com/hook/abc123xyz

This URL is permanent — it doesn't change between restarts. Register it in Shopify once and never touch it again.

For guest mode (no account), the URL expires after 24 hours. For a permanent URL, create a free account and name the endpoint (e.g. "shopify-dev").


Step 2 — Register the Webhook in Shopify

There are two ways to register a webhook in Shopify:

Option A — Shopify Admin (for store-level webhooks)

  1. Go to your Shopify Admin → Settings → Notifications
  2. Scroll to Webhooks at the bottom
  3. Click Create webhook
  4. Choose the event (e.g. Order creation), set format to JSON, paste your WebhookWhisper URL
  5. Click Save — Shopify shows you the webhook secret at this point. Copy it.

Option B — Shopify Admin API (for app webhooks)

curl -X POST https://your-store.myshopify.com/admin/api/2024-01/webhooks.json \
  -H "X-Shopify-Access-Token: your-access-token" \
  -H "Content-Type: application/json" \
  -d '{
    "webhook": {
      "topic": "orders/create",
      "address": "https://webhookwhisper.com/hook/abc123xyz",
      "format": "json"
    }
  }'

The response includes a api_client_id — the secret for HMAC verification is your app's client secret, not a per-webhook secret.


Step 3 — Fire a Test Event

You don't need to place a real order to test your handler. Two options:

Option A — Shopify "Send test notification"

In the Shopify Admin webhook settings, click Send test notification next to your registered webhook. Shopify fires a sample payload at your endpoint immediately.

Option B — WebhookWhisper Test Sender

On the Shopify webhook testing page, select an event type and click Send. A realistic Shopify payload fires at your endpoint instantly — no Shopify store action needed. Great for iterating on handler logic before wiring up the real store.

Common Shopify events to test:

TopicUse case
orders/createTrigger fulfilment, send confirmation email
orders/cancelledRefund, restock inventory
orders/fulfilledSend shipping notification
products/updateSync product catalogue to external system
customers/createAdd to email list, CRM sync
checkouts/createAbandoned cart recovery trigger
refunds/createUpdate accounting, notify warehouse

Step 4 — Forward to Localhost

Once events are arriving at your WebhookWhisper endpoint, set up webhook forwarding to relay them to your local server:

  1. In WebhookWhisper, open your endpoint and go to the Forwarding tab
  2. Click Add rule
  3. Set target URL to http://localhost:3000/webhooks/shopify
  4. Click Save

Every Shopify event that arrives at your public URL is instantly relayed to your local handler — with all original headers intact, including X-Shopify-Hmac-Sha256, X-Shopify-Shop-Domain, and X-Shopify-Topic.

If your local server is down, WebhookWhisper retries automatically. The delivery log shows what HTTP status your handler returned for every attempt, so you can see exactly what failed.


Step 5 — Verify the Shopify HMAC Signature

Shopify signs every webhook with an HMAC-SHA256 digest of the raw request body, base64-encoded. You must verify this in production. Here's how to do it in Node.js:

import crypto from 'crypto'
import express from 'express'

const app = express()
const SHOPIFY_WEBHOOK_SECRET = process.env.SHOPIFY_WEBHOOK_SECRET

// Must use raw body — not JSON-parsed
app.post('/webhooks/shopify',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const hmac = req.headers['x-shopify-hmac-sha256']
    const topic = req.headers['x-shopify-topic']
    const shop = req.headers['x-shopify-shop-domain']

    // Compute expected HMAC
    const digest = crypto
      .createHmac('sha256', SHOPIFY_WEBHOOK_SECRET)
      .update(req.body)          // raw Buffer
      .digest('base64')

    if (digest !== hmac) {
      console.error('Shopify HMAC verification failed')
      return res.status(401).send('Unauthorized')
    }

    const payload = JSON.parse(req.body)

    switch (topic) {
      case 'orders/create':
        // handle new order...
        break
      case 'orders/cancelled':
        // handle cancellation...
        break
      default:
        console.log('Unhandled topic:', topic)
    }

    res.status(200).send('ok')
  }
)

The same rule as Stripe: use express.raw() on the webhook route, not express.json(). The HMAC is computed over the raw bytes — once Node parses it as JSON and re-serialises, the byte sequence changes and the check fails.


Step 6 — Inspect and Replay in the Dashboard

With events flowing, the WebhookWhisper inspector becomes your primary debug tool:

  • Full payload view — see the exact JSON Shopify sent, including nested objects like line items, shipping address, and metafields
  • Header inspection — verify X-Shopify-Hmac-Sha256, X-Shopify-Topic, and X-Shopify-Shop-Domain are all present
  • Delivery log — see what HTTP status your local handler returned
  • Event replay — hit Replay on any past event to re-fire it at your handler after fixing a bug

This replaces the painful workflow of triggering real store events (placing test orders, updating real products) every time you need to test a handler change. Read the full webhook debugging guide for a deeper look at inspecting failures.


Common Shopify Webhook Mistakes

1. Using JSON middleware before HMAC verification

Same as Stripe: if Express parses the body as JSON before you compute the HMAC, the signature check fails. Use express.raw() and parse manually after verification.

2. Not responding within 5 seconds

Shopify marks a delivery failed if your endpoint doesn't return a 2xx within 5 seconds. For heavy operations (sending emails, calling external APIs), respond 200 immediately and process asynchronously in a queue.

3. Missing idempotency handling

Shopify guarantees at-least-once delivery. If your endpoint returns a 5xx or times out, Shopify retries up to 19 times over 48 hours. Your handler can receive the same orders/create event multiple times. Use the order ID as an idempotency key before processing.

4. Wrong secret for app vs store webhooks

Store-level webhooks (registered in Admin) use the secret shown on the webhook settings page. App webhooks (registered via API) use your app's client secret. Using the wrong one causes every HMAC check to fail.

5. Not testing cancellations and refunds

Most developers test orders/create and ship. The hard bugs are in orders/cancelled, refunds/create, and orders/partially_fulfilled. Use WebhookWhisper's Test Sender to fire each event type before going live.


Summary

The fastest Shopify webhook test loop:

  1. Get a free WebhookWhisper endpoint (permanent URL, no account needed)
  2. Register it in Shopify Admin or via the API
  3. Add a forwarding rule to your localhost handler
  4. Fire test events from Shopify or WebhookWhisper's Test Sender
  5. Inspect payloads and replay failures without touching Shopify

No deploy. No rotating URLs. No CLI to keep running.

Related Guides

#shopify#webhooks#testing#localhost#hmac

Ready to test your webhooks?

Get a free HTTPS endpoint in under 5 seconds — no signup required.

Create Free Account
How to Test Shopify Webhooks Locally (2026 Guide) | WebhookWhisper