How to Securely Verify Shopify Webhooks in a Next.js App
April 4, 2026How to Securely Verify Shopify Webhooks in a Next.js App
If you are building a custom Shopify app or a headless storefront using Next.js in 2026, you are almost certainly using webhooks. Whether it’s to update your local database when an order is paid or to trigger a WhatsApp message when a fulfillment is created, webhooks are essential "tech plumbing."
However, there is a major security flaw I see in many junior-level implementations: failing to verify the webhook signature. Without verification, anyone who knows your endpoint URL can send fake data to your server, potentially creating fake orders or corrupting your data.
1. The Anatomy of a Shopify Webhook
Every webhook sent by Shopify includes a
X-Shopify-Hmac-Sha2562. The Next.js Challenge: Raw Body Access
In Next.js (especially the App Router), accessing the "raw" request body can be tricky because the framework often parses it as JSON automatically. To verify an HMAC, you must have the exact, un-parsed raw string.
Here is the high-level pattern for a secure Route Handler:
import { NextRequest, NextResponse } from 'next/server'; import crypto from 'crypto'; export async function POST(req: NextRequest) { const hmac = req.headers.get('x-shopify-hmac-sha256'); const topic = req.headers.get('x-shopify-topic'); const shop = req.headers.get('x-shopify-shop-domain'); // 1. Get the raw body as a string const rawBody = await req.text(); // 2. Recreate the HMAC using your secret const generatedHash = crypto .createHmac('sha256', process.env.SHOPIFY_API_SECRET!) .update(rawBody, 'utf8') .digest('base64'); // 3. Securely compare the hashes if (generatedHash !== hmac) { return new NextResponse('Invalid signature', { status: 401 }); } // 4. Process the validated data const data = JSON.parse(rawBody); console.log(`Received ${topic} from ${shop}`); return new NextResponse('OK', { status: 200 }); }
3. Why crypto.timingSafeEqual
Matters
crypto.timingSafeEqualIn a production environment, you should actually use
crypto.timingSafeEqual4. Environment Variables and Secret Rotation
Never hardcode your
SHOPIFY_API_SECRET.env5. Testing with Ngrok
Since Shopify can't send webhooks to
localhostConclusion
Security isn't an "add-on"; it’s a foundational requirement. By verifying your Shopify webhooks, you are protecting your business and your customers' data from malicious actors. It’s a few extra lines of code that could save you from a massive data disaster.
Need help securing your headless Shopify architecture? Let's audit your endpoints.