Shopify webhooks fire from their servers to your endpoint — which means your local development server needs a public URL. Here are three approaches, from simplest to most controlled.
Option 1: WebhookWhisper Forwarding (Fastest)
WebhookWhisper gives you a permanent public HTTPS URL that forwards traffic to your local server:
- Sign up at webhookwhisper.com and create a forwarding endpoint
- Point it to
http://localhost:3000/webhooks/shopify - Register the WebhookWhisper URL in Shopify Admin under Settings > Notifications > Webhooks
- Trigger events in your Shopify dev store and watch them arrive in the dashboard
Option 2: Shopify CLI
shopify app dev
# In a separate terminal
shopify webhook trigger --topic orders/create --api-version 2024-01The CLI sends a simulated webhook payload to your local server. Note: simulated webhooks use a test HMAC secret.
Option 3: Manual curl Testing
#!/bin/bash
SECRET="your_shopify_webhook_secret"
PAYLOAD='{"id":1234,"email":"[email protected]","total_price":"99.00"}'
# Shopify uses base64, NOT hex
SIG=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET" -binary | base64)
curl -X POST http://localhost:3000/webhooks/shopify \
-H "Content-Type: application/json" \
-H "X-Shopify-Hmac-Sha256: $SIG" \
-H "X-Shopify-Topic: orders/create" \
-d "$PAYLOAD"Shopify Signature Verification: Base64, Not Hex
import crypto from 'crypto'
function verifyShopifyWebhook(rawBody, hmacHeader) {
const computed = crypto
.createHmac('sha256', process.env.SHOPIFY_WEBHOOK_SECRET)
.update(rawBody)
.digest('base64') // base64, NOT 'hex'
return crypto.timingSafeEqual(Buffer.from(computed), Buffer.from(hmacHeader))
}Available Shopify Webhook Topics
| Topic | When it fires |
|---|---|
| orders/create | New order placed |
| orders/paid | Payment captured |
| orders/fulfilled | Order shipped |
| customers/create | New customer |
| products/update | Product modified |
| app/uninstalled | App removed from store |
Common Issues
- Signature mismatch: Are you using base64? Is your secret the webhook-specific secret?
- Timeout: Shopify requires a response within 5 seconds. Return 200 immediately, process async.
- Missing events: Shopify retries 19 times over 48 hours.