Receiving Shopify Webhooks: Configuration & Security Guide
Shopify webhooks let your app react instantly when merchants create orders, update products, or change store settings. This guide blends official documentation with real-world practices so you can choose the right delivery method, automate subscription management, harden security, and operate webhook workloads at scale.
How Shopify Webhooks Work
Shopify raises a webhook whenever a subscribed event fires, signs the JSON payload using your app secret, and delivers it to the endpoint (or event bus) you configured. Return 200 OK within five seconds—Shopify retries failed deliveries up to eight times across several hours and may delete the subscription after repeated failures.
- Topics define which events trigger delivery, such as
orders/createorproducts/update. - Delivery channels include HTTPS endpoints, Google Pub/Sub topics, and Amazon EventBridge.
- Idempotency is required—Shopify can deliver duplicates and events are not strictly ordered.
Key Shopify Webhook Headers
Inspect headers to authenticate requests, deduplicate events, and instrument observability.
| Header | Purpose |
|---|---|
| X-Shopify-Topic | Event topic (for routing, e.g., orders/create) |
| X-Shopify-Hmac-Sha256 | HMAC signature generated with your app secret |
| X-Shopify-Shop-Domain | Shop domain that triggered the webhook |
| X-Shopify-Webhook-Id | Unique delivery identifier for auditing and retries |
| X-Shopify-Event-Id | Unique event identifier—store it to prevent duplicate processing |
| X-Shopify-Triggered-At | Timestamp for ordering out-of-sequence deliveries |
Choose a Delivery Method
Start by selecting the channel that matches your infrastructure. Most apps use an HTTPS endpoint because it works everywhere, while Pub/Sub and EventBridge integrate natively with Google Cloud and AWS event pipelines.
| Method | Best For | Highlights |
|---|---|---|
| HTTPS endpoint | Backend services or serverless functions | Simple setup, signature verification, automatic retries |
| Google Pub/Sub | Event-driven Google Cloud workloads | Fan-out processing to Cloud Functions, Dataflow, BigQuery |
| Amazon EventBridge | AWS-native integrations and Step Functions | Route with EventBridge rules—no web server to manage |
Register Webhook Subscriptions
Shopify offers two primary approaches. Use the app configuration file when every install needs the same set of topics. Reach for the GraphQL Admin API when merchants should opt in to specific events or when you want to apply filters programmatically.
Prerequisites
- Admin API access token with the
write_*scopes required by each topic. - Publicly reachable HTTPS endpoint with a valid SSL certificate.
- Configured
api_versionso payload schemas align with your app release cadence.
App configuration file
Define subscriptions declaratively alongside your app source. Shopify provisions them automatically during installation and reconciles diffs whenever you deploy updates.
[webhooks]
api_version = "2023-04"
[[webhooks.subscriptions]]
topics = ["orders/create", "products/update"]
uri = "https://your-backend.com/webhooks"- topics: select events from Shopify's webhook topic catalog.
- uri: use HTTPS in production; for local testing tunnel traffic with Hooklistener or ngrok.
- Keep this file under version control so updates roll out consistently.
GraphQL Admin API
Use a webhookSubscriptionCreate mutation when subscriptions depend on merchant settings or you need to inject filters dynamically.
mutation webhookSubscriptionCreate {
webhookSubscriptionCreate(
topic: ORDERS_CREATE
webhookSubscription: {
format: JSON
callbackUrl: "https://your-backend.com/webhooks"
includeFields: ["id", "email", "total_price"]
}
) {
webhookSubscription {
id
topic
endpoint {
__typename
... on WebhookHttpEndpoint {
callbackUrl
}
}
}
userErrors {
field
message
}
}
}- Check
userErrorsto catch missing scopes or invalid filters. - Persist the subscription
idso you can delete or rotate it later. - Apply
filterorincludeFieldsto manage payload volume per merchant.
Implement a Verified Webhook Endpoint
Shopify expects endpoints to validate authenticity, respond quickly, and queue heavy work. Capture the raw body before parsing it so HMAC verification succeeds.
import express from "express";
import crypto from "crypto";
const app = express();
const secret = process.env.SHOPIFY_WEBHOOK_SECRET ?? "";
app.post(
"/webhooks",
express.text({ type: "*/*" }),
async (req, res) => {
const hmacHeader = req.get("X-Shopify-Hmac-Sha256") ?? "";
const digest = crypto
.createHmac("sha256", secret)
.update(req.body, "utf8")
.digest("base64");
if (!crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(hmacHeader))) {
return res.status(401).send("Unauthorized");
}
res.status(200).send("OK");
const topic = req.get("X-Shopify-Topic");
const shop = req.get("X-Shopify-Shop-Domain");
const payload = JSON.parse(req.body);
await enqueueWebhook({ topic, shop, payload });
},
);
async function enqueueWebhook(event: {
topic: string | null;
shop: string | null;
payload: unknown;
}) {
// Push onto a queue (BullMQ, SQS, Cloud Tasks, etc.) for async processing
}
app.listen(3000, () => console.log("Webhook server listening on port 3000"));// Using @shopify/shopify-app-express to validate requests
import { shopifyApp } from "@shopify/shopify-app-express";
const shopify = shopifyApp({
api: {
apiKey: process.env.SHOPIFY_API_KEY!,
apiSecretKey: process.env.SHOPIFY_API_SECRET!,
},
});
app.post(
"/webhooks",
express.text({ type: "*/*" }),
async (req, res) => {
const { valid, topic, domain } = await shopify.webhooks.validate({
rawBody: req.body,
rawRequest: req,
rawResponse: res,
});
if (!valid) {
return res.status(400).send("Bad Request");
}
res.status(200).send("OK");
await enqueueWebhook({ topic, shop: domain, payload: JSON.parse(req.body) });
},
);Filter Events to Reduce Noise
Shopify lets you filter subscriptions so that only specific objects trigger delivery. Filters use the same syntax as Shopify search, giving you precise control over payload volume.
[[webhooks.subscriptions]]
topics = ["products/update"]
uri = "https://your-backend-service.com/webhooks"
filter = "status:active AND variants.price:>=100"- Filters run before delivery, lowering processing and storage costs.
- Apply filters in configuration files and GraphQL mutations.
- Document applied filters so downstream services know which events are omitted.
Trim Payloads to Essential Fields
Large Shopify objects can exceed 100 KB. Payload transformations let you request only the fields your backend needs, speeding up processing and storage.
- Use
includeFieldsin GraphQL mutations to deliver a trimmed schema. - Store payload documentation alongside your code to avoid brittle assumptions.
- Combine payload shaping with filters for the leanest possible webhook traffic.
Secure Incoming Shopify Requests
Every webhook must be verified to prevent spoofing. Shopify signs the request body using your app secret and places the digest in X-Shopify-Hmac-Sha256. Recreate the signature with the same secret before trusting the payload.
import { createHmac, timingSafeEqual } from "crypto";
export function verifyShopifyWebhook(rawBody: Buffer, hmacHeader: string, secret: string) {
const digest = createHmac("sha256", secret).update(rawBody).digest("base64");
const valid = timingSafeEqual(Buffer.from(digest), Buffer.from(hmacHeader));
if (!valid) {
throw new Error("Invalid Shopify signature");
}
}
export function handleDuplicate(eventId: string, store: Set<string>) {
if (store.has(eventId)) {
return false;
}
store.add(eventId);
return true;
}Security checklist
- Capture the raw request body before any JSON parsing middleware runs.
- Log
X-Shopify-Webhook-IdandX-Shopify-Event-Idfor replay detection. - Use
X-Shopify-Triggered-Atto order out-of-sequence deliveries. - Rotate webhook secrets if your app secret changes and update verification code simultaneously.
Performance & Reliability Tactics
Reliability
- Respond within five seconds; enqueue heavy processing for background jobs.
- Implement retry-aware queues (BullMQ, SQS, Cloud Tasks) to smooth bursts.
- Run reconciliation cron jobs to compare webhook events with API state.
Performance
- Enable HTTP keep-alive on outbound calls made during processing.
- Use connection pools or serverless concurrency limits to handle spikes.
- Instrument success and failure metrics per topic to spot regressions quickly.
Common Shopify Webhook Topics
| Topic | Description |
|---|---|
orders/create | Triggered when a new order is created |
orders/updated | Order details have changed |
orders/fulfilled | Fulfillment status updated to fulfilled |
products/update | Product information changed |
customers/create | New customer account created |
inventory_levels/update | Inventory quantity changed for a location |
Mandatory compliance topics
Public apps distributed through the Shopify App Store must subscribe to:
customers/data_requestcustomers/redactshop/redact
Testing & Troubleshooting
- Expose local endpoints with Hooklistener tunnels or ngrok:
ngrok http 3000. - Trigger test webhooks via the Admin API or by simulating events in a development store.
- Investigate missing webhooks by checking HTTPS availability, SSL validity, and subscription status.
- Track signature failures to surface secret mismatches or body parsing issues.
Summary of Subscription Options
| Method | Setup Location | Primary Use Case | Example Topic |
|---|---|---|---|
| App configuration file | App source or config repo | Standardized events for every install | products/create |
| GraphQL Admin API | App backend at install time | Per-shop customization or opt-in events | orders/create |
| Google Pub/Sub | Google Cloud project | Event pipelines and analytics workloads | app/uninstalled |
| Amazon EventBridge | AWS account | Serverless routing and Step Functions | orders/fulfilled |
Build Faster with Hooklistener
Hooklistener captures Shopify webhooks in real-time, forwards them to your tunnels, and keeps an audit trail for replay. Ship production-ready integrations faster with tooling built for modern webhook teams.