Shopify Webhook Integration
Shopify webhooks notify your application in real-time when events occur in your Shopify store, including order creation, inventory updates, customer actions, and more. Perfect for order fulfillment, inventory management, and customer engagement automation.
Quick Start
- Get your Unhook URL:
https://unhook.sh/wh_YOUR_ID
- Configure via Shopify Admin API or Partner Dashboard
- Start receiving events locally:
unhook listen
Configuration Methods
Method 1: Using Shopify Admin API
// Create webhook subscription
const webhook = {
webhook: {
topic: 'orders/create',
address: 'https://unhook.sh/wh_YOUR_ID',
format: 'json'
}
};
fetch(`https://${shop}.myshopify.com/admin/api/2024-01/webhooks.json`, {
method: 'POST',
headers: {
'X-Shopify-Access-Token': accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify(webhook)
});
Method 2: Using GraphQL Admin API
mutation webhookSubscriptionCreate($topic: WebhookSubscriptionTopic!, $webhookSubscription: WebhookSubscriptionInput!) {
webhookSubscriptionCreate(topic: $topic, webhookSubscription: $webhookSubscription) {
webhookSubscription {
id
}
userErrors {
field
message
}
}
}
Variables:
{
"topic": "ORDERS_CREATE",
"webhookSubscription": {
"callbackUrl": "https://unhook.sh/wh_YOUR_ID",
"format": "JSON"
}
}
Method 3: Using Shopify CLI (Development)
# Install Shopify CLI
npm install -g @shopify/cli
# Create webhook
shopify webhook create \
--topic=orders/create \
--address=https://unhook.sh/wh_YOUR_ID
Webhook Topics
Order Events
orders/create
- New order placed
orders/updated
- Order modified
orders/paid
- Order payment received
orders/cancelled
- Order cancelled
orders/fulfilled
- Order shipped
orders/partially_fulfilled
- Partial shipment
refunds/create
- Refund issued
transactions/create
- Payment transaction
Product Events
products/create
- Product added
products/update
- Product modified
products/delete
- Product removed
inventory_levels/update
- Stock level changed
inventory_items/create
- Inventory item added
inventory_items/update
- Inventory item modified
inventory_items/delete
- Inventory item removed
Customer Events
customers/create
- New customer registered
customers/update
- Customer profile updated
customers/delete
- Customer deleted
customers/enable
- Customer account activated
customers/disable
- Customer account deactivated
Collection Events
collections/create
- Collection created
collections/update
- Collection modified
collections/delete
- Collection removed
collection_listings/add
- Product added to collection
collection_listings/remove
- Product removed from collection
Cart & Checkout Events
carts/create
- Cart created
carts/update
- Cart modified
checkouts/create
- Checkout started
checkouts/update
- Checkout updated
checkouts/delete
- Checkout abandoned
App Events
app/uninstalled
- App uninstalled
shop/update
- Shop settings changed
themes/publish
- Theme published
themes/update
- Theme modified
Event Payload Examples
Order Created
{
"id": 820982911946154508,
"admin_graphql_api_id": "gid://shopify/Order/820982911946154508",
"app_id": null,
"browser_ip": "192.168.1.1",
"buyer_accepts_marketing": true,
"cancel_reason": null,
"cancelled_at": null,
"cart_token": "68778783ad298f1c80c3bafcddeea02f",
"checkout_id": 901414060,
"checkout_token": "bd5a8aa1ecd019dd3520ff791ee3a24c",
"confirmed": true,
"created_at": "2023-11-20T12:00:00-05:00",
"currency": "USD",
"current_subtotal_price": "100.00",
"current_total_price": "110.00",
"customer": {
"id": 115310627314723954,
"email": "customer@example.com",
"first_name": "John",
"last_name": "Doe"
},
"email": "customer@example.com",
"financial_status": "paid",
"fulfillment_status": null,
"line_items": [
{
"id": 866550311766439020,
"variant_id": 808950810,
"title": "Example Product",
"quantity": 1,
"sku": "EXAMPLE-001",
"variant_title": "Medium",
"vendor": "Example Vendor",
"fulfillment_service": "manual",
"product_id": 632910392,
"price": "100.00",
"price_set": {
"shop_money": {
"amount": "100.00",
"currency_code": "USD"
}
}
}
],
"name": "#1001",
"number": 1,
"order_number": 1001,
"payment_gateway_names": ["stripe"],
"processed_at": "2023-11-20T12:00:00-05:00",
"shipping_address": {
"first_name": "John",
"last_name": "Doe",
"address1": "123 Main St",
"city": "New York",
"province": "New York",
"country": "United States",
"zip": "10001"
},
"total_price": "110.00",
"total_tax": "10.00"
}
Product Updated
{
"id": 788032119674292922,
"title": "Example T-Shirt",
"body_html": "<p>Comfortable cotton t-shirt</p>",
"vendor": "Example Vendor",
"product_type": "Shirts",
"created_at": "2023-11-01T12:00:00-05:00",
"handle": "example-t-shirt",
"updated_at": "2023-11-20T14:30:00-05:00",
"published_at": "2023-11-01T12:00:00-05:00",
"status": "active",
"tags": "cotton, summer, casual",
"variants": [
{
"id": 808950810,
"product_id": 788032119674292922,
"title": "Small",
"price": "19.99",
"sku": "TSHIRT-S",
"inventory_quantity": 100,
"old_inventory_quantity": 95
}
],
"options": [
{
"id": 594680422,
"product_id": 788032119674292922,
"name": "Size",
"position": 1,
"values": ["Small", "Medium", "Large"]
}
],
"images": [
{
"id": 850703190,
"product_id": 788032119674292922,
"src": "https://cdn.shopify.com/s/files/1/0001/0001/products/example.jpg"
}
]
}
Customer Created
{
"id": 706405506930370084,
"email": "customer@example.com",
"accepts_marketing": true,
"created_at": "2023-11-20T15:00:00-05:00",
"updated_at": "2023-11-20T15:00:00-05:00",
"first_name": "Jane",
"last_name": "Smith",
"phone": "+1-555-123-4567",
"verified_email": true,
"addresses": [
{
"id": 1053317181,
"customer_id": 706405506930370084,
"first_name": "Jane",
"last_name": "Smith",
"address1": "456 Oak St",
"city": "Los Angeles",
"province": "California",
"country": "United States",
"zip": "90001",
"default": true
}
],
"tags": "VIP, newsletter",
"note": "Preferred customer",
"state": "enabled",
"total_spent": "0.00",
"orders_count": 0
}
Webhook Verification
Verify HMAC Signature
Shopify signs all webhooks. Always verify:
const crypto = require('crypto');
function verifyShopifyWebhook(data, hmacHeader) {
const hash = crypto
.createHmac('sha256', process.env.SHOPIFY_WEBHOOK_SECRET)
.update(data, 'utf8')
.digest('base64');
return hash === hmacHeader;
}
// Express middleware
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
const hmac = req.get('X-Shopify-Hmac-Sha256');
if (!verifyShopifyWebhook(req.body, hmac)) {
return res.status(401).send('Unauthorized');
}
const topic = req.get('X-Shopify-Topic');
const shop = req.get('X-Shopify-Shop-Domain');
const data = JSON.parse(req.body);
// Process webhook based on topic
console.log(`Webhook from ${shop}: ${topic}`);
res.status(200).send('OK');
});
Shopify includes useful headers:
X-Shopify-Topic
- Event type (e.g., ‘orders/create’)
X-Shopify-Shop-Domain
- Shop domain
X-Shopify-API-Version
- API version used
X-Shopify-Webhook-Id
- Unique webhook ID
Best Practices
1. Idempotent Processing
Handle duplicate webhooks:
const processedWebhooks = new Set();
async function processWebhook(webhookId, data) {
if (processedWebhooks.has(webhookId)) {
console.log(`Webhook ${webhookId} already processed`);
return;
}
processedWebhooks.add(webhookId);
// Process the webhook
await handleWebhookData(data);
}
2. Quick Response
Respond within 5 seconds:
app.post('/webhook', async (req, res) => {
// Verify webhook (as shown above)
// Respond immediately
res.status(200).send('OK');
// Process asynchronously
setImmediate(async () => {
try {
await processWebhookAsync(req.body, req.headers);
} catch (error) {
console.error('Webhook processing error:', error);
// Implement retry logic if needed
}
});
});
3. Mandatory Webhooks
For public apps, implement mandatory webhooks:
// GDPR mandatory webhooks
const mandatoryWebhooks = [
{ topic: 'customers/redact', path: '/webhooks/customers/redact' },
{ topic: 'shop/redact', path: '/webhooks/shop/redact' },
{ topic: 'customers/data_request', path: '/webhooks/customers/data_request' }
];
// Register during app installation
async function registerMandatoryWebhooks(shop, accessToken) {
for (const webhook of mandatoryWebhooks) {
await createWebhook(shop, accessToken, webhook);
}
}
4. Error Handling
Implement robust error handling:
async function handleWebhook(topic, data) {
try {
switch (topic) {
case 'orders/create':
await handleNewOrder(data);
break;
case 'products/update':
await handleProductUpdate(data);
break;
default:
console.log(`Unhandled webhook topic: ${topic}`);
}
} catch (error) {
console.error(`Error processing ${topic}:`, error);
// Log to monitoring service
await logError({
type: 'webhook_processing_error',
topic,
error: error.message,
data
});
// Implement retry if appropriate
if (shouldRetry(error)) {
await queueForRetry(topic, data);
}
}
}
Testing Webhooks
Create Test Notifications
# Using Shopify CLI
shopify webhook trigger --topic=orders/create
# Using API
curl -X POST https://{shop}.myshopify.com/admin/api/2024-01/webhooks/{webhook_id}/notifications.json \
-H "X-Shopify-Access-Token: {access_token}" \
-H "Content-Type: application/json"
Development Testing
Use ngrok or similar for local testing:
# Start local server
npm run dev
# In another terminal
ngrok http 3000
# Use ngrok URL for webhook address
Common Issues
Webhook Not Received
- Verify webhook is registered correctly
- Check webhook notification history in admin
- Ensure Unhook tunnel is active
- Verify HMAC secret is correct
Verification Failures
- Use raw request body for HMAC calculation
- Ensure webhook secret matches
- Check for middleware modifying body
Missing Topics
- Some topics require specific API access scopes
- Check app permissions
- Verify API version compatibility
Rate Limits
- Shopify limits webhook creation (4 per second)
- Maximum 1000 webhooks per app
- Consider batching operations
Environment Variables
# Shopify App Credentials
SHOPIFY_API_KEY=your_api_key
SHOPIFY_API_SECRET=your_api_secret
SHOPIFY_WEBHOOK_SECRET=your_webhook_secret
# Shop Details
SHOPIFY_SHOP_DOMAIN=yourstore.myshopify.com
SHOPIFY_ACCESS_TOKEN=shpat_xxxxxxxxxxxxx
# App URL (for OAuth)
SHOPIFY_APP_URL=https://yourapp.com
Useful Links
Support
Need help with Shopify webhooks?
Responses are generated using AI and may contain mistakes.