Blog
Technical10 min read

Webhook Debugging: Why Your VIP Alerts Aren't Firing (And How to Fix It)

DH
Dennis Hegstad
Founder, sonarID · February 23, 2026
Webhook Debugging: Why Your VIP Alerts Aren't Firing (And How to Fix It)

If your VIP alerts have gone quiet while high-value orders keep coming in, the cause is almost always one of four things: the webhook subscription was never created or got deleted, Shopify is delivering the payload but your endpoint returns a non-200 response, your endpoint times out before it can respond, or HMAC verification is silently rejecting the request. The fastest way to diagnose it is to open your webhook delivery log, find the most recent order, and read the HTTP status code your endpoint returned. A 200 means the order reached you and the problem is downstream in your alerting logic. Anything else, whether a 401, 404, 5xx, or no delivery at all, means the webhook itself is broken.

This guide walks each case in order, from confirming the subscription exists to setting up absence-of-signal monitoring. Webhooks are the backbone of real-time VIP detection: when a customer checks out, Shopify fires an orders/create event to a URL you registered, your system enriches that order against identity signals, scores the customer, and pushes an alert to Slack or Klaviyo. Every link in that chain can fail quietly, because webhooks are fire-and-forget from the merchant's point of view. Nothing in the Shopify admin turns red when deliveries start failing. You only notice when a VIP slips through and nobody got pinged. The good news is that webhook failures are deterministic and almost entirely observable once you know where to look. If you want the full setup walkthrough rather than a debugging pass, start with setting up Shopify webhooks for VIP alerts and come back here when something breaks.

Step One: Confirm The Subscription Actually Exists

The most common reason VIP alerts stop firing is that the webhook subscription is simply gone. App reinstalls, scope changes, switching from a development store to production, or a theme migration can all wipe or orphan subscriptions, and Shopify does not notify you when it happens.

List your active webhook subscriptions through the Admin API and confirm three things. First, an orders/create subscription exists, or orders/paid if you prefer to score only after payment clears. Second, its address field points at your live production endpoint and not an old ngrok tunnel or a staging URL left over from testing. Third, the API version on the subscription is one Shopify still supports, because subscriptions pinned to a deprecated version stop delivering after that version sunsets.

If you find a stale or missing subscription, recreate it pointing at your current HTTPS endpoint. A subtle trap: webhook subscriptions are tied to the access token and app that created them. If you rotated credentials or reinstalled the app, the old subscriptions may still appear in the list but no longer map to a valid token, so they go dark. Delete the orphans and create fresh ones under the current installation.

Step Two: Read The Delivery Log And The Status Code

Once the subscription is confirmed, the delivery log tells you the rest of the story. Shopify records the HTTP status your endpoint returned for recent deliveries, and your own application logs should record every inbound request. Line these up against a known recent order and read the status code.

A 200 means Shopify delivered the order and your endpoint accepted it. If alerts still are not firing, the bug is past the webhook layer, somewhere in your enrichment, scoring, or notification code, so skip ahead to the monitoring section.

A 401 or 403 almost always means HMAC verification failed: your endpoint received the request but rejected it as unauthorized. This is the single most common false failure in webhook debugging, and it gets its own section below.

A 404 means the URL is wrong. The subscription points at a path your server no longer serves, usually because a route was renamed or a deploy changed the URL structure. Fix the subscription address or the route.

A 5xx means your code threw an error while processing the payload. Pull the stack trace from your application logs for that request. Often it is a payload-shape assumption that broke, which Step Six covers.

No delivery record at all, despite a real order, points back to Step One: the subscription is not active, or it is pointed somewhere you are not watching.

Step Three: Fix HMAC Verification, The Silent Rejector

Every Shopify webhook arrives with an X-Shopify-Hmac-Sha256 header. You are expected to compute an HMAC-SHA256 of the raw request body using your app's shared secret and compare it to that header. If they do not match, you reject the request. This is correct and necessary security, but it is also where good payloads get thrown away by accident.

The number one cause of HMAC mismatch is computing the signature over a parsed body instead of the raw bytes. Many web frameworks parse JSON into an object before your handler runs, and if you then re-serialize that object to verify, the byte order, whitespace, and key formatting will differ from what Shopify signed, so the HMAC never matches. You must capture the raw, unmodified request body before any JSON middleware touches it and run the HMAC over those exact bytes.

