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-Sha256header, base64-encoded HMAC-SHA256 of the raw body using your webhook secret - A shop domain — in the
X-Shopify-Shop-Domainheader
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)
- Go to your Shopify Admin → Settings → Notifications
- Scroll to Webhooks at the bottom
- Click Create webhook
- Choose the event (e.g.
Order creation), set format to JSON, paste your WebhookWhisper URL - 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:
| Topic | Use case |
|---|---|
orders/create | Trigger fulfilment, send confirmation email |
orders/cancelled | Refund, restock inventory |
orders/fulfilled | Send shipping notification |
products/update | Sync product catalogue to external system |
customers/create | Add to email list, CRM sync |
checkouts/create | Abandoned cart recovery trigger |
refunds/create | Update 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:
- In WebhookWhisper, open your endpoint and go to the Forwarding tab
- Click Add rule
- Set target URL to
http://localhost:3000/webhooks/shopify - 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, andX-Shopify-Shop-Domainare 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:
- Get a free WebhookWhisper endpoint (permanent URL, no account needed)
- Register it in Shopify Admin or via the API
- Add a forwarding rule to your localhost handler
- Fire test events from Shopify or WebhookWhisper's Test Sender
- Inspect payloads and replay failures without touching Shopify
No deploy. No rotating URLs. No CLI to keep running.