Whether you're building a Shopify app, a custom fulfilment integration, or an inventory sync — Shopify webhooks are how you get notified when things happen in the store. Testing them means needing a public HTTPS URL before your app is deployed. WebhookWhisper gives you that URL instantly, so you can test your Shopify webhook handler from day one.
How to Test Shopify Webhooks in Under 2 Minutes
- Get your free WebhookWhisper URL — click the button below, no account or credit card needed.
- Register the webhook in Shopify — go to Shopify Admin → Settings → Notifications → Webhooks and click "Create webhook". Select the event topic and paste your WebhookWhisper URL.
- Trigger an event — place a test order, update a product, or use Shopify's "Send test notification" button in the webhook settings.
- Inspect in real time — see the full Shopify payload with all headers including
X-Shopify-Hmac-Sha256for signature verification. - Forward to your local app — add a forwarding rule to
http://localhost:3000/webhooks/shopifyand every event reaches your local handler with automatic retry.
Shopify Webhook Topics You Can Test
Shopify has 40+ webhook topics across orders, products, customers, fulfilment, and more. Here are the most commonly tested:
| Topic | Fires when | Common use case |
|---|---|---|
orders/create | New order placed | Fulfilment, inventory deduction |
orders/paid | Order payment confirmed | Trigger warehouse pick, send receipt |
orders/cancelled | Order cancelled | Restock inventory, refund logic |
orders/fulfilled | Order marked fulfilled | Send tracking email, update CRM |
products/create | New product added | Sync to external catalogue |
products/update | Product price/stock changed | Update search index, feed |
customers/create | New customer registered | Add to email list, CRM sync |
refunds/create | Refund issued | Update accounting, notify warehouse |
app/uninstalled | App removed from store | Clean up merchant data (GDPR) |
Shopify Webhook Gotchas — What Trips Developers Up
HMAC signature verification is mandatory for Shopify app review
Shopify requires all public apps to verify webhook signatures before processing the payload. The signature is sent in the X-Shopify-Hmac-Sha256 header — it's a Base64-encoded HMAC-SHA256 of the raw request body using your app's shared secret. WebhookWhisper shows this header alongside the raw body, so you can test your verification code with real Shopify signatures before submitting for app review.
Shopify retries failed webhooks — your handler must be idempotent
If your endpoint returns a non-2xx status, Shopify retries the webhook up to 19 times over 48 hours. This means your handler might process the same orders/create event twice. Use the id field in the payload as an idempotency key and store processed IDs. WebhookWhisper's forwarding log shows the response status your server returned, so you can verify you're responding 200 consistently.
The 5-second response window
Shopify times out webhook delivery after 5 seconds. If your handler does heavy work synchronously (database writes, third-party API calls) it will time out and Shopify will retry. The correct pattern is to acknowledge the webhook immediately with a 200 response and process the job asynchronously in a queue. WebhookWhisper's delivery log shows response time so you can spot slow handlers during testing.
GDPR webhooks are required for app store listing
Shopify requires every listed app to handle three mandatory GDPR webhooks: customers/data_request, customers/redact, and shop/redact. You can test all three by registering them in your Partner Dashboard and firing test payloads. WebhookWhisper shows the exact payload shape so you can build the handlers correctly before submitting.
Guides & Related Resources
- How to Debug Webhooks: A Practical Guide
- Forward Webhooks to Localhost Without ngrok
- Shopify Webhook Events & Payload Reference
- Webhook Forwarding to Localhost — How It Works
- Stripe Webhook Testing — Free Tool
- GitHub Webhook Testing — Free Tool
Frequently Asked Questions
How do I test Shopify webhooks locally?
Create a free WebhookWhisper endpoint and register it as your Shopify webhook URL. Then add a forwarding rule pointing to http://localhost:3000/webhooks/shopify. Every Shopify event is relayed to your local server automatically — no ngrok, no Shopify CLI tunnel needed.
Can I test Shopify webhooks without a Shopify store?
You can use WebhookWhisper's Test Sender to fire authentic Shopify-shaped payloads at your endpoint without any Shopify account. This is useful for building and unit-testing your handler logic early. For signature verification testing you'll need a real Shopify store or Partner account.
How do I verify Shopify webhook signatures?
Compute Base64(HMAC-SHA256(rawBody, shopifySecret)) and compare it to the X-Shopify-Hmac-Sha256 header. In Node.js: crypto.createHmac('sha256', secret).update(rawBody, 'utf8').digest('base64'). WebhookWhisper shows you the raw header value and body together so you can debug verification failures easily.
Does WebhookWhisper work with Shopify CLI?
Yes — they're complementary tools. Shopify CLI (shopify app dev) handles app scaffolding and OAuth. WebhookWhisper handles webhook inspection and forwarding. You can use both together: point your Shopify webhook at WebhookWhisper and use the forwarding rule to send events to the localhost URL that Shopify CLI is running.
Shopify HMAC Webhook Verification — Code Examples
Shopify requires you to verify the X-Shopify-Hmac-Sha256 header on every incoming webhook. Here are copy-paste implementations for the most common stacks:
Node.js / Express
const crypto = require('crypto');
function verifyShopifyWebhook(req, secret) {
const hmac = req.get('X-Shopify-Hmac-Sha256');
const body = req.rawBody; // requires express.raw() middleware
const digest = crypto
.createHmac('sha256', secret)
.update(body, 'utf8')
.digest('base64');
return crypto.timingSafeEqual(
Buffer.from(digest),
Buffer.from(hmac)
);
}
app.post('/webhooks/shopify', express.raw({ type: 'application/json' }), (req, res) => {
if (!verifyShopifyWebhook(req, process.env.SHOPIFY_SECRET)) {
return res.status(401).send('Unauthorized');
}
const data = JSON.parse(req.body);
// process event...
res.sendStatus(200);
});
Python / Flask
import hmac, hashlib, base64, os
from flask import Flask, request, abort
app = Flask(__name__)
SHOPIFY_SECRET = os.environ['SHOPIFY_SECRET']
def verify_shopify_webhook(data, hmac_header):
digest = hmac.new(
SHOPIFY_SECRET.encode('utf-8'),
data,
digestmod=hashlib.sha256
).digest()
computed = base64.b64encode(digest).decode('utf-8')
return hmac.compare_digest(computed, hmac_header)
@app.route('/webhooks/shopify', methods=['POST'])
def shopify_webhook():
data = request.get_data()
hmac_header = request.headers.get('X-Shopify-Hmac-Sha256', '')
if not verify_shopify_webhook(data, hmac_header):
abort(401)
payload = request.json
# process event...
return '', 200
Ruby on Rails
class ShopifyWebhooksController < ApplicationController
skip_before_action :verify_authenticity_token
def create
data = request.raw_post
hmac = request.headers['X-Shopify-Hmac-Sha256']
digest = Base64.strict_encode64(
OpenSSL::HMAC.digest('SHA256', ENV['SHOPIFY_SECRET'], data)
)
unless ActiveSupport::SecurityUtils.secure_compare(digest, hmac)
head :unauthorized and return
end
payload = JSON.parse(data)
# process event...
head :ok
end
end
Important: Always use the raw request body for HMAC computation — not the parsed JSON. Any whitespace difference will cause verification to fail. Use express.raw() in Express, not express.json(), for the webhook route.