The second cause is the wrong secret. Apps installed through the Shopify App Store sign with the app's client secret. Custom apps and webhooks created in the admin can sign with a different secret shown at creation time. If you verify against the wrong one, every request fails. Confirm which secret corresponds to the subscription that is actually delivering.

A third, sneakier cause is an encoding mismatch. The header is base64-encoded. If your comparison expects hex, or you compare a base64 string against a raw digest, you get a false negative on every request. Use a constant-time comparison on matching encodings.

To test HMAC in isolation, log both the computed digest and the header value for a few requests, but never log the secret itself. If they differ consistently by the same transformation, you have an encoding or raw-body problem, not a secret problem.

Step Four: Diagnose Timeouts

Shopify expects your endpoint to respond within a few seconds. If your handler does enrichment, scoring, a database write, and a Slack call all inline before returning 200, you are racing the clock. Under load, or when an enrichment provider is slow, the handler blows past the timeout, Shopify records the delivery as failed, and the order never gets processed even though your code was technically working.

The fix is to decouple acknowledgment from processing. Your webhook handler should do the absolute minimum: verify the HMAC, persist the raw payload to a queue or table, and return 200 immediately. A separate worker then picks up the job and runs the slow work of enrichment and alerting. This pattern is non-negotiable at any real order volume, and it is exactly how a real-time system should be built so that a slow third-party call never costs you a VIP detection. For more on why this matters under load, see our guide on managing API rate limits for real-time enrichment.

A timeout often shows up in the log as a delivery Shopify marks failed with no status code, or as a 5xx if your framework returns an error when the response is cut off. If failures correlate with traffic spikes or with one slow downstream dependency, timeouts are your culprit.

Step Five: Understand Shopify's Retry Behavior

Shopify does not give up on the first failed delivery. When your endpoint returns a non-200 or times out, Shopify retries the webhook on a backoff schedule over roughly the next couple of days. This is a safety net, but it has two consequences you must design around.

First, retries mean you will receive the same order more than once whenever a delivery is marked failed but your code actually processed it. If your enrichment costs money per lookup, naive reprocessing on every retry burns budget and can double-fire alerts. Make your processing idempotent: key off the order ID or the X-Shopify-Webhook-Id header and skip work you have already completed. Idempotency is the difference between a retry being harmless and a retry being expensive.

Second, Shopify will eventually stop retrying. If an endpoint fails enough deliveries over the retry window, Shopify can remove the subscription entirely and send the app owner a removal notice. This is the trapdoor that turns a brief outage into permanently silent alerts. A 30-minute deploy where your endpoint returns 500s can, in the worst case, cost you the subscription. After any incident, go back to Step One and confirm the subscription still exists.

Step Six: Validate The Payload Shape

Sometimes the webhook delivers, HMAC passes, and your code still fails because it assumes something about the payload that is not always true. Real order payloads are messier than the documentation examples.

The email field can be null on orders placed through some channels or with certain guest-checkout configurations. If your scoring logic dereferences the email without a null check, it throws, and you get a 5xx on exactly the orders that matter. The shipping address can be absent on digital-only orders, which is critical because VIP scoring leans on the shipping address as the residence signal. Your code needs a deliberate fallback when shipping is missing rather than crashing. Phone, company, and the customer object can all be partially populated, and country and province codes vary in format across markets.

Validate defensively. Treat every field as optional, log the payloads that fail your assertions, and make sure missing data degrades scoring gracefully instead of throwing. Clean inbound data also pays off downstream, which is why we treat email and address data hygiene as part of the pipeline rather than an afterthought. If you are still weighing webhooks against polling for this pipeline, the tradeoffs are laid out in webhooks versus API polling.

Step Seven: Set Up Monitoring So You Never Debug Blind Again

Webhook failures hurt because they stay invisible until a VIP slips through. You fix that with monitoring, not vigilance.

