FeaturesGuidesBlogDocumentationFreeGet Started

Test Shopify Webhooks in Development

Shopify Admin custom app webhooks in dev — HMAC validation and tunnel URLs without a public staging store.

Shopify signs every webhook with HMAC-SHA256 using your app secret — the flow differs from Stripe's whsec_ CLI pattern. Use the webhook testing hub to decide tunnel vs other tools first.

Try it in 60 seconds

Shopify delivers webhooks to HTTPS URLs only. Get a public endpoint without deploying:

shopify app dev   # or npm run dev
npx localtolink --port 3000

Register https://k7m2x9p4q1.localtolink.com/webhooks/shopify in Partners Dashboard. Trigger a test order in your development store. Check your server logs for the POST — if it arrives, HMAC verification is the next step, not networking.

Shopify webhooks during development

Shopify apps receive webhook events for orders, products, customers, app uninstalls, and GDPR data requests. During local development, your app runs on localhost — unreachable from Shopify's servers. A tunnel gives Shopify a public HTTPS URL to deliver events to your local handler.

This guide covers tunnel setup, webhook registration, HMAC verification, and combining OAuth with webhooks on the same subdomain.

Setup

Terminal 1 — your Shopify app:

shopify app dev
# or: npm run dev

Terminal 2 — tunnel:

npx localtolink --port 3000

Note the assigned subdomain.

Register webhook URL

Depending on your app setup, register webhooks via:

Shopify Partners Dashboard → Your App → Configuration → Webhooks

Admin API — programmatic registration during app setup

shopify.app.toml — for Shopify CLI apps:

[webhooks]
api_version = "2024-10"

[[webhooks.subscriptions]]
topics = ["orders/create"]
uri = "/webhooks/shopify"

With a tunnel, your full webhook URL becomes:

https://k7m2x9p4q1.localtolink.com/webhooks/shopify

Shopify CLI's shopify app dev may manage tunnel URLs automatically in some configurations. When using LocalToLink manually, register the URL in your app configuration.

Verify HMAC signatures

Shopify signs every webhook with HMAC-SHA256 using your app's client secret. Verify before processing:

import crypto from "crypto";

function verifyShopifyWebhook(rawBody, hmacHeader, secret) {
  const hash = crypto
    .createHmac("sha256", secret)
    .update(rawBody, "utf8")
    .digest("base64");
  try {
    return crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(hmacHeader));
  } catch {
    return false;
  }
}

// Handler
const hmac = req.headers["x-shopify-hmac-sha256"];
if (!verifyShopifyWebhook(rawBody, hmac, process.env.SHOPIFY_CLIENT_SECRET)) {
  return new Response("Unauthorized", { status: 401 });
}

Use the raw request body — parsing JSON before verification breaks HMAC validation.

OAuth + webhooks on the same tunnel

Shopify apps typically need both OAuth (install flow) and webhooks. Register the same subdomain for both:

SettingExample URL
App URLhttps://k7m2x9p4q1.localtolink.com
Allowed redirection URLhttps://k7m2x9p4q1.localtolink.com/auth/callback
Webhook URLhttps://k7m2x9p4q1.localtolink.com/webhooks/shopify

When the subdomain changes (new tunnel session), update all three in Partners Dashboard.

See OAuth Callback on Localhost for redirect URI details.

Mandatory compliance webhooks

Shopify requires apps to handle GDPR webhooks:

Register these topics and implement handlers before app review. Test delivery through the tunnel the same way as business webhooks.

Common Shopify webhook topics

TopicUse case
orders/createFulfillment integrations
products/updateCatalog sync
app/uninstalledCleanup tenant data
customers/data_requestGDPR compliance (required)
shop/redactGDPR compliance (required)

Implement compliance handlers even if business logic is incomplete — app review checks for them.

Partners Dashboard vs tunnel subdomain

Shopify signs webhooks with your app secret against the raw request body. Log the body before JSON parsing in development so HMAC mismatches are obvious. After each new npx localtolink session, update both the App URL and webhook endpoints in Partners — install flows and webhooks fail independently if only one field is refreshed. Start from Expose Localhost to the Internet if you need the tunnel basics.

Important notes

Troubleshooting

ProblemFix
HMAC verification failsUse raw body; confirm client secret matches app
Webhook not deliveredConfirm URL in Partners Dashboard matches tunnel subdomain
OAuth works, webhooks failSeparate URL registrations — check both
Session endedRestart tunnel and update URLs
App install redirect failsUpdate allowed redirection URL with new subdomain

FAQ

Can I use Shopify CLI's built-in tunnel?
Shopify CLI may provide its own tunnel during shopify app dev. LocalToLink is an alternative when you want a standalone tunnel or CLI tunnel is unavailable.

Do I need a development store?
Yes. Create one in Partners Dashboard for testing installs and webhooks.

What about Shopify Functions?
Functions run on Shopify's infrastructure, not localhost. This guide covers app webhooks only.

See Test Webhooks Locally for the general webhook hub.

Try it now

Start a tunnel from your project directory — no account required.

$ npx --yes localtolink@latest

See the full CLI reference for flags and exit codes.

LocalToLink CLI 1.0.1 — verified 2026-09-20