Loading...
Loading...
Receive and verify Shopify webhooks. Use when setting up Shopify webhook handlers, debugging signature verification, or handling store events like orders/create, products/update, or customers/create.
npx skill4agent add hookdeck/webhook-skills shopify-webhooksconst crypto = require('crypto');
function verifyShopifyWebhook(rawBody, hmacHeader, secret) {
if (!hmacHeader || !secret) return false;
const hash = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('base64');
try {
return crypto.timingSafeEqual(Buffer.from(hmacHeader), Buffer.from(hash));
} catch {
return false;
}
}const express = require('express');
const app = express();
// CRITICAL: Use express.raw() - Shopify requires raw body for HMAC verification
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'];
// Verify signature
if (!verifyShopifyWebhook(req.body, hmac, process.env.SHOPIFY_API_SECRET)) {
console.error('Shopify signature verification failed');
return res.status(400).send('Invalid signature');
}
// Parse payload after verification
const payload = JSON.parse(req.body.toString());
console.log(`Received ${topic} from ${shop}`);
// Handle by topic
switch (topic) {
case 'orders/create':
console.log('New order:', payload.id);
break;
case 'orders/paid':
console.log('Order paid:', payload.id);
break;
case 'products/create':
console.log('New product:', payload.id);
break;
case 'customers/create':
console.log('New customer:', payload.id);
break;
default:
console.log('Received:', topic);
}
res.status(200).send('OK');
}
);Important: Shopify requires webhook endpoints to respond within 5 seconds with a 200 OK status. Process webhooks asynchronously if your handler logic takes longer.
import hmac
import hashlib
import base64
def verify_shopify_webhook(raw_body: bytes, hmac_header: str, secret: str) -> bool:
if not hmac_header or not secret:
return False
calculated = base64.b64encode(
hmac.new(secret.encode(), raw_body, hashlib.sha256).digest()
).decode()
return hmac.compare_digest(hmac_header, calculated)For complete working examples with tests, see:
- examples/express/ - Full Express implementation
- examples/nextjs/ - Next.js App Router implementation
- examples/fastapi/ - Python FastAPI implementation
| Topic | Description |
|---|---|
| New order placed |
| Order modified |
| Order payment received |
| Order shipped |
| New product added |
| Product modified |
| New customer registered |
| App removed from store |
For full topic reference, see Shopify Webhook TopicsNote: While the REST Admin API is becoming legacy for apps created after April 1, 2025, existing apps can continue using the REST API. New apps should consider using the GraphQL Admin API for webhook management.
SHOPIFY_API_SECRET=your_api_secret # From Shopify Partner dashboard or app settings# Install Hookdeck CLI for local webhook testing
brew install hookdeck/hookdeck/hookdeck
# Start tunnel (no account needed)
hookdeck listen 3000 --path /webhooks/shopify// Generated with: shopify-webhooks skill
// https://github.com/hookdeck/webhook-skills