Track inbound webhook volume and alert on absence. If you normally receive a steady stream of orders/create events and that stream drops to zero for longer than your quietest expected gap, something is wrong, and you want to know within minutes, not when you notice a missed VIP next week. Absence-of-signal monitoring catches the dead-subscription case that nothing else will.

Record the outcome of every webhook: received, HMAC passed, enriched, scored, alert sent. A simple per-order status trail turns every future debugging session into a database query instead of a guess. When a merchant says an alert never fired, you can point to the exact step where the order fell out of the pipeline.

Watch your status-code distribution. A rising rate of 5xx or 401 responses is an early warning that a deploy broke something, and it surfaces before any human notices missing alerts. Pair this with downstream delivery confirmation: an order can pass through your whole pipeline and still fail to reach the merchant if the Slack token expired or the Klaviyo flow is misconfigured. Confirm the alert actually landed, not just that you sent it. Our breakdown of Slack alerts for VIP orders covers the most common notification-side failures.

Where SonarID Fits

This is a lot of plumbing to own yourself, and every piece of it is a place where a VIP can go undetected. SonarID runs this pipeline as managed infrastructure: it maintains the webhook subscription, verifies HMAC correctly against raw payloads, acknowledges fast and processes asynchronously so timeouts cannot drop orders, handles retries idempotently so you never double-pay for enrichment or double-fire an alert, and validates payloads defensively against null emails and missing shipping addresses. When an order arrives, it enriches against the free signal layer of email-domain, spend, and affluent-zip matching, then optionally runs full enrichment at $0.05 per enrichment within your plan's cap, scores the customer using the shipping address as the residence signal, and fires the alert to Slack or Klaviyo. Monitoring is built in, so a dead subscription or a downstream notification failure surfaces as an issue you can see rather than a VIP you missed.

If you are building this yourself, the seven steps above are the whole map: confirm the subscription, read the status code, fix HMAC, decouple to avoid timeouts, design for retries, validate the payload, and monitor for absence. If you would rather the plumbing just work, that is what we built. Either way, the goal is the same: every VIP order should fire an alert, every time, with no silent failures in between. To go deeper on the alerting side once delivery is solid, see real-time VIP order alerts and how this connects to Shopify Flow automation.

Frequently asked questions

Why did my Shopify webhook stop firing suddenly with no error?

The most likely cause is a deleted or orphaned subscription after an app reinstall, scope change, or credential rotation, since Shopify removes failing subscriptions silently. List your active webhook subscriptions through the Admin API and confirm one still points at your live endpoint on a supported API version.

How do I know if my webhook is failing on HMAC verification?

Check your delivery log for 401 or 403 status codes, which mean your endpoint received the request but rejected it. The usual cause is computing the HMAC over a parsed and re-serialized body instead of the raw request bytes, so capture the unmodified raw body before any JSON middleware runs.

What happens when my endpoint times out responding to a Shopify webhook?

Shopify marks the delivery failed and retries on a backoff schedule, but the order is not processed until a retry succeeds. Fix it by acknowledging fast: verify HMAC, queue the raw payload, return 200 immediately, and run enrichment and alerting in a separate background worker.

Will Shopify permanently disable my webhook if my endpoint keeps failing?

Yes. If deliveries fail repeatedly across the retry window of roughly a couple of days, Shopify can remove the subscription entirely and notify the app owner, which turns a short outage into permanently silent alerts. After any incident, re-confirm the subscription still exists.

How do I stop webhook retries from double-charging my enrichment or double-firing alerts?

Make processing idempotent by keying off the order ID or the X-Shopify-Webhook-Id header and skipping any work you already completed. A retry of an order you already processed then stays harmless instead of triggering a second paid lookup or a duplicate alert.

How can I monitor webhooks so I catch failures before a VIP slips through?

Use absence-of-signal alerting that fires when inbound webhook volume drops to zero longer than your quietest expected gap, record a per-order status trail through each pipeline step, and watch your status-code distribution for rising 401s or 5xxs. Also confirm the downstream alert actually reached Slack or Klaviyo, not just that you sent it.

Ready to know who is buying from you?

Start identifying VIP customers, influencers, and notable figures in your order stream — automatically.

Start detecting VIPs
End
DH
Written by
Dennis Hegstad
Founder, sonarID