How to Securely Verify Shopify Webhooks in a Next.js App

April 4, 2026

How 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-Sha256
header. This is a cryptographic signature created using your App’s "Shared Secret" and the raw body of the request. To secure your endpoint, you must recreate this signature on your server and compare it to the one Shopify sent.

2. 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

In a production environment, you should actually use

crypto.timingSafeEqual
for the comparison. This prevents "timing attacks" where an attacker guesses your secret by measuring how long it takes for your server to return a 401 error.

4. Environment Variables and Secret Rotation

Never hardcode your

SHOPIFY_API_SECRET
. Use an
.env
file. If you ever suspect your secret has been leaked, rotate it immediately in the Shopify Partner Dashboard and update your Next.js environment variables.

5. Testing with Ngrok

Since Shopify can't send webhooks to

localhost
, you’ll need a tool like Ngrok or Cloudflare Tunnel to expose your Next.js dev server. Always test your verification logic in development before pushing to Vercel or your local SA host.

Conclusion

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.


Related Articles