<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: ShopHooks</title>
    <description>The latest articles on DEV Community by ShopHooks (@shophooks).</description>
    <link>https://dev.to/shophooks</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3779766%2F2b6cbba6-9075-4c28-8658-2a4eab801f9b.png</url>
      <title>DEV Community: ShopHooks</title>
      <link>https://dev.to/shophooks</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shophooks"/>
    <language>en</language>
    <item>
      <title>How Shopify Silently Kills Your Webhook Subscriptions (And How to Prevent It)</title>
      <dc:creator>ShopHooks</dc:creator>
      <pubDate>Wed, 18 Feb 2026 15:34:23 +0000</pubDate>
      <link>https://dev.to/shophooks/how-shopify-silently-kills-your-webhook-subscriptions-and-how-to-prevent-it-1d3f</link>
      <guid>https://dev.to/shophooks/how-shopify-silently-kills-your-webhook-subscriptions-and-how-to-prevent-it-1d3f</guid>
      <description>&lt;p&gt;Here's something most Shopify app developers learn the hard way: Shopify will silently delete your webhook subscriptions if your endpoint fails — and it won't tell you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No email. No notification. No entry in your Partner Dashboard. Just... silence.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Shopify Webhook Retry Policy
&lt;/h2&gt;

&lt;p&gt;When your webhook endpoint fails to respond with a &lt;code&gt;2xx&lt;/code&gt; status within 5 seconds, Shopify doesn't immediately give up. It retries. After the original delivery attempt plus &lt;strong&gt;18 retries&lt;/strong&gt; over roughly &lt;strong&gt;48 hours&lt;/strong&gt;, Shopify &lt;strong&gt;permanently removes&lt;/strong&gt; your webhook subscription.&lt;/p&gt;

&lt;p&gt;This is documented in &lt;a href="https://shopify.dev/docs/apps/build/webhooks/troubleshooting-webhooks" rel="noopener noreferrer"&gt;Shopify's webhook troubleshooting docs&lt;/a&gt;, but it's easy to miss — and the consequences are severe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Is Worse Than a Regular Outage
&lt;/h2&gt;

&lt;p&gt;Most server outages get noticed quickly — your app goes down, users complain, your monitoring catches it. But webhook endpoints are different:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;They're passive.&lt;/strong&gt; They only receive traffic when Shopify has an event to send. There's no user staring at a loading screen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failures are invisible.&lt;/strong&gt; Shopify retries silently. There's no proactive notification about delivery failures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The blast radius is delayed.&lt;/strong&gt; The real damage — missing orders, unfulfilled shipments, broken inventory sync — shows up hours or days later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-subscribing isn't automatic.&lt;/strong&gt; Once Shopify removes the subscription, it stays removed until you manually re-register it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A Real-World Scenario
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Timeline of a silent webhook failure&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;🟢 &lt;strong&gt;Monday 9:00 AM&lt;/strong&gt;&lt;br&gt;
Everything works fine. Your &lt;code&gt;orders/create&lt;/code&gt; webhook fires, your fulfillment system picks it up, customers get tracking emails.&lt;/p&gt;

&lt;p&gt;🟡 &lt;strong&gt;Tuesday 2:13 AM&lt;/strong&gt;&lt;br&gt;
A routine deploy introduces a regression. Your webhook handler starts returning 500s. Shopify begins retrying silently. You're asleep.&lt;/p&gt;

&lt;p&gt;🟠 &lt;strong&gt;Wednesday ~2 AM — 48 hours later&lt;/strong&gt;&lt;br&gt;
Shopify exhausts all 19 attempts. It silently deletes your webhook subscription. Your integration is now completely disconnected. Events still happen — they just have nowhere to go.&lt;/p&gt;

&lt;p&gt;🔴 &lt;strong&gt;Wednesday afternoon — a customer complains&lt;/strong&gt;&lt;br&gt;
"Why hasn't my order shipped?" You check the logs. Nothing. You check Shopify Admin. The webhook subscription is &lt;strong&gt;gone&lt;/strong&gt;. You've been missing every order for 30+ hours.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Generic Uptime Monitors Don't Catch This
&lt;/h2&gt;

