Shopify webhooks push real-time event data to your server the moment something happens in your store. An order gets placed. A product gets updated. A customer cancels.
Your server receives that payload and acts on it instantly.
That speed is the power of webhooks. But it also creates a serious problem: anyone can send a POST request to your endpoint. Without verification, your system has no way to tell a legitimate Shopify event from a forged one.
Shopify webhook verification is the process of confirming that an incoming webhook payload was genuinely sent by Shopify and has not been tampered with in transit. Shopify handles this through a cryptographic signature attached to every request it sends.
If you are new to how webhooks function inside a Shopify store, the Shopify Webhooks guide on KolachiTech covers the fundamentals you need before diving into security.
Why Webhook Security Cannot Be Optional
Unverified webhooks are an open door. An attacker who knows your webhook endpoint URL can:
- Trigger fake order fulfillments
- Inject fraudulent customer data
- Flood your system with requests to exhaust resources
- Manipulate inventory counts through spoofed stock events
The damage compounds quickly when your webhooks connect to an ERP, a CRM, or a fulfillment warehouse. A single forged payload can cascade into corrupted data across multiple systems.
If your store uses Shopify ERP integration or connects to third-party platforms through scalable Shopify integration patterns, webhook security becomes even more critical. Compromised webhook events will silently corrupt data in every connected system.
Skipping verification is not a shortcut. It is a liability.
How HMAC Verification Works
Shopify uses HMAC-SHA256 (Hash-Based Message Authentication Code) to sign every webhook it sends.
Here is how the process works at a high level:
| Step | Actor | Action |
|---|---|---|
| 1 | Shopify | Takes the raw request body |
| 2 | Shopify | Signs it using your app’s shared secret with SHA256 |
| 3 | Shopify | Base64-encodes the resulting hash |
| 4 | Shopify | Sends it in the X-Shopify-Hmac-SHA256 header |
| 5 | Your Server | Recomputes the same HMAC using the raw body + shared secret |
| 6 | Your Server | Compares the two hashes using a constant-time function |
| 7 | Your Server | Accepts or rejects the request based on the match |
The critical point is step 5 and 6. Your server must perform the same calculation Shopify performed and compare the results. If the hashes match, the payload is authentic. If they differ, the request is rejected immediately.
Why HMAC Works
HMAC is effective because only two parties know the shared secret: Shopify and your application. Without that secret, an attacker cannot produce a valid signature, even if they know the exact payload contents.
This is different from simple API key authentication. An API key proves who you are. An HMAC signature proves that the message content itself has not changed since it was signed.
Step-by-Step: Verifying a Shopify Webhook
The implementation varies slightly by language, but the logic is always the same.
Prerequisites
- Your Shopify app’s webhook signing secret (found in Partner Dashboard under your app’s API credentials)
- The raw request body (not parsed JSON, the exact bytes as received)
- The value of the
X-Shopify-Hmac-SHA256request header
Node.js Implementation
const crypto = require('crypto');
function verifyShopifyWebhook(rawBody, hmacHeader, secret) {
const generatedHmac = crypto
.createHmac('sha256', secret)
.update(rawBody, 'utf8')
.digest('base64');
// Use timingSafeEqual to prevent timing attacks
const hmacBuffer = Buffer.from(hmacHeader, 'base64');
const generatedBuffer = Buffer.from(generatedHmac, 'base64');
if (hmacBuffer.length !== generatedBuffer.length) {
return false;
}
return crypto.timingSafeEqual(hmacBuffer, generatedBuffer);
}
Python Implementation
import hmac
import hashlib
import base64
def verify_shopify_webhook(raw_body: bytes, hmac_header: str, secret: str) -> bool:
digest = hmac.new(
secret.encode('utf-8'),
raw_body,
digestmod=hashlib.sha256
).digest()
computed_hmac = base64.b64encode(digest).decode('utf-8')
# Compare in constant time
return hmac.compare_digest(computed_hmac, hmac_header)
Critical Implementation Rules
| Rule | Why It Matters |
|---|---|
| Use the raw body, never parsed JSON | Parsing changes whitespace and key order, breaking the signature |
| Use constant-time comparison | Prevents timing side-channel attacks |
| Return 401 or 403 on failure | Do not process the payload or reveal error details |
| Never log the shared secret | Logs are often less protected than code |
| Read the body once and buffer it | Streaming parsers may consume the body before verification runs |
Common Webhook Security Vulnerabilities
Many teams implement HMAC verification correctly but still leave gaps in their webhook security layer. These are the most common mistakes.
1. Parsing Before Verifying
Your framework’s body parser often transforms the raw payload before your verification code runs. If you compute the HMAC on parsed JSON instead of the original bytes, the hashes will never match, so developers remove verification entirely out of frustration.
Fix: Buffer the raw request body first. In Express.js, use express.raw({ type: 'application/json' }) on your webhook route before any JSON parsing middleware.
2. String Comparison Instead of Constant-Time Comparison
Using === or == to compare strings leaks timing information. An attacker can measure how long your comparison takes and deduce how many bytes of the signature they guessed correctly.
Fix: Always use crypto.timingSafeEqual() in Node.js or hmac.compare_digest() in Python.
3. Shared Secret Rotation Never Planned
Shared secrets are static by default. If a secret leaks, you have no way to invalidate old signatures without rotating it. Most teams have no process for this.
Fix: Document a secret rotation procedure. Test it before you need it.
4. Ignoring Duplicate Events
Shopify retries webhooks when your server returns a non-2xx response. Your endpoint may receive the same event multiple times. Without idempotency controls, you can process an order twice.
The idempotency strategies guide for Shopify systems covers exactly how to handle this at the data layer.
5. No Timeout on Signature Validation
Verification adds latency. Without a timeout, a slow verification process can become a vector for denial-of-service attacks.
Fix: Set a strict timeout on your webhook handler, typically under 5 seconds.
Replay Attack Prevention
HMAC verification confirms authenticity but not freshness. A valid, signed webhook payload from 24 hours ago is still cryptographically valid today. An attacker who captures a legitimate payload can replay it later.
Shopify includes a timestamp in most webhook headers through the X-Shopify-Hmac-Triggered-At value embedded in the payload. You should also check the X-Shopify-Topic and X-Shopify-Shop-Domain headers to confirm the event belongs to the correct store.
Replay Prevention Strategy
| Method | How It Works |
|---|---|
| Timestamp validation | Reject payloads older than 5 minutes |
| Nonce tracking | Store a unique payload hash; reject if seen before |
| Event ID deduplication | Use Shopify’s event ID to detect reprocessed events |
For teams running high-volume webhook pipelines, combining timestamp validation with a fast cache lookup (Redis works well here) provides strong replay protection without significant overhead.
The queue-based Shopify webhook processing architecture explains how to slot verification into an async processing pipeline without blocking your main application thread.
Advanced: Multi-Store Webhook Verification
If your Shopify app serves multiple stores, each store will have the same shared secret tied to your app credentials. But you must also verify that the event came from the specific store you expect.
Always validate the X-Shopify-Shop-Domain header against your database of registered stores. Reject any payload from a shop domain you do not recognize.
function verifyShopAndSignature(rawBody, headers, secret, registeredShops) {
const shopDomain = headers['x-shopify-shop-domain'];
// Verify shop is registered
if (!registeredShops.includes(shopDomain)) {
return false;
}
// Verify HMAC
return verifyShopifyWebhook(rawBody, headers['x-shopify-hmac-sha256'], secret);
}
This becomes especially important when your integration connects to a Shopify CRM synchronization system or a Shopify WMS integration that handles data from many merchants simultaneously.
Secure Webhook Infrastructure Checklist
Use this checklist before putting any webhook consumer into production.
Transport Layer
- [ ] Endpoint uses HTTPS only, no HTTP fallback
- [ ] TLS 1.2 or higher enforced
- [ ] Certificate renewed before expiry with monitoring in place
Signature Verification
- [ ] Raw body buffered before any parsing
- [ ] HMAC computed using SHA256 and the correct shared secret
- [ ] Constant-time comparison used, not
=== - [ ] Failed verifications return 401 or 403 immediately
- [ ] No payload processing occurs before verification passes
Replay Protection
- [ ] Timestamp header validated, reject anything older than 5 minutes
- [ ] Event ID stored and checked for duplicates
- [ ] Shop domain validated against allowed list
Operational Security
- [ ] Shared secret stored in environment variables or secrets manager, never in code
- [ ] Secret rotation procedure documented and tested
- [ ] Verification failures logged with enough context to investigate
- [ ] Webhook endpoint rate-limited at the infrastructure layer
Reliability
- [ ] Handler returns 200 immediately and processes asynchronously
- [ ] Dead letter queue configured for failed events
- [ ] Retry behavior tested under failure conditions
For the reliability side of this checklist, building reliable Shopify webhook consumers and the dead letter queue guide for Shopify webhooks give you detailed implementation patterns.
You can also review Shopify webhook retry strategies and webhook monitoring and observability to complete your production setup.
Webhook Security in the Broader Architecture
Secure Shopify webhooks are one layer of a larger system design. Verification stops forged payloads. But your system still needs to handle ordering problems, duplicate events, and delivery failures.
If you are designing a full event-driven backend, event-driven architecture for Shopify apps shows how webhook verification fits into a broader message-processing pipeline. For scaling under high event volume, scaling Shopify webhook infrastructure covers the infrastructure decisions that determine throughput and reliability.
FAQs
Q1: What is the X-Shopify-Hmac-SHA256 header used for? Shopify sends this header with every webhook. It contains a base64-encoded HMAC-SHA256 signature of the request body. Your server recomputes the same signature and compares it to confirm the payload is authentic and unmodified.
Q2: Where do I find my Shopify webhook signing secret? In the Shopify Partner Dashboard, navigate to your app’s API credentials. The webhook signing secret is listed there. Keep it in an environment variable and never commit it to source control.
Q3: Why must I use the raw request body for HMAC verification? Shopify signs the exact bytes it sends. Parsing the body into JSON and then stringifying it again can alter whitespace, key order, or encoding. This changes the byte content, causing your computed HMAC to differ from Shopify’s signature even on a legitimate request.
Q4: What HTTP status code should I return when verification fails? Return 401 (Unauthorized) or 403 (Forbidden). Do not return 200 on a failed verification, as that tells Shopify the delivery succeeded. Do not return verbose error messages that hint at what failed.
Q5: Can a verified webhook still be replayed by an attacker? Yes. HMAC verification confirms authenticity but not freshness. A captured valid payload can be replayed later. Add timestamp validation and event ID deduplication to your handler to prevent replay attacks.
Q6: Do I need different secrets for different Shopify stores? No. With Shopify app webhooks, all stores using your app share the same signing secret tied to your app credentials. However, you must still validate the X-Shopify-Shop-Domain header to confirm the event came from the expected store.
Q7: What happens if Shopify rotates or I rotate the webhook signing secret? Any webhooks in flight during the rotation will fail verification. Plan rotations during low-traffic windows. Update your environment variable immediately after rotating in the Partner Dashboard and verify delivery resumes correctly.
Q8: Is webhook verification enough to secure my Shopify integration? Verification is a critical first layer, but not the only one. You also need idempotency controls, rate limiting, replay protection, TLS enforcement, and proper secret management to build a fully secure webhook system.