&lt;p&gt;"Just use UptimeRobot" is the usual advice. Here's why it's insufficient for webhooks:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UptimeRobot and Pingdom monitor availability.&lt;/strong&gt; They send a GET request and check if your server responds with 200. That tells you "your server is up."&lt;/p&gt;

&lt;p&gt;But webhook endpoints have different failure modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your server might be up, but your webhook &lt;em&gt;handler&lt;/em&gt; throws a 500 on POST requests&lt;/li&gt;
&lt;li&gt;Your server responds fine to GET, but times out on the actual webhook payload processing&lt;/li&gt;
&lt;li&gt;Your endpoint works, but Shopify already removed the subscription — so nothing is coming through&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The gap: generic monitors tell you if your server is up. They don't tell you if your webhook integration is actually working.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  What You Should Actually Monitor
&lt;/h2&gt;

&lt;p&gt;For reliable Shopify webhook integrations, you need to check:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Endpoint health&lt;/strong&gt; — Is the endpoint responding to the correct HTTP method (POST) with a &lt;code&gt;2xx&lt;/code&gt; status within Shopify's 5-second timeout?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response time trends&lt;/strong&gt; — Is your response time creeping up? A handler averaging 4.5 seconds today will start timing out next week.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delivery gaps&lt;/strong&gt; — If you normally receive 50 events per day and suddenly get 0, something is wrong even if the endpoint is "up."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subscription status&lt;/strong&gt; — Is the webhook subscription still registered in Shopify?&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Prevention Checklist
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Acknowledge immediately, process async
&lt;/h3&gt;

&lt;p&gt;Your webhook handler &lt;strong&gt;must&lt;/strong&gt; respond within 5 seconds. If you do heavy processing, acknowledge first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Node.js — acknowledge immediately, process async&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/webhooks/orders-create&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Respond to Shopify immediately&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ok&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Process the order in the background&lt;/span&gt;
  &lt;span class="nf"&gt;processOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Failed to process order:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Add a health check endpoint
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Go — simple health check handler&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;healthCheck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ping&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"db unhealthy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;503&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Implement idempotent webhook handlers
&lt;/h3&gt;

&lt;p&gt;Shopify can send duplicate webhooks. Use the &lt;code&gt;X-Shopify-Webhook-Id&lt;/code&gt; header to deduplicate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Python — idempotent handler
&lt;/span&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/webhooks/orders-create&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;webhook_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;X-Shopify-Webhook-Id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;webhook:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;webhook_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;already processed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;

    &lt;span class="nf"&gt;process_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;webhook:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;webhook_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;172800&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Check your webhook subscriptions periodically
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List all registered webhooks for your Shopify app&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; GET &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://your-store.myshopify.com/admin/api/2024-01/webhooks.json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-Shopify-Access-Token: YOUR_TOKEN"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the list comes back shorter than expected, you've already lost subscriptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automated Monitoring with ShopHooks
&lt;/h2&gt;

&lt;p&gt;After experiencing this failure firsthand, I built &lt;a href="https://shophooks.dev" rel="noopener noreferrer"&gt;ShopHooks&lt;/a&gt; — a monitoring tool specifically designed for Shopify webhook endpoints.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;60-second health checks&lt;/strong&gt; using the same HTTP method Shopify uses (POST)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Smart alerts&lt;/strong&gt; via Slack and email with context about what failed and why&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;AI health analysis&lt;/strong&gt; that spots degradation trends before they become outages&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Recovery notifications&lt;/strong&gt; when endpoints come back up&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's &lt;a href="https://shophooks.dev/sign-up" rel="noopener noreferrer"&gt;free for up to 3 endpoints&lt;/a&gt; (no credit card required), which covers most single-store integrations. Setup takes about 60 seconds.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have you experienced silent webhook removal? I'd love to hear your war stories in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>webhooks</category>
      <category>monitoring</